Compare commits

..
2 Commits
Author SHA1 Message Date
Efril 5d73f82017 fix payment struck 2026-01-13 23:41:56 +07:00
aefril 1d52f22f5f Merge pull request 'dev' (#1) from dev into main
Reviewed-on: #1
2025-08-13 17:19:46 +00:00
90 changed files with 2882 additions and 13330 deletions
+1 -5
View File
@@ -2,8 +2,6 @@ plugins {
id "com.android.application" id "com.android.application"
id "kotlin-android" id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin" id "dev.flutter.flutter-gradle-plugin"
id "com.google.gms.google-services"
id "com.google.firebase.crashlytics"
} }
def localProperties = new Properties() def localProperties = new Properties()
@@ -47,7 +45,7 @@ android {
applicationId "com.appscale.pos" applicationId "com.appscale.pos"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion 23 minSdkVersion flutter.minSdkVersion
targetSdkVersion 35 targetSdkVersion 35
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
@@ -71,6 +69,4 @@ dependencies {
//multiDex //multiDex
implementation 'com.android.support:multidex:1.0.3' implementation 'com.android.support:multidex:1.0.3'
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation platform('com.google.firebase:firebase-bom:34.2.0')
implementation 'com.google.firebase:firebase-crashlytics'
} }
-29
View File
@@ -1,29 +0,0 @@
{
"project_info": {
"project_number": "1094037575589",
"project_id": "apskel-pos",
"storage_bucket": "apskel-pos.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:1094037575589:android:5157684038a971a3d3fa4c",
"android_client_info": {
"package_name": "com.appscale.pos"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyAle1ZcO4Z7wtNJUHkHEVkuBHr6mHcvPvI"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
-2
View File
@@ -7,8 +7,6 @@ buildscript {
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.4.3'
classpath 'com.google.firebase:firebase-crashlytics-gradle:3.0.6'
} }
} }
+1 -2
View File
@@ -70,8 +70,7 @@ class _CustomDatePickerState extends State<CustomDatePicker> {
), ),
decoration: InputDecoration( decoration: InputDecoration(
suffixIcon: Padding( suffixIcon: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.all(16.0),
vertical: 8, horizontal: 16),
child: Assets.icons.calendar.svg(), child: Assets.icons.calendar.svg(),
), ),
prefix: widget.prefix, prefix: widget.prefix,
-409
View File
@@ -1,409 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_datepicker/datepicker.dart';
class DateRangePickerModal {
static Future<DateRangePickerSelectionChangedArgs?> show({
required BuildContext context,
String title = 'Pilih Rentang Tanggal',
DateTime? initialStartDate,
DateTime? initialEndDate,
DateTime? minDate,
DateTime? maxDate,
String confirmText = 'Pilih',
String cancelText = 'Batal',
Color primaryColor = Colors.blue,
Function(DateTime? startDate, DateTime? endDate)? onChanged,
}) async {
return await showDialog<DateRangePickerSelectionChangedArgs?>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) => _DateRangePickerDialog(
title: title,
initialStartDate: initialStartDate,
initialEndDate: initialEndDate,
minDate: minDate,
maxDate: maxDate,
confirmText: confirmText,
cancelText: cancelText,
primaryColor: primaryColor,
onChanged: onChanged,
),
);
}
}
class _DateRangePickerDialog extends StatefulWidget {
final String title;
final DateTime? initialStartDate;
final DateTime? initialEndDate;
final DateTime? minDate;
final DateTime? maxDate;
final String confirmText;
final String cancelText;
final Color primaryColor;
final Function(DateTime? startDate, DateTime? endDate)? onChanged;
const _DateRangePickerDialog({
required this.title,
this.initialStartDate,
this.initialEndDate,
this.minDate,
this.maxDate,
required this.confirmText,
required this.cancelText,
required this.primaryColor,
this.onChanged,
});
@override
State<_DateRangePickerDialog> createState() => _DateRangePickerDialogState();
}
class _DateRangePickerDialogState extends State<_DateRangePickerDialog>
with TickerProviderStateMixin {
DateRangePickerSelectionChangedArgs? _selectionChangedArgs;
late AnimationController _animationController;
late Animation<double> _scaleAnimation;
late Animation<double> _fadeAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_scaleAnimation = Tween<double>(
begin: 0.8,
end: 1.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: Curves.elasticOut,
));
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
));
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _onSelectionChanged(DateRangePickerSelectionChangedArgs args) {
setState(() {
_selectionChangedArgs = args;
});
// Note: onChanged callback is now called only when confirm button is pressed
// This allows users to see real-time selection without triggering callbacks
}
String _getSelectionText() {
if (_selectionChangedArgs?.value is PickerDateRange) {
final PickerDateRange range = _selectionChangedArgs!.value;
if (range.startDate != null && range.endDate != null) {
return '${_formatDate(range.startDate!)} - ${_formatDate(range.endDate!)}';
} else if (range.startDate != null) {
return _formatDate(range.startDate!);
}
}
return 'Belum ada tanggal dipilih';
}
String _formatDate(DateTime date) {
final months = [
'Jan',
'Feb',
'Mar',
'Apr',
'Mei',
'Jun',
'Jul',
'Agu',
'Sep',
'Okt',
'Nov',
'Des'
];
return '${date.day} ${months[date.month - 1]} ${date.year}';
}
bool get _isValidSelection {
if (_selectionChangedArgs?.value is PickerDateRange) {
final PickerDateRange range = _selectionChangedArgs!.value;
return range.startDate != null && range.endDate != null;
}
return false;
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return FadeTransition(
opacity: _fadeAnimation,
child: ScaleTransition(
scale: _scaleAnimation,
child: Dialog(
backgroundColor: Colors.transparent,
elevation: 0,
insetPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
child: Container(
width: MediaQuery.of(context).size.width,
constraints: BoxConstraints(
maxWidth: 400,
maxHeight: MediaQuery.of(context).size.height * 0.85,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
widget.primaryColor,
widget.primaryColor.withOpacity(0.8),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
child: Row(
children: [
Icon(
Icons.calendar_today_rounded,
color: Colors.white,
size: 24,
),
const SizedBox(width: 12),
Expanded(
child: Text(
widget.title,
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
// Scrollable Content
Flexible(
child: SingleChildScrollView(
child: Column(
children: [
// Selection Info
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: widget.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: widget.primaryColor.withOpacity(0.2),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Tanggal Terpilih:',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: widget.primaryColor,
),
),
const SizedBox(height: 4),
Text(
_getSelectionText(),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
],
),
),
// Date Picker
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16),
child: Container(
height: 320,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.grey.withOpacity(0.2),
),
),
child: SfDateRangePicker(
onSelectionChanged: _onSelectionChanged,
selectionMode:
DateRangePickerSelectionMode.range,
initialSelectedRange:
(widget.initialStartDate != null &&
widget.initialEndDate != null)
? PickerDateRange(
widget.initialStartDate,
widget.initialEndDate,
)
: null,
minDate: widget.minDate,
maxDate: widget.maxDate,
startRangeSelectionColor: widget.primaryColor,
endRangeSelectionColor: widget.primaryColor,
rangeSelectionColor:
widget.primaryColor.withOpacity(0.2),
todayHighlightColor: widget.primaryColor,
headerStyle: DateRangePickerHeaderStyle(
backgroundColor: Colors.transparent,
textAlign: TextAlign.center,
textStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
monthViewSettings:
DateRangePickerMonthViewSettings(
viewHeaderStyle:
DateRangePickerViewHeaderStyle(
backgroundColor:
Colors.grey.withOpacity(0.1),
textStyle: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: widget.primaryColor,
),
),
),
selectionTextStyle: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 14,
),
rangeTextStyle: TextStyle(
color: widget.primaryColor,
fontWeight: FontWeight.w500,
fontSize: 14,
),
),
),
),
],
),
),
),
// Action Buttons
Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => Navigator.of(context).pop(),
style: OutlinedButton.styleFrom(
padding:
const EdgeInsets.symmetric(vertical: 14),
side: BorderSide(color: Colors.grey.shade400),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
widget.cancelText,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.grey,
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton(
onPressed: _isValidSelection
? () {
// Call onChanged when confirm button is pressed
if (widget.onChanged != null &&
_selectionChangedArgs?.value
is PickerDateRange) {
final PickerDateRange range =
_selectionChangedArgs!.value;
widget.onChanged!(
range.startDate, range.endDate);
}
Navigator.of(context)
.pop(_selectionChangedArgs);
}
: null,
style: ElevatedButton.styleFrom(
backgroundColor: widget.primaryColor,
padding:
const EdgeInsets.symmetric(vertical: 14),
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
disabledBackgroundColor: Colors.grey.shade300,
),
child: Text(
widget.confirmText,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: _isValidSelection
? Colors.white
: Colors.grey.shade600,
),
),
),
),
],
),
),
],
),
),
),
),
);
},
);
}
}
+1 -1
View File
@@ -2,6 +2,6 @@ class Variables {
static const String appName = 'POS Kasir Resto App'; static const String appName = 'POS Kasir Resto App';
static const String apiVersion = 'v1'; static const String apiVersion = 'v1';
// static const String baseUrl = 'http://192.168.1.202:8000'; // static const String baseUrl = 'http://192.168.1.202:8000';
static const String baseUrl = 'https://api-pos.apskel.id'; static const String baseUrl = 'https://enaklo-pos-be.altru.id';
static const int defaultLimit = 10; static const int defaultLimit = 10;
} }
-87
View File
@@ -1,87 +0,0 @@
import 'dart:async';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DatabaseHelper {
static DatabaseHelper? _instance;
static Database? _database;
DatabaseHelper._internal();
static DatabaseHelper get instance {
_instance ??= DatabaseHelper._internal();
return _instance!;
}
Future<Database> get database async {
_database ??= await _initDatabase();
return _database!;
}
Future<Database> _initDatabase() async {
String path = join(await getDatabasesPath(), 'pos_database.db');
return await openDatabase(
path,
version: 1,
onCreate: _onCreate,
onUpgrade: _onUpgrade,
);
}
Future<void> _onCreate(Database db, int version) async {
// Products table
await db.execute('''
CREATE TABLE products (
id TEXT PRIMARY KEY,
organization_id TEXT,
category_id TEXT,
sku TEXT,
name TEXT,
description TEXT,
price INTEGER,
cost INTEGER,
business_type TEXT,
image_url TEXT,
printer_type TEXT,
metadata TEXT,
is_active INTEGER,
created_at TEXT,
updated_at TEXT
)
''');
// Product Variants table
await db.execute('''
CREATE TABLE product_variants (
id TEXT PRIMARY KEY,
product_id TEXT,
name TEXT,
price_modifier INTEGER,
cost INTEGER,
metadata TEXT,
created_at TEXT,
updated_at TEXT,
FOREIGN KEY (product_id) REFERENCES products (id) ON DELETE CASCADE
)
''');
// Create indexes for better performance
await db.execute(
'CREATE INDEX idx_products_category_id ON products(category_id)');
await db.execute('CREATE INDEX idx_products_name ON products(name)');
await db.execute('CREATE INDEX idx_products_sku ON products(sku)');
await db.execute(
'CREATE INDEX idx_products_description ON products(description)');
}
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
// Handle database upgrades here
}
Future<void> close() async {
final db = await database;
await db.close();
_database = null;
}
}
-29
View File
@@ -1,29 +0,0 @@
import 'package:sqflite/sqflite.dart';
class DatabaseMigrationHandler {
static Future<void> migrate(
Database db, int oldVersion, int newVersion) async {
if (oldVersion < 2) {
// Add indexes for better performance
await db.execute(
'CREATE INDEX IF NOT EXISTS idx_products_name_search ON products(name)');
await db.execute(
'CREATE INDEX IF NOT EXISTS idx_products_sku_search ON products(sku)');
}
if (oldVersion < 3) {
// Add full text search support
await db.execute(
'CREATE VIRTUAL TABLE products_fts USING fts5(name, sku, description, content=products, content_rowid=rowid)');
await db.execute(
'INSERT INTO products_fts SELECT name, sku, description FROM products');
}
if (oldVersion < 4) {
// Add sync tracking
await db.execute('ALTER TABLE products ADD COLUMN last_sync_at TEXT');
await db.execute(
'ALTER TABLE products ADD COLUMN sync_version INTEGER DEFAULT 1');
}
}
}
@@ -1,53 +0,0 @@
import 'dart:developer';
import 'dart:io';
import 'package:sqflite/sqflite.dart';
class DatabaseErrorHandler {
static Future<T> executeWithRetry<T>(
Future<T> Function() operation, {
int maxRetries = 3,
Duration delay = const Duration(milliseconds: 500),
}) async {
int attempts = 0;
while (attempts < maxRetries) {
try {
return await operation();
} catch (e) {
attempts++;
if (attempts >= maxRetries) {
rethrow;
}
log('Database operation failed (attempt $attempts/$maxRetries): $e');
await Future.delayed(delay * attempts);
}
}
throw Exception('Max retries exceeded');
}
static bool isDatabaseCorrupted(dynamic error) {
final errorString = error.toString().toLowerCase();
return errorString.contains('corrupt') ||
errorString.contains('malformed') ||
errorString.contains('no such table');
}
static Future<void> handleDatabaseCorruption() async {
try {
// Delete corrupted database
final dbPath = await getDatabasesPath();
final file = File('$dbPath/pos_database.db');
if (await file.exists()) {
await file.delete();
}
log('Corrupted database deleted, will be recreated');
} catch (e) {
log('Error handling database corruption: $e');
}
}
}
+8 -216
View File
@@ -12,7 +12,6 @@ import 'package:enaklo_pos/data/datasources/settings_local_datasource.dart';
import 'package:enaklo_pos/data/models/response/order_response_model.dart'; import 'package:enaklo_pos/data/models/response/order_response_model.dart';
import 'package:enaklo_pos/data/type/bussines_type.dart'; import 'package:enaklo_pos/data/type/bussines_type.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:barcode/barcode.dart'; import 'package:barcode/barcode.dart';
import 'package:image/image.dart' as img; import 'package:image/image.dart' as img;
@@ -23,9 +22,6 @@ Future<void> onPrint(
required Order order, required Order order,
}) async { }) async {
final outlet = await OutletLocalDatasource().get(); final outlet = await OutletLocalDatasource().get();
final settings = await SettingsLocalDatasource().getTax();
final authData = await AuthLocalDataSource().getAuthData();
if (outlet.businessType == BusinessType.restaurant) { if (outlet.businessType == BusinessType.restaurant) {
final checkerPrinter = final checkerPrinter =
await ProductLocalDatasource.instance.getPrinterByCode('checker'); await ProductLocalDatasource.instance.getPrinterByCode('checker');
@@ -33,53 +29,8 @@ Future<void> onPrint(
await ProductLocalDatasource.instance.getPrinterByCode('kitchen'); await ProductLocalDatasource.instance.getPrinterByCode('kitchen');
final barPrinter = final barPrinter =
await ProductLocalDatasource.instance.getPrinterByCode('bar'); await ProductLocalDatasource.instance.getPrinterByCode('bar');
final receiptPrinter =
await ProductLocalDatasource.instance.getPrinterByCode('receipt');
if (receiptPrinter != null) { final authData = await AuthLocalDataSource().getAuthData();
try {
final printValue = await PrintDataoutputs.instance.printOrderV4(
order,
authData.user?.name ?? "",
'',
0,
0,
settings.value,
receiptPrinter.paper.toIntegerFromText,
order.orderType ?? "",
outlet,
productQuantity);
await PrinterService()
.printWithPrinter(receiptPrinter, printValue, context);
} catch (e, stackTrace) {
FirebaseCrashlytics.instance.recordError(
e,
stackTrace,
reason: 'Print receipt failed',
information: [
'Order ID: ${order.id}',
'Printer: ${receiptPrinter.name}',
],
);
log("Error printing receipt order: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error printing receipt order: $e')),
);
}
} else {
FirebaseCrashlytics.instance.recordError(
'Receipt printer not found',
null,
reason:
'Receipt printer not found / Printer not setting in printer page',
information: [
'Order ID: ${order.id}',
],
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Anda belum menghubungkan printer receipt')),
);
}
// Checker printer // Checker printer
if (checkerPrinter != null) { if (checkerPrinter != null) {
@@ -89,144 +40,63 @@ Future<void> onPrint(
order.tableNumber ?? "", order.tableNumber ?? "",
order.orderNumber ?? "", order.orderNumber ?? "",
authData.user?.name ?? "", authData.user?.name ?? "",
order.metadata?['customer_name'] ?? "",
checkerPrinter.paper.toIntegerFromText, checkerPrinter.paper.toIntegerFromText,
order.orderType ?? "", order.orderType ?? "",
); );
if (productQuantity.isNotEmpty) {
await PrinterService() await PrinterService()
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
.printWithPrinter(checkerPrinter, printValue, context); .printWithPrinter(checkerPrinter, printValue, context);
} } catch (e) {
} catch (e, stackTrace) {
log("Error printing checker: $e"); log("Error printing checker: $e");
FirebaseCrashlytics.instance.recordError(
e,
stackTrace,
reason: 'Error printing checker ${checkerPrinter.name}',
information: [
'Printer: checker',
'data: ${checkerPrinter.toMap()}',
],
);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error printing checker: $e')), SnackBar(content: Text('Error printing checker: $e')),
); );
} }
} else {
FirebaseCrashlytics.instance.recordError(
'Checker printer not found',
null,
reason:
'Checker printer not found / Printer not setting in printer page',
information: [
'Order ID: ${order.id}',
],
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Anda belum menghubungkan printer checker')),
);
} }
// Kitchen printer // Kitchen printer
if (kitchenPrinter != null) { if (kitchenPrinter != null) {
try { try {
final productByPrinter = productQuantity
.where((item) => item.product.printerType == 'kitchen')
.toList();
final printValue = await PrintDataoutputs.instance.printKitchen( final printValue = await PrintDataoutputs.instance.printKitchen(
productByPrinter, productQuantity,
order.tableNumber ?? "", order.tableNumber!,
order.orderNumber ?? "", order.orderNumber ?? "",
authData.user?.name ?? "", authData.user?.name ?? "",
order.metadata?['customer_name'] ?? "",
kitchenPrinter.paper.toIntegerFromText, kitchenPrinter.paper.toIntegerFromText,
order.orderType ?? "", order.orderType ?? "",
); );
if (productByPrinter.isNotEmpty) {
await PrinterService() await PrinterService()
.printWithPrinter(kitchenPrinter, printValue, context); .printWithPrinter(kitchenPrinter, printValue, context);
} } catch (e) {
} catch (e, stackTrace) {
log("Error printing kitchen order: $e"); log("Error printing kitchen order: $e");
FirebaseCrashlytics.instance.recordError(
e,
stackTrace,
reason: 'Error printing kitchen ${kitchenPrinter.name}',
information: [
'Printer: kitchen',
'data: ${kitchenPrinter.toMap()}',
],
);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error printing kitchen order: $e')), SnackBar(content: Text('Error printing kitchen order: $e')),
); );
} }
} else {
FirebaseCrashlytics.instance.recordError(
'Kitchen printer not found',
null,
reason:
'Kitchen printer not found / Printer not setting in printer page',
information: [
'Order ID: ${order.id}',
],
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Anda belum menghubungkan printer kitchen')),
);
} }
// Bar printer // Bar printer
if (barPrinter != null) { if (barPrinter != null) {
try { try {
final productByPrinter = productQuantity
.where((item) => item.product.printerType == 'bar')
.toList();
final printValue = await PrintDataoutputs.instance.printBar( final printValue = await PrintDataoutputs.instance.printBar(
productByPrinter, productQuantity,
order.tableNumber ?? "", order.tableNumber ?? "",
order.orderNumber ?? "", order.orderNumber ?? "",
authData.user?.name ?? "", authData.user?.name ?? "",
order.metadata?['customer_name'] ?? "",
barPrinter.paper.toIntegerFromText, barPrinter.paper.toIntegerFromText,
order.orderType ?? "", order.orderType ?? "",
); );
if (productByPrinter.isNotEmpty) {
await PrinterService() await PrinterService()
.printWithPrinter(barPrinter, printValue, context); .printWithPrinter(barPrinter, printValue, context);
} } catch (e) {
} catch (e, stackTrace) {
log("Error printing bar order: $e"); log("Error printing bar order: $e");
FirebaseCrashlytics.instance.recordError(
e,
stackTrace,
reason: 'Error printing bar ${barPrinter.name}',
information: [
'Printer: bar',
'data: ${barPrinter.toMap()}',
],
);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error printing bar order: $e')), SnackBar(content: Text('Error printing bar order: $e')),
); );
} }
} else {
FirebaseCrashlytics.instance.recordError(
'Bar printer not found',
null,
reason: 'Bar printer not found / Printer not setting in printer page',
information: [
'Order ID: ${order.id}',
],
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Anda belum menghubungkan printer bar')),
);
} }
} }
@@ -247,34 +117,12 @@ Future<void> onPrint(
await PrinterService() await PrinterService()
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
.printWithPrinter(ticketPrinter, printValue, context); .printWithPrinter(ticketPrinter, printValue, context);
} catch (e, stackTrace) { } catch (e) {
FirebaseCrashlytics.instance.recordError(
e,
stackTrace,
reason: 'Error printing ticket ${ticketPrinter.name}',
information: [
'Printer: ticket',
'data: ${ticketPrinter.toMap()}',
],
);
log("Error printing ticket: $e"); log("Error printing ticket: $e");
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error printing ticket: $e')), SnackBar(content: Text('Error printing ticket: $e')),
); );
} }
} else {
FirebaseCrashlytics.instance.recordError(
'Ticket printer not found',
null,
reason:
'Ticket printer not found / Printer not setting in printer page',
information: [
'Order ID: ${order.id}',
],
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Anda belum menghubungkan printer ticket')),
);
} }
} }
} }
@@ -285,7 +133,6 @@ Future<void> onPrintRecipt(
required String paymentMethod, required String paymentMethod,
required int nominalBayar, required int nominalBayar,
required int kembalian, required int kembalian,
required List<ProductQuantity> productQuantity,
}) async { }) async {
final receiptPrinter = final receiptPrinter =
await ProductLocalDatasource.instance.getPrinterByCode('receipt'); await ProductLocalDatasource.instance.getPrinterByCode('receipt');
@@ -305,62 +152,7 @@ Future<void> onPrintRecipt(
receiptPrinter.paper.toIntegerFromText, receiptPrinter.paper.toIntegerFromText,
order.orderType ?? "", order.orderType ?? "",
outlet, outlet,
productQuantity);
await PrinterService()
.printWithPrinter(receiptPrinter, printValue, context);
} catch (e, stackTrace) {
FirebaseCrashlytics.instance.recordError(
e,
stackTrace,
reason: 'Print receipt failed',
information: [
'Order ID: ${order.id}',
'Payment Method: $paymentMethod',
'Printer: ${receiptPrinter.name}',
],
); );
log("Error printing receipt order: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error printing receipt order: $e')),
);
}
} else {
FirebaseCrashlytics.instance.recordError(
'Kitchen printer not found',
null,
reason: 'Kitchen printer not found / Printer not setting in printer page',
information: [
'Order ID: ${order.id}',
],
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Anda belum menghubungkan printer kitchen')),
);
}
}
Future<void> onPrinVoidRecipt(
context, {
required Order order,
required List<OrderItem> productItemVoid,
required int totalVoid,
}) async {
final receiptPrinter =
await ProductLocalDatasource.instance.getPrinterByCode('receipt');
final authData = await AuthLocalDataSource().getAuthData();
final settings = await SettingsLocalDatasource().getTax();
final outlet = await OutletLocalDatasource().get();
if (receiptPrinter != null) {
try {
final printValue = await PrintDataoutputs.instance.printVoidOrder(
order,
authData.user?.name ?? "",
settings.value,
receiptPrinter.paper.toIntegerFromText,
totalVoid,
outlet,
productItemVoid);
await PrinterService() await PrinterService()
.printWithPrinter(receiptPrinter, printValue, context); .printWithPrinter(receiptPrinter, printValue, context);
} catch (e) { } catch (e) {
+2 -2
View File
@@ -7,8 +7,8 @@ import 'package:shared_preferences/shared_preferences.dart';
class DioClient { class DioClient {
static final Dio _dio = Dio(BaseOptions( static final Dio _dio = Dio(BaseOptions(
connectTimeout: const Duration(minutes: 1), connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(minutes: 1), receiveTimeout: const Duration(seconds: 10),
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
}, },
@@ -1,64 +0,0 @@
import 'dart:developer';
class DatabasePerformanceMonitor {
static final Map<String, List<int>> _queryTimes = {};
static Future<T> monitorQuery<T>(
String queryName,
Future<T> Function() query,
) async {
final stopwatch = Stopwatch()..start();
try {
final result = await query();
stopwatch.stop();
_recordQueryTime(queryName, stopwatch.elapsedMilliseconds);
return result;
} catch (e) {
stopwatch.stop();
log('Query "$queryName" failed after ${stopwatch.elapsedMilliseconds}ms: $e');
rethrow;
}
}
static void _recordQueryTime(String queryName, int milliseconds) {
if (!_queryTimes.containsKey(queryName)) {
_queryTimes[queryName] = [];
}
_queryTimes[queryName]!.add(milliseconds);
// Keep only last 100 entries
if (_queryTimes[queryName]!.length > 100) {
_queryTimes[queryName]!.removeAt(0);
}
// Log slow queries
if (milliseconds > 1000) {
log('Slow query detected: "$queryName" took ${milliseconds}ms');
}
}
static Map<String, dynamic> getPerformanceStats() {
final stats = <String, dynamic>{};
_queryTimes.forEach((queryName, times) {
if (times.isNotEmpty) {
final avgTime = times.reduce((a, b) => a + b) / times.length;
final maxTime = times.reduce((a, b) => a > b ? a : b);
final minTime = times.reduce((a, b) => a < b ? a : b);
stats[queryName] = {
'average_ms': avgTime.round(),
'max_ms': maxTime,
'min_ms': minTime,
'total_queries': times.length,
};
}
});
return stats;
}
}
-568
View File
@@ -1,568 +0,0 @@
import 'dart:io';
import 'package:enaklo_pos/core/utils/helper_pdf_service.dart';
import 'package:enaklo_pos/data/datasources/outlet_local_datasource.dart';
import 'package:enaklo_pos/data/models/response/inventory_analytic_response_model.dart';
import 'package:flutter/services.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
class InventoryReport {
static final primaryColor = PdfColor.fromHex("36175e");
static Future<File> previewPdf({
required String searchDateFormatted,
required InventoryAnalyticData? inventory,
}) async {
final pdf = pw.Document();
final ByteData dataImage = await rootBundle.load('assets/logo/logo.png');
final Uint8List bytes = dataImage.buffer.asUint8List();
final outlet = await OutletLocalDatasource().get();
final image = pw.MemoryImage(bytes);
pdf.addPage(
pw.MultiPage(
pageFormat: PdfPageFormat.a4,
margin: pw.EdgeInsets.zero,
build: (pw.Context context) {
return [
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
// Bagian kiri - Logo dan Info Perusahaan
pw.Row(
crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [
// Icon/Logo placeholder (bisa diganti dengan gambar logo)
pw.Container(
width: 40,
height: 40,
child: pw.Image(image),
),
pw.SizedBox(width: 15),
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Text(
'Apskel',
style: pw.TextStyle(
fontSize: 28,
fontWeight: pw.FontWeight.bold,
color: primaryColor,
),
),
pw.SizedBox(height: 4),
pw.Text(
outlet.name ?? "",
style: pw.TextStyle(
fontSize: 16,
color: PdfColors.grey700,
),
),
pw.SizedBox(height: 2),
pw.Text(
outlet.address ?? "",
style: pw.TextStyle(
fontSize: 12,
color: PdfColors.grey600,
),
),
],
),
],
),
// Bagian kanan - Info Laporan
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.end,
children: [
pw.Text(
'Laporan Transaksi',
style: pw.TextStyle(
fontSize: 24,
fontWeight: pw.FontWeight.bold,
color: PdfColors.grey800,
),
),
pw.SizedBox(height: 8),
pw.Text(
searchDateFormatted,
style: pw.TextStyle(
fontSize: 14,
color: PdfColors.grey600,
),
),
pw.SizedBox(height: 4),
pw.Text(
'Laporan',
style: pw.TextStyle(
fontSize: 12,
color: PdfColors.grey500,
),
),
],
),
],
),
),
pw.Container(
width: double.infinity,
height: 3,
color: primaryColor,
),
// Summary
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSectionWidget('1. Ringkasan'),
pw.SizedBox(height: 30),
pw.Row(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Expanded(
flex: 1,
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSummaryItem(
'Total Item',
(inventory?.summary.totalProducts ?? 0)
.toString(),
),
_buildSummaryItem(
'Total Item Masuk',
(inventory?.products.fold<num>(
0,
(sum, item) =>
sum + (item.totalIn)) ??
0)
.toString(),
),
_buildSummaryItem(
'Total Item Keluar',
(inventory?.products.fold<num>(0,
(sum, item) => sum + (item.totalOut)))
.toString(),
),
],
),
),
pw.SizedBox(width: 20),
pw.Expanded(
flex: 1,
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSummaryItem(
'Total Ingredient',
(inventory?.summary.totalIngredients ?? 0)
.toString(),
),
_buildSummaryItem(
'Total Ingredient Masuk',
(inventory?.ingredients.fold<num>(
0,
(sum, item) =>
sum + (item.totalIn)) ??
0)
.toString(),
),
_buildSummaryItem(
'Total Ingredient Keluar',
(inventory?.ingredients.fold<num>(
0,
(sum, item) =>
sum + (item.totalOut)) ??
0)
.toString(),
),
],
),
),
],
),
],
),
),
// Summary Item
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Column(
children: [
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSectionWidget('2. Item'),
pw.SizedBox(height: 30),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
topLeft: pw.Radius.circular(8),
topRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(2), // Kategori
2: pw.FlexColumnWidth(1), // Stock
3: pw.FlexColumnWidth(2), // Masuk
4: pw.FlexColumnWidth(2), // Keluar
},
children: [
pw.TableRow(
children: [
_buildHeaderCell('Nama'),
_buildHeaderCell('Kategori'),
_buildHeaderCell('Stock'),
_buildHeaderCell('Masuk'),
_buildHeaderCell('Keluar'),
],
),
],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: PdfColors.white,
),
child: pw.Table(
columnWidths: {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(2), // Kategori
2: pw.FlexColumnWidth(1), // Stock
3: pw.FlexColumnWidth(2), // Masuk
4: pw.FlexColumnWidth(2), // Keluar
},
children: inventory?.products
.map((item) => _buildProductDataRow(
item,
inventory.products.indexOf(item) %
2 ==
0,
))
.toList() ??
[],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
bottomLeft: pw.Radius.circular(8),
bottomRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(2), // Kategori
2: pw.FlexColumnWidth(1), // Stock
3: pw.FlexColumnWidth(2), // Masuk
4: pw.FlexColumnWidth(2), // Keluar
},
children: [
pw.TableRow(
children: [
_buildTotalCell('TOTAL'),
_buildTotalCell(''),
_buildTotalCell(
(inventory?.products.fold<num>(
0,
(sum, item) =>
sum + (item.quantity)) ??
0)
.toString(),
),
_buildTotalCell(
(inventory?.products.fold<num>(
0,
(sum, item) =>
sum + (item.totalIn)) ??
0)
.toString(),
),
_buildTotalCell(
(inventory?.products.fold<num>(
0,
(sum, item) =>
sum + (item.totalOut)) ??
0)
.toString(),
),
],
),
],
),
),
],
),
],
),
),
// Summary Ingredient
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Column(
children: [
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSectionWidget('3. Ingredient'),
pw.SizedBox(height: 30),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
topLeft: pw.Radius.circular(8),
topRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Name
1: pw.FlexColumnWidth(1), // Stock
2: pw.FlexColumnWidth(2), // Masuk
3: pw.FlexColumnWidth(2), // Keluar
},
children: [
pw.TableRow(
children: [
_buildHeaderCell('Nama'),
_buildHeaderCell('Stock'),
_buildHeaderCell('Masuk'),
_buildHeaderCell('Keluar'),
],
),
],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: PdfColors.white,
),
child: pw.Table(
columnWidths: {
0: pw.FlexColumnWidth(2.5), // Name
1: pw.FlexColumnWidth(1), // Stock
2: pw.FlexColumnWidth(2), // Masuk
3: pw.FlexColumnWidth(2), // Keluar
},
children: inventory?.ingredients
.map((item) => _buildIngredientsDataRow(
item,
inventory.ingredients.indexOf(item) %
2 ==
0,
))
.toList() ??
[],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
bottomLeft: pw.Radius.circular(8),
bottomRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Name
1: pw.FlexColumnWidth(1), // Stock
2: pw.FlexColumnWidth(2), // Masuk
3: pw.FlexColumnWidth(2), // Keluar
},
children: [
pw.TableRow(
children: [
_buildTotalCell('TOTAL'),
_buildTotalCell(
(inventory?.ingredients.fold<num>(
0,
(sum, item) =>
sum + (item.quantity)) ??
0)
.toString(),
),
_buildTotalCell(
(inventory?.ingredients.fold<num>(
0,
(sum, item) =>
sum + (item.totalIn)) ??
0)
.toString(),
),
_buildTotalCell(
(inventory?.ingredients.fold<num>(
0,
(sum, item) =>
sum + (item.totalOut)) ??
0)
.toString(),
),
],
),
],
),
),
],
),
],
),
),
];
}),
);
return HelperPdfService.saveDocument(
name:
'Apskel POS | Inventory Report | ${DateTime.now().millisecondsSinceEpoch}.pdf',
pdf: pdf);
}
static pw.Widget _buildSectionWidget(String title) {
return pw.Text(
title,
style: pw.TextStyle(
fontSize: 20,
fontWeight: pw.FontWeight.bold,
color: primaryColor,
),
);
}
static pw.Widget _buildSummaryItem(
String label,
String value, {
pw.TextStyle? valueStyle,
pw.TextStyle? labelStyle,
}) {
return pw.Container(
padding: pw.EdgeInsets.only(bottom: 8),
margin: pw.EdgeInsets.only(bottom: 16),
decoration: pw.BoxDecoration(
border: pw.Border(
bottom: pw.BorderSide(
color: PdfColors.grey300,
),
),
),
child: pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Text(label, style: labelStyle),
pw.Text(
value,
style: valueStyle ??
pw.TextStyle(
fontWeight: pw.FontWeight.bold,
),
),
],
),
);
}
static pw.Widget _buildHeaderCell(String text) {
return pw.Container(
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
child: pw.Text(
text,
style: pw.TextStyle(
color: PdfColors.white,
fontWeight: pw.FontWeight.bold,
fontSize: 12,
),
textAlign: pw.TextAlign.center,
),
);
}
static pw.Widget _buildDataCell(String text,
{pw.Alignment alignment = pw.Alignment.center, PdfColor? textColor}) {
return pw.Container(
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
alignment: alignment,
child: pw.Text(
text,
style: pw.TextStyle(
fontSize: 12,
color: textColor ?? PdfColors.black,
fontWeight: pw.FontWeight.normal,
),
textAlign: alignment == pw.Alignment.centerLeft
? pw.TextAlign.left
: pw.TextAlign.center,
),
);
}
static pw.Widget _buildTotalCell(String text) {
return pw.Container(
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
child: pw.Text(
text,
style: pw.TextStyle(
color: PdfColors.white,
fontWeight: pw.FontWeight.bold,
fontSize: 12,
),
textAlign: pw.TextAlign.center,
),
);
}
static pw.TableRow _buildProductDataRow(
InventoryProductItem product, bool isEven) {
return pw.TableRow(
decoration: pw.BoxDecoration(
color: product.isZeroStock
? PdfColors.red100
: product.isLowStock
? PdfColors.yellow100
: isEven
? PdfColors.grey50
: PdfColors.white,
),
children: [
_buildDataCell(product.productName, alignment: pw.Alignment.centerLeft),
_buildDataCell(product.categoryName,
alignment: pw.Alignment.centerLeft),
_buildDataCell(product.quantity.toString()),
_buildDataCell(product.totalIn.toString()),
_buildDataCell(product.totalOut.toString()),
],
);
}
static pw.TableRow _buildIngredientsDataRow(
InventoryIngredientItem item, bool isEven) {
return pw.TableRow(
decoration: pw.BoxDecoration(
color: item.isZeroStock
? PdfColors.red100
: item.isLowStock
? PdfColors.yellow100
: isEven
? PdfColors.grey50
: PdfColors.white,
),
children: [
_buildDataCell(item.ingredientName, alignment: pw.Alignment.centerLeft),
_buildDataCell(item.quantity.toString()),
_buildDataCell(item.totalIn.toString()),
_buildDataCell(item.totalOut.toString()),
],
);
}
}
+7 -117
View File
@@ -1,5 +1,4 @@
import 'dart:developer'; import 'dart:developer';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_esc_pos_network/flutter_esc_pos_network.dart'; import 'package:flutter_esc_pos_network/flutter_esc_pos_network.dart';
import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart'; import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart';
@@ -21,36 +20,17 @@ class PrinterService {
} }
// Connect to the printer // Connect to the printer
bool connected = bool connected = await PrintBluetoothThermal.connect(
await PrintBluetoothThermal.connect(macPrinterAddress: macAddress); macPrinterAddress: macAddress);
if (connected) { if (connected) {
log("Successfully connected to Bluetooth printer: $macAddress"); log("Successfully connected to Bluetooth printer: $macAddress");
} else { } else {
FirebaseCrashlytics.instance.recordError(
'Failed to connect to Bluetooth printer',
null,
reason: 'Failed to connect to Bluetooth printe',
information: [
'function: connectBluetoothPrinter(String macAddress)',
'macAddress: $macAddress',
],
);
log("Failed to connect to Bluetooth printer: $macAddress"); log("Failed to connect to Bluetooth printer: $macAddress");
} }
return connected; return connected;
} catch (e, stackTrace) { } catch (e) {
FirebaseCrashlytics.instance.recordError(
e,
stackTrace,
reason: 'Error connecting to Bluetooth printer',
information: [
'function: connectBluetoothPrinter(String macAddress)',
'Printer: Bluetooth printer',
'macAddress: $macAddress',
],
);
log("Error connecting to Bluetooth printer: $e"); log("Error connecting to Bluetooth printer: $e");
return false; return false;
} }
@@ -69,30 +49,11 @@ class PrinterService {
if (printResult) { if (printResult) {
log("Successfully printed via Bluetooth"); log("Successfully printed via Bluetooth");
} else { } else {
FirebaseCrashlytics.instance.recordError(
'Failed to print via Bluetooth',
null,
reason: 'Failed to print via Bluetooth',
information: [
'function: printBluetooth(List<int> printData)',
'printData: $printData',
],
);
log("Failed to print via Bluetooth"); log("Failed to print via Bluetooth");
} }
return printResult; return printResult;
} catch (e, stackTrace) { } catch (e) {
FirebaseCrashlytics.instance.recordError(
e,
stackTrace,
reason: 'Error printing via Bluetooth',
information: [
'function: printBluetooth(List<int> printData)',
'Printer: Bluetooth printer',
'printData: $printData',
],
);
log("Error printing via Bluetooth: $e"); log("Error printing via Bluetooth: $e");
return false; return false;
} }
@@ -112,69 +73,25 @@ class PrinterService {
log("Successfully printed via Network printer: $ipAddress"); log("Successfully printed via Network printer: $ipAddress");
return true; return true;
} else { } else {
FirebaseCrashlytics.instance.recordError(
'Failed to print via Network printer: ${printing.msg}',
null,
reason: 'Failed to print via Network printer',
information: [
'function: printNetwork(String ipAddress, List<int> printData)',
'Printer: Network printer',
'ipAddress: $ipAddress',
'printData: $printData',
],
);
log("Failed to print via Network printer: ${printing.msg}"); log("Failed to print via Network printer: ${printing.msg}");
return false; return false;
} }
} else { } else {
FirebaseCrashlytics.instance.recordError(
'Failed to connect to Network printer: ${connect.msg}',
null,
reason: 'Failed to connectNetwork printer',
information: [
'function: printNetwork(String ipAddress, List<int> printData)',
'Printer: Network printer',
'ipAddress: $ipAddress',
'printData: $printData',
],
);
log("Failed to connect to Network printer: ${connect.msg}"); log("Failed to connect to Network printer: ${connect.msg}");
return false; return false;
} }
} catch (e, stackTrace) { } catch (e) {
FirebaseCrashlytics.instance.recordError(
e,
stackTrace,
reason: 'Error printing via Network',
information: [
'function: printNetwork(String ipAddress, List<int> printData)',
'Printer: Network printer',
'ipAddress: $ipAddress',
'printData: $printData',
],
);
log("Error printing via Network: $e"); log("Error printing via Network: $e");
return false; return false;
} }
} }
/// Print with automatic printer type detection /// Print with automatic printer type detection
Future<bool> printWithPrinter( Future<bool> printWithPrinter(PrintModel printer, List<int> printData, BuildContext context) async {
PrintModel printer, List<int> printData, BuildContext context) async {
try { try {
if (printer.type == 'Bluetooth') { if (printer.type == 'Bluetooth') {
bool connected = await connectBluetoothPrinter(printer.address); bool connected = await connectBluetoothPrinter(printer.address);
if (!connected) { if (!connected) {
FirebaseCrashlytics.instance.recordError(
'Failed to connect to Bluetooth printer',
null,
reason: 'Failed to connect to Bluetooth printe',
information: [
'function: connectBluetoothPrinter(String macAddress)',
'Printer: ${printer.name}',
'macAddress: ${printer.address}',
],
);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to connect to ${printer.name}')), SnackBar(content: Text('Failed to connect to ${printer.name}')),
); );
@@ -183,14 +100,6 @@ class PrinterService {
bool printResult = await printBluetooth(printData); bool printResult = await printBluetooth(printData);
if (!printResult) { if (!printResult) {
FirebaseCrashlytics.instance.recordError(
'Failed to print to ${printer.name}',
null,
information: [
'function: await printBluetooth(printData);',
'print: $printData',
],
);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to print to ${printer.name}')), SnackBar(content: Text('Failed to print to ${printer.name}')),
); );
@@ -199,32 +108,13 @@ class PrinterService {
} else { } else {
bool printResult = await printNetwork(printer.address, printData); bool printResult = await printNetwork(printer.address, printData);
if (!printResult) { if (!printResult) {
FirebaseCrashlytics.instance.recordError(
'Failed to connect to Network Printer',
null,
reason: 'Failed to connect to Network Printer',
information: [
'function: await printNetwork(printer.address, printData);',
'Printer: ${printer.name}',
'ipAddress: ${printer.address}',
'print: $printData',
],
);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to print to ${printer.name}')), SnackBar(content: Text('Failed to print to ${printer.name}')),
); );
} }
return printResult; return printResult;
} }
} catch (e, stackTrace) { } catch (e) {
FirebaseCrashlytics.instance.recordError(
e,
stackTrace,
reason: 'Error printing with printer ${printer.name}',
information: [
'Printer: ${printer.name}',
],
);
log("Error printing with printer ${printer.name}: $e"); log("Error printing with printer ${printer.name}: $e");
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error printing to ${printer.name}: $e')), SnackBar(content: Text('Error printing to ${printer.name}: $e')),
-912
View File
@@ -1,912 +0,0 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/core/utils/helper_pdf_service.dart';
import 'package:enaklo_pos/data/models/response/category_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/payment_method_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/product_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/profit_loss_response_model.dart';
import 'package:enaklo_pos/presentation/home/models/outlet_model.dart';
import 'package:flutter/services.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
class TransactionReport {
static final primaryColor = PdfColor.fromHex("36175e");
static Future<File> previewPdf({
required Outlet outlet,
required String searchDateFormatted,
required CategoryAnalyticData? categoryAnalyticData,
required ProfitLossData? profitLossData,
required PaymentMethodAnalyticData? paymentMethodAnalyticData,
required ProductAnalyticData? productAnalyticData,
}) async {
final pdf = pw.Document();
final ByteData dataImage = await rootBundle.load('assets/logo/logo.png');
final Uint8List bytes = dataImage.buffer.asUint8List();
final profitLossProductSummary = {
'totalRevenue': profitLossData?.productData
.fold<num>(0, (sum, item) => sum + (item.revenue)) ??
0,
'totalCost': profitLossData?.productData
.fold<num>(0, (sum, item) => sum + (item.cost)) ??
0,
'totalGrossProfit': profitLossData?.productData
.fold<num>(0, (sum, item) => sum + (item.grossProfit)) ??
0,
'totalQuantity': profitLossData?.productData
.fold<num>(0, (sum, item) => sum + (item.quantitySold)) ??
0,
};
final categorySummary = {
'totalRevenue': categoryAnalyticData?.data
.fold<num>(0, (sum, item) => sum + (item.totalRevenue)) ??
0,
'orderCount': categoryAnalyticData?.data
.fold<num>(0, (sum, item) => sum + (item.orderCount)) ??
0,
'productCount': categoryAnalyticData?.data
.fold<num>(0, (sum, item) => sum + (item.productCount)) ??
0,
'totalQuantity': categoryAnalyticData?.data
.fold<num>(0, (sum, item) => sum + (item.totalQuantity)) ??
0,
};
final productItemSummary = {
'totalRevenue': productAnalyticData?.data
.fold<num>(0, (sum, item) => sum + (item.revenue)) ??
0,
'orderCount': productAnalyticData?.data
.fold<num>(0, (sum, item) => sum + (item.orderCount)) ??
0,
'totalQuantitySold': productAnalyticData?.data
.fold<num>(0, (sum, item) => sum + (item.quantitySold)) ??
0,
};
// Membuat objek Image dari gambar
final image = pw.MemoryImage(bytes);
pdf.addPage(
pw.MultiPage(
pageFormat: PdfPageFormat.a4,
margin: pw.EdgeInsets.zero,
build: (pw.Context context) {
return [
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
// Bagian kiri - Logo dan Info Perusahaan
pw.Row(
crossAxisAlignment: pw.CrossAxisAlignment.center,
children: [
// Icon/Logo placeholder (bisa diganti dengan gambar logo)
pw.Container(
width: 40,
height: 40,
child: pw.Image(image),
),
pw.SizedBox(width: 15),
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Text(
'Apskel',
style: pw.TextStyle(
fontSize: 28,
fontWeight: pw.FontWeight.bold,
color: primaryColor,
),
),
pw.SizedBox(height: 4),
pw.Text(
outlet.name ?? "",
style: pw.TextStyle(
fontSize: 16,
color: PdfColors.grey700,
),
),
pw.SizedBox(height: 2),
pw.Text(
outlet.address ?? "",
style: pw.TextStyle(
fontSize: 12,
color: PdfColors.grey600,
),
),
],
),
],
),
// Bagian kanan - Info Laporan
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.end,
children: [
pw.Text(
'Laporan Transaksi',
style: pw.TextStyle(
fontSize: 24,
fontWeight: pw.FontWeight.bold,
color: PdfColors.grey800,
),
),
pw.SizedBox(height: 8),
pw.Text(
searchDateFormatted,
style: pw.TextStyle(
fontSize: 14,
color: PdfColors.grey600,
),
),
pw.SizedBox(height: 4),
pw.Text(
'Laporan',
style: pw.TextStyle(
fontSize: 12,
color: PdfColors.grey500,
),
),
],
),
],
),
),
pw.Container(
width: double.infinity,
height: 3,
color: primaryColor,
),
// Summary
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSectionWidget('1. Ringkasan'),
pw.SizedBox(height: 30),
pw.Row(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Expanded(
flex: 1,
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSummaryItem(
'Total Penjualan (termasuk rasik)',
(profitLossData?.summary.totalRevenue ?? 0)
.toString()
.currencyFormatRpV2,
),
_buildSummaryItem(
'Total Terjual',
(profitLossData?.summary.totalOrders ?? 0)
.toString(),
),
_buildSummaryItem(
'HPP',
'${(profitLossData?.summary.totalCost ?? 0).toString().currencyFormatRpV2} | ${(((profitLossData?.summary.totalCost ?? 0) / (profitLossData?.summary.totalRevenue ?? 1)) * 100).round()}%',
),
_buildSummaryItem(
'Laba Kotor',
'${(profitLossData?.summary.grossProfit ?? 0).toString().currencyFormatRpV2} | ${(profitLossData?.summary.grossProfitMargin ?? 0).round()}%',
valueStyle: pw.TextStyle(
color: PdfColors.green800,
fontWeight: pw.FontWeight.bold,
fontSize: 16,
),
labelStyle: pw.TextStyle(
color: PdfColors.green800,
fontWeight: pw.FontWeight.bold,
fontSize: 16,
),
),
],
),
),
pw.SizedBox(width: 20),
pw.Expanded(
flex: 1,
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSummaryItem(
'Biaya Lain lain',
'${(profitLossData?.summary.totalTax ?? 0).toString().currencyFormatRpV2} | ${(((profitLossData?.summary.totalTax ?? 0) / (profitLossData?.summary.totalRevenue ?? 1)) * 100).round()}%',
),
_buildSummaryItem(
'Laba/Rugi',
'${(profitLossData?.summary.netProfit ?? 0).toString().currencyFormatRpV2} | ${(profitLossData?.summary.netProfitMargin ?? 0).round()}%',
valueStyle: pw.TextStyle(
color: PdfColors.blue800,
fontWeight: pw.FontWeight.bold,
fontSize: 16,
),
labelStyle: pw.TextStyle(
color: PdfColors.blue800,
fontWeight: pw.FontWeight.bold,
fontSize: 16,
),
),
],
),
),
],
),
pw.SizedBox(height: 16),
pw.Text(
"Laba Rugi Perproduk",
style: pw.TextStyle(
fontSize: 16,
fontWeight: pw.FontWeight.bold,
color: primaryColor,
),
),
pw.SizedBox(height: 20),
pw.Column(
children: [
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
topLeft: pw.Radius.circular(8),
topRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(1), // Qty
2: pw.FlexColumnWidth(2.5), // Pendapatan
3: pw.FlexColumnWidth(2), // HPP
4: pw.FlexColumnWidth(2), // Laba Kotor
5: pw.FlexColumnWidth(2), // Margin (%)
},
children: [
pw.TableRow(
children: [
_buildHeaderCell('Produk'),
_buildHeaderCell('Qty'),
_buildHeaderCell('Pendapatan'),
_buildHeaderCell('HPP'),
_buildHeaderCell('Laba Kotor'),
_buildHeaderCell('Margin (%)'),
],
),
],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: PdfColors.white,
),
child: pw.Table(
columnWidths: {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(1), // Qty
2: pw.FlexColumnWidth(2.5), // Pendapatan
3: pw.FlexColumnWidth(2), // HPP
4: pw.FlexColumnWidth(2), // Laba Kotor
5: pw.FlexColumnWidth(2), // Margin (%)
},
children: profitLossData?.productData
.map(
(profitLoss) => _buildPerProductDataRow(
product: profitLoss.productName,
qty: profitLoss.quantitySold.toString(),
pendapatan: profitLoss.revenue
.toString()
.currencyFormatRpV2,
hpp: profitLoss.cost
.toString()
.currencyFormatRpV2,
labaKotor: profitLoss.grossProfit
.toString()
.currencyFormatRpV2,
margin:
'${profitLoss.grossProfitMargin.round()}%',
isEven: profitLossData.productData
.indexOf(profitLoss) %
2 ==
0,
),
)
.toList() ??
[],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
bottomLeft: pw.Radius.circular(8),
bottomRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(1), // Qty
2: pw.FlexColumnWidth(2.5), // Pendapatan
3: pw.FlexColumnWidth(2), // HPP
4: pw.FlexColumnWidth(2), // Laba Kotor
5: pw.FlexColumnWidth(2), // Margin (%)
},
children: [
pw.TableRow(
children: [
_buildTotalCell('TOTAL'),
_buildTotalCell(
profitLossProductSummary['totalQuantity']
.toString()),
_buildTotalCell(
profitLossProductSummary['totalRevenue']
.toString()
.currencyFormatRpV2),
_buildTotalCell(
profitLossProductSummary['totalCost']
.toString()
.currencyFormatRpV2),
_buildTotalCell(
profitLossProductSummary['totalGrossProfit']
.toString()
.currencyFormatRpV2),
_buildTotalCell(''),
],
),
],
),
),
],
),
],
),
),
// Summary Payment Method
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Column(
children: [
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSectionWidget('2. Ringkasan Metode Pembayaran'),
pw.SizedBox(height: 30),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
topLeft: pw.Radius.circular(8),
topRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Nama
1: pw.FlexColumnWidth(1), // Tipe
2: pw.FlexColumnWidth(2.5), // Jumlah Order
3: pw.FlexColumnWidth(2), // Total Amount
4: pw.FlexColumnWidth(2), // Presentase
},
children: [
pw.TableRow(
children: [
_buildHeaderCell('Nama'),
_buildHeaderCell('Tipe'),
_buildHeaderCell('Jumlah Order'),
_buildHeaderCell('Total Amount'),
_buildHeaderCell('Presentase'),
],
),
],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: PdfColors.white,
),
child: pw.Table(
columnWidths: {
0: pw.FlexColumnWidth(2.5), // Nama
1: pw.FlexColumnWidth(1), // Tipe
2: pw.FlexColumnWidth(2.5), // Jumlah Order
3: pw.FlexColumnWidth(2), // Total Amount
4: pw.FlexColumnWidth(2), // Presentase
},
children: paymentMethodAnalyticData?.data
.map(
(payment) => _buildPaymentMethodDataRow(
name: payment.paymentMethodName,
tipe: payment.paymentMethodType
.toTitleCase(),
jumlahOrder:
payment.orderCount.toString(),
totalAmount: payment.totalAmount
.toString()
.currencyFormatRpV2,
presentase:
'${payment.percentage.round()}%',
isEven: paymentMethodAnalyticData.data
.indexOf(payment) %
2 ==
0,
),
)
.toList() ??
[],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
bottomLeft: pw.Radius.circular(8),
bottomRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(1), // Qty
2: pw.FlexColumnWidth(2.5), // Pendapatan
3: pw.FlexColumnWidth(2), // HPP
4: pw.FlexColumnWidth(2), // Laba Kotor
5: pw.FlexColumnWidth(2), // Margin (%)
},
children: [
pw.TableRow(
children: [
_buildTotalCell('TOTAL'),
_buildTotalCell(''),
_buildTotalCell((paymentMethodAnalyticData
?.summary.totalOrders ??
0)
.toString()),
_buildTotalCell((paymentMethodAnalyticData
?.summary.totalAmount ??
0)
.toString()
.currencyFormatRpV2),
_buildTotalCell(''),
],
),
],
),
),
],
),
],
),
),
// Summary Category
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSectionWidget('3. Ringkasan Kategori'),
pw.SizedBox(height: 30),
pw.Column(
children: [
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
topLeft: pw.Radius.circular(8),
topRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Nama
1: pw.FlexColumnWidth(2), // Total Product
2: pw.FlexColumnWidth(1), // qty
3: pw.FlexColumnWidth(2), // Jumlah Order
4: pw.FlexColumnWidth(2.5), // Presentase
},
children: [
pw.TableRow(
children: [
_buildHeaderCell('Nama'),
_buildHeaderCell('Total Produk'),
_buildHeaderCell('Qty'),
_buildHeaderCell('Jumlah Order'),
_buildHeaderCell('Pendapatan'),
],
),
],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: PdfColors.white,
),
child: pw.Table(
columnWidths: {
0: pw.FlexColumnWidth(2.5), // Nama
1: pw.FlexColumnWidth(2), // Total Product
2: pw.FlexColumnWidth(1), // qty
3: pw.FlexColumnWidth(2), // Jumlah Order
4: pw.FlexColumnWidth(2.5), // Presentase
},
children: categoryAnalyticData?.data
.map((category) => _buildCategoryDataRow(
name: category.categoryName,
totalProduct:
category.productCount.toString(),
qty: category.totalQuantity.toString(),
jumlahOrder:
category.orderCount.toString(),
pendapatan: category.totalRevenue
.toString()
.currencyFormatRpV2,
isEven: categoryAnalyticData.data
.indexOf(category) %
2 ==
0,
))
.toList() ??
[],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
bottomLeft: pw.Radius.circular(8),
bottomRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Nama
1: pw.FlexColumnWidth(2), // Total Product
2: pw.FlexColumnWidth(1), // qty
3: pw.FlexColumnWidth(2), // Jumlah Order
4: pw.FlexColumnWidth(2.5), // Presentase
},
children: [
pw.TableRow(
children: [
_buildTotalCell('TOTAL'),
_buildTotalCell(
categorySummary['productCount'].toString()),
_buildTotalCell(categorySummary['totalQuantity']
.toString()),
_buildTotalCell(
categorySummary['orderCount'].toString()),
_buildTotalCell(categorySummary['totalRevenue']
.toString()
.currencyFormatRpV2),
],
),
],
),
),
],
),
],
),
),
// Summary Item
pw.Container(
padding: pw.EdgeInsets.all(20),
child: pw.Column(
children: [
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_buildSectionWidget('4. Ringkasan Item'),
pw.SizedBox(height: 30),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
topLeft: pw.Radius.circular(8),
topRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(2), // Kategori
2: pw.FlexColumnWidth(1), // qty
3: pw.FlexColumnWidth(2), // Order
4: pw.FlexColumnWidth(2), // Pendapatan
5: pw.FlexColumnWidth(2), // Average
},
children: [
pw.TableRow(
children: [
_buildHeaderCell('Produk'),
_buildHeaderCell('Kategori'),
_buildHeaderCell('Qty'),
_buildHeaderCell('Order'),
_buildHeaderCell('Pendapatan'),
_buildHeaderCell('Rata Rata'),
],
),
],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: PdfColors.white,
),
child: pw.Table(
columnWidths: {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(2), // Kategori
2: pw.FlexColumnWidth(1), // qty
3: pw.FlexColumnWidth(2), // Order
4: pw.FlexColumnWidth(2), // Pendapatan
5: pw.FlexColumnWidth(2), // Average
},
children: productAnalyticData?.data
.map((item) => _buildItemDataRow(
product: item.productName,
category: item.categoryName,
qty: item.quantitySold.toString(),
order: item.orderCount.toString(),
pendapatan: item.revenue
.toString()
.currencyFormatRpV2,
average: item.averagePrice
.round()
.toString()
.currencyFormatRpV2,
isEven: productAnalyticData.data
.indexOf(item) %
2 ==
0,
))
.toList() ??
[],
),
),
pw.Container(
decoration: pw.BoxDecoration(
color: primaryColor, // Purple color
borderRadius: pw.BorderRadius.only(
bottomLeft: pw.Radius.circular(8),
bottomRight: pw.Radius.circular(8),
),
),
child: pw.Table(
columnWidths: const {
0: pw.FlexColumnWidth(2.5), // Produk
1: pw.FlexColumnWidth(2), // Kategori
2: pw.FlexColumnWidth(1), // qty
3: pw.FlexColumnWidth(2), // Order
4: pw.FlexColumnWidth(2), // Pendapatan
5: pw.FlexColumnWidth(2), // Average
},
children: [
pw.TableRow(
children: [
_buildTotalCell('TOTAL'),
_buildTotalCell(''),
_buildTotalCell(
productItemSummary['totalQuantitySold']
.toString()),
_buildTotalCell(productItemSummary['orderCount']
.toString()),
_buildTotalCell(
productItemSummary['totalRevenue']
.toString()
.currencyFormatRpV2),
_buildTotalCell(''),
],
),
],
),
),
],
),
],
),
),
];
},
),
);
return HelperPdfService.saveDocument(
name: 'Laporan Transaksi | $searchDateFormatted.pdf', pdf: pdf);
}
static pw.Widget _buildSectionWidget(String title) {
return pw.Text(
title,
style: pw.TextStyle(
fontSize: 20,
fontWeight: pw.FontWeight.bold,
color: primaryColor,
),
);
}
static pw.Widget _buildSummaryItem(
String label,
String value, {
pw.TextStyle? valueStyle,
pw.TextStyle? labelStyle,
}) {
return pw.Container(
padding: pw.EdgeInsets.only(bottom: 8),
margin: pw.EdgeInsets.only(bottom: 16),
decoration: pw.BoxDecoration(
border: pw.Border(
bottom: pw.BorderSide(
color: PdfColors.grey300,
),
),
),
child: pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Text(label, style: labelStyle),
pw.Text(
value,
style: valueStyle ??
pw.TextStyle(
fontWeight: pw.FontWeight.bold,
),
),
],
),
);
}
static pw.Widget _buildHeaderCell(String text) {
return pw.Container(
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
child: pw.Text(
text,
style: pw.TextStyle(
color: PdfColors.white,
fontWeight: pw.FontWeight.bold,
fontSize: 12,
),
textAlign: pw.TextAlign.center,
),
);
}
static pw.Widget _buildDataCell(String text,
{pw.Alignment alignment = pw.Alignment.center, PdfColor? textColor}) {
return pw.Container(
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
alignment: alignment,
child: pw.Text(
text,
style: pw.TextStyle(
fontSize: 12,
color: textColor ?? PdfColors.black,
fontWeight: pw.FontWeight.normal,
),
textAlign: alignment == pw.Alignment.centerLeft
? pw.TextAlign.left
: pw.TextAlign.center,
),
);
}
static pw.Widget _buildTotalCell(String text) {
return pw.Container(
padding: pw.EdgeInsets.symmetric(horizontal: 12, vertical: 16),
child: pw.Text(
text,
style: pw.TextStyle(
color: PdfColors.white,
fontWeight: pw.FontWeight.bold,
fontSize: 12,
),
textAlign: pw.TextAlign.center,
),
);
}
static pw.TableRow _buildPerProductDataRow({
required String product,
required String qty,
required String pendapatan,
required String hpp,
required String labaKotor,
required String margin,
required bool isEven,
}) {
return pw.TableRow(
decoration: pw.BoxDecoration(
color: isEven ? PdfColors.grey50 : PdfColors.white,
),
children: [
_buildDataCell(product, alignment: pw.Alignment.centerLeft),
_buildDataCell(qty),
_buildDataCell(pendapatan),
_buildDataCell(hpp, textColor: PdfColors.red600),
_buildDataCell(labaKotor, textColor: PdfColors.green600),
_buildDataCell(margin),
],
);
}
static pw.TableRow _buildPaymentMethodDataRow({
required String name,
required String tipe,
required String jumlahOrder,
required String totalAmount,
required String presentase,
required bool isEven,
}) {
return pw.TableRow(
decoration: pw.BoxDecoration(
color: isEven ? PdfColors.grey50 : PdfColors.white,
),
children: [
_buildDataCell(name, alignment: pw.Alignment.centerLeft),
_buildDataCell(tipe),
_buildDataCell(jumlahOrder),
_buildDataCell(totalAmount),
_buildDataCell(presentase),
],
);
}
static pw.TableRow _buildCategoryDataRow({
required String name,
required String totalProduct,
required String qty,
required String jumlahOrder,
required String pendapatan,
required bool isEven,
}) {
return pw.TableRow(
decoration: pw.BoxDecoration(
color: isEven ? PdfColors.grey50 : PdfColors.white,
),
children: [
_buildDataCell(name, alignment: pw.Alignment.centerLeft),
_buildDataCell(totalProduct),
_buildDataCell(qty),
_buildDataCell(jumlahOrder),
_buildDataCell(pendapatan),
],
);
}
static pw.TableRow _buildItemDataRow({
required String product,
required String category,
required String qty,
required String order,
required String pendapatan,
required String average,
required bool isEven,
}) {
return pw.TableRow(
decoration: pw.BoxDecoration(
color: isEven ? PdfColors.grey50 : PdfColors.white,
),
children: [
_buildDataCell(product, alignment: pw.Alignment.centerLeft),
_buildDataCell(category, alignment: pw.Alignment.centerLeft),
_buildDataCell(qty),
_buildDataCell(order),
_buildDataCell(pendapatan),
_buildDataCell(average),
],
);
}
}
+20 -285
View File
@@ -534,7 +534,7 @@ class PrintDataoutputs {
styles: const PosStyles(align: PosAlign.left), styles: const PosStyles(align: PosAlign.left),
), ),
PosColumn( PosColumn(
text: '${DateFormat('yyyyMMddhhmm').format(DateTime.now())}', text: 'JF-${DateFormat('yyyyMMddhhmm').format(DateTime.now())}',
width: 6, width: 6,
styles: const PosStyles(align: PosAlign.right), styles: const PosStyles(align: PosAlign.right),
), ),
@@ -751,7 +751,6 @@ class PrintDataoutputs {
int paper, int paper,
String orderType, String orderType,
Outlet outlet, Outlet outlet,
List<ProductQuantity> products,
) async { ) async {
List<int> bytes = []; List<int> bytes = [];
@@ -799,7 +798,7 @@ class PrintDataoutputs {
styles: const PosStyles(align: PosAlign.left), styles: const PosStyles(align: PosAlign.left),
), ),
PosColumn( PosColumn(
text: DateFormat('yyyyMMddhhmm').format(DateTime.now()), text: 'JF-${DateFormat('yyyyMMddhhmm').format(DateTime.now())}',
width: 6, width: 6,
styles: const PosStyles(align: PosAlign.right), styles: const PosStyles(align: PosAlign.right),
), ),
@@ -841,10 +840,9 @@ class PrintDataoutputs {
styles: const PosStyles(align: PosAlign.right), styles: const PosStyles(align: PosAlign.right),
), ),
]); ]);
if (paymentMethod != '') {
bytes += generator.row([ bytes += generator.row([
PosColumn( PosColumn(
text: 'Payment', text: 'Pembayaran',
width: 8, width: 8,
styles: const PosStyles(align: PosAlign.left), styles: const PosStyles(align: PosAlign.left),
), ),
@@ -854,7 +852,6 @@ class PrintDataoutputs {
styles: const PosStyles(align: PosAlign.right), styles: const PosStyles(align: PosAlign.right),
), ),
]); ]);
}
bytes += generator.text( bytes += generator.text(
paper == 80 paper == 80
@@ -868,16 +865,17 @@ class PrintDataoutputs {
? '------------------------------------------------' ? '------------------------------------------------'
: '--------------------------------', : '--------------------------------',
styles: const PosStyles(bold: false, align: PosAlign.center)); styles: const PosStyles(bold: false, align: PosAlign.center));
for (final product in (products)) { for (final product
in (order.orderItems?.where((item) => item.status != 'cancelled') ??
<OrderItem>[])) {
bytes += generator.row([ bytes += generator.row([
PosColumn( PosColumn(
text: '${product.quantity} x ${product.product.name}', text: '${product.quantity} x ${product.productName}',
width: 8, width: 8,
styles: const PosStyles(bold: true, align: PosAlign.left), styles: const PosStyles(bold: true, align: PosAlign.left),
), ),
PosColumn( PosColumn(
text: (((product.product.price ?? 0) * product.quantity)) text: (product.totalPrice ?? 0).currencyFormatRpV2,
.currencyFormatRpV2,
width: 4, width: 4,
styles: const PosStyles(bold: true, align: PosAlign.right), styles: const PosStyles(bold: true, align: PosAlign.right),
), ),
@@ -891,7 +889,7 @@ class PrintDataoutputs {
bytes += generator.row([ bytes += generator.row([
PosColumn( PosColumn(
text: 'Subtotal ${products.length} Product', text: 'Subtotal ${order.orderItems?.length ?? "0"} Product',
width: 6, width: 6,
styles: const PosStyles(align: PosAlign.left), styles: const PosStyles(align: PosAlign.left),
), ),
@@ -1004,239 +1002,6 @@ class PrintDataoutputs {
return bytes; return bytes;
} }
Future<List<int>> printVoidOrder(
Order order,
String chashierName,
int taxPercentage,
int paper,
int nominalDikembalikan,
Outlet outlet,
List<OrderItem> productItemVoid,
) async {
List<int> bytes = [];
final profile = await CapabilityProfile.load();
final generator =
Generator(paper == 58 ? PaperSize.mm58 : PaperSize.mm80, profile);
bytes += generator.reset();
bytes += generator.text(outlet.name ?? "",
styles: const PosStyles(
bold: true,
align: PosAlign.center,
height: PosTextSize.size1,
width: PosTextSize.size1,
));
bytes += generator.text(outlet.address ?? "",
styles: const PosStyles(bold: false, align: PosAlign.center));
bytes += generator.text(outlet.phoneNumber ?? "",
styles: const PosStyles(bold: false, align: PosAlign.center));
bytes += generator.text(
paper == 80
? '------------------------------------------------'
: '--------------------------------',
styles: const PosStyles(bold: false, align: PosAlign.center));
bytes += generator.row([
PosColumn(
text: DateFormat('dd MMM yyyy').format(DateTime.now()),
width: 6,
styles: const PosStyles(align: PosAlign.left),
),
PosColumn(
text: DateFormat('HH:mm').format(DateTime.now()),
width: 6,
styles: const PosStyles(align: PosAlign.right),
),
]);
bytes += generator.row([
PosColumn(
text: 'Receipt Number',
width: 6,
styles: const PosStyles(align: PosAlign.left),
),
PosColumn(
text: '${DateFormat('yyyyMMddhhmm').format(DateTime.now())}',
width: 6,
styles: const PosStyles(align: PosAlign.right),
),
]);
bytes += generator.row([
PosColumn(
text: 'Order ID',
width: 6,
styles: const PosStyles(align: PosAlign.left),
),
PosColumn(
text: Random().nextInt(100000).toString(),
width: 6,
styles: const PosStyles(align: PosAlign.right),
),
]);
bytes += generator.row([
PosColumn(
text: 'Bill Name',
width: 6,
styles: const PosStyles(align: PosAlign.left),
),
PosColumn(
text: order.metadata?['customer_name'] ?? '',
width: 6,
styles: const PosStyles(align: PosAlign.right),
),
]);
bytes += generator.row([
PosColumn(
text: 'Collected By',
width: 6,
styles: const PosStyles(align: PosAlign.left),
),
PosColumn(
text: chashierName,
width: 6,
styles: const PosStyles(align: PosAlign.right),
),
]);
bytes += generator.row([
PosColumn(
text: 'Status',
width: 8,
styles: const PosStyles(align: PosAlign.left),
),
PosColumn(
text: 'Dibatalkan',
width: 4,
styles: const PosStyles(align: PosAlign.right),
),
]);
for (final product in (productItemVoid)) {
bytes += generator.row([
PosColumn(
text: '${product.quantity} x ${product.productName}',
width: 8,
styles: const PosStyles(bold: true, align: PosAlign.left),
),
PosColumn(
text: ((product.totalPrice ?? 0)).currencyFormatRpV2,
width: 4,
styles: const PosStyles(bold: true, align: PosAlign.right),
),
]);
}
bytes += generator.text(
paper == 80
? '------------------------------------------------'
: '--------------------------------',
styles: const PosStyles(bold: false, align: PosAlign.center));
bytes += generator.row([
PosColumn(
text: 'Subtotal ${productItemVoid.length} Product',
width: 6,
styles: const PosStyles(align: PosAlign.left),
),
PosColumn(
text: (order.subtotal ?? 0).currencyFormatRpV2,
width: 6,
styles: const PosStyles(align: PosAlign.right),
),
]);
bytes += generator.row([
PosColumn(
text: 'Discount',
width: 6,
styles: const PosStyles(align: PosAlign.left),
),
PosColumn(
text: (order.discountAmount ?? 0).currencyFormatRpV2,
width: 6,
styles: const PosStyles(align: PosAlign.right),
),
]);
// Only show tax if it's greater than 0
if ((order.taxAmount ?? 0) > 0) {
bytes += generator.row([
PosColumn(
text: 'Tax PB1 ($taxPercentage%)',
width: 6,
styles: const PosStyles(align: PosAlign.left),
),
PosColumn(
text: (order.taxAmount ?? 0).currencyFormatRpV2,
width: 6,
styles: const PosStyles(align: PosAlign.right),
),
]);
}
// Only show service charge if it's greater than 0
// if (serviceCharge > 0) {
// bytes += generator.row([
// PosColumn(
// text: 'Service Charge($serviceChargePercentage%)',
// width: 6,
// styles: const PosStyles(align: PosAlign.left),
// ),
// PosColumn(
// text: serviceCharge.currencyFormatRpV2,
// width: 6,
// styles: const PosStyles(align: PosAlign.right),
// ),
// ]);
// }
bytes += generator.text(
paper == 80
? '------------------------------------------------'
: '--------------------------------',
styles: const PosStyles(bold: false, align: PosAlign.center));
bytes += generator.row([
PosColumn(
text: 'Total',
width: 6,
styles: const PosStyles(bold: true, align: PosAlign.left),
),
PosColumn(
text: '${order.totalAmount ?? ""}'.currencyFormatRpV2,
width: 6,
styles: const PosStyles(bold: true, align: PosAlign.right),
),
]);
bytes += generator.row([
PosColumn(
text: 'Dikembalikan',
width: 6,
styles: const PosStyles(align: PosAlign.left),
),
PosColumn(
text: nominalDikembalikan.currencyFormatRpV2,
width: 6,
styles: const PosStyles(align: PosAlign.right),
),
]);
bytes += generator.text(
paper == 80
? '------------------------------------------------'
: '--------------------------------',
styles: const PosStyles(bold: false, align: PosAlign.center));
// bytes += generator.text('Notes',
// styles: const PosStyles(bold: false, align: PosAlign.center));
// bytes += generator.text('Pass Wifi: fic14jilid2',
// styles: const PosStyles(bold: false, align: PosAlign.center));
// //terima kasih
// bytes += generator.text('Terima Kasih',
// styles: const PosStyles(bold: true, align: PosAlign.center));
paper == 80 ? bytes += generator.feed(3) : bytes += generator.feed(1);
bytes += generator.cut();
return bytes;
}
Future<List<int>> printQRIS( Future<List<int>> printQRIS(
int totalPrice, Uint8List imageQris, int paper) async { int totalPrice, Uint8List imageQris, int paper) async {
List<int> bytes = []; List<int> bytes = [];
@@ -1277,7 +1042,6 @@ class PrintDataoutputs {
String tableName, String tableName,
String draftName, String draftName,
String cashierName, String cashierName,
String customerName,
int paper, int paper,
String orderType) async { String orderType) async {
List<int> bytes = []; List<int> bytes = [];
@@ -1328,11 +1092,14 @@ class PrintDataoutputs {
styles: const PosStyles(align: PosAlign.left), styles: const PosStyles(align: PosAlign.left),
), ),
PosColumn( PosColumn(
text: '${DateFormat('yyyyMMddhhmm').format(DateTime.now())}', text: 'JF-${DateFormat('yyyyMMddhhmm').format(DateTime.now())}',
width: 6, width: 6,
styles: const PosStyles(align: PosAlign.right), styles: const PosStyles(align: PosAlign.right),
), ),
]); ]);
// bytes += generator.text(
// 'Receipt: JF-${DateFormat('yyyyMMddhhmm').format(DateTime.now())}',
// styles: const PosStyles(bold: false, align: PosAlign.left));
//cashier name //cashier name
bytes += generator.row([ bytes += generator.row([
PosColumn( PosColumn(
@@ -1352,7 +1119,7 @@ class PrintDataoutputs {
//column 2 //column 2
bytes += generator.row([ bytes += generator.row([
PosColumn( PosColumn(
text: '$customerName - $draftName', text: 'Customer - $draftName',
width: 6, width: 6,
styles: const PosStyles(align: PosAlign.left), styles: const PosStyles(align: PosAlign.left),
), ),
@@ -1410,7 +1177,6 @@ class PrintDataoutputs {
String tableNumber, String tableNumber,
String draftName, String draftName,
String cashierName, String cashierName,
String customerName,
int paper, int paper,
String orderType) async { String orderType) async {
List<int> bytes = []; List<int> bytes = [];
@@ -1463,26 +1229,14 @@ class PrintDataoutputs {
styles: const PosStyles(align: PosAlign.left), styles: const PosStyles(align: PosAlign.left),
), ),
PosColumn( PosColumn(
text: DateFormat('yyyyMMddhhmm').format(DateTime.now()), text: 'JF-${DateFormat('yyyyMMddhhmm').format(DateTime.now())}',
width: 6, width: 6,
styles: const PosStyles(align: PosAlign.right), styles: const PosStyles(align: PosAlign.right),
), ),
]); ]);
bytes += generator.row([ bytes += generator.row([
PosColumn( PosColumn(
text: 'Cashier', text: 'Customer - $draftName',
width: 6,
styles: const PosStyles(align: PosAlign.left),
),
PosColumn(
text: cashierName,
width: 6,
styles: const PosStyles(align: PosAlign.right),
),
]);
bytes += generator.row([
PosColumn(
text: '$customerName - $draftName',
width: 6, width: 6,
styles: const PosStyles(align: PosAlign.left), styles: const PosStyles(align: PosAlign.left),
), ),
@@ -1537,15 +1291,8 @@ class PrintDataoutputs {
return bytes; return bytes;
} }
Future<List<int>> printBar( Future<List<int>> printBar(List<ProductQuantity> products, String tableNumber,
List<ProductQuantity> products, String draftName, String cashierName, int paper, String orderType) async {
String tableNumber,
String draftName,
String cashierName,
String customerName,
int paper,
String orderType,
) async {
List<int> bytes = []; List<int> bytes = [];
final profile = await CapabilityProfile.load(); final profile = await CapabilityProfile.load();
@@ -1596,26 +1343,14 @@ class PrintDataoutputs {
styles: const PosStyles(align: PosAlign.left), styles: const PosStyles(align: PosAlign.left),
), ),
PosColumn( PosColumn(
text: DateFormat('yyyyMMddhhmm').format(DateTime.now()), text: 'JF-${DateFormat('yyyyMMddhhmm').format(DateTime.now())}',
width: 6, width: 6,
styles: const PosStyles(align: PosAlign.right), styles: const PosStyles(align: PosAlign.right),
), ),
]); ]);
bytes += generator.row([ bytes += generator.row([
PosColumn( PosColumn(
text: 'Cashier', text: 'Customer - $draftName',
width: 6,
styles: const PosStyles(align: PosAlign.left),
),
PosColumn(
text: cashierName,
width: 6,
styles: const PosStyles(align: PosAlign.right),
),
]);
bytes += generator.row([
PosColumn(
text: '$customerName - $draftName',
width: 6, width: 6,
styles: const PosStyles(align: PosAlign.left), styles: const PosStyles(align: PosAlign.left),
), ),
@@ -5,9 +5,7 @@ import 'package:dio/dio.dart';
import 'package:enaklo_pos/core/constants/variables.dart'; import 'package:enaklo_pos/core/constants/variables.dart';
import 'package:enaklo_pos/core/network/dio_client.dart'; import 'package:enaklo_pos/core/network/dio_client.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/data/models/response/category_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/dashboard_analytic_response_model.dart'; import 'package:enaklo_pos/data/models/response/dashboard_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/inventory_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/payment_method_analytic_response_model.dart'; import 'package:enaklo_pos/data/models/response/payment_method_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/product_analytic_response_model.dart'; import 'package:enaklo_pos/data/models/response/product_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/profit_loss_response_model.dart'; import 'package:enaklo_pos/data/models/response/profit_loss_response_model.dart';
@@ -186,72 +184,4 @@ class AnalyticRemoteDatasource {
return left('Unexpected error occurred'); return left('Unexpected error occurred');
} }
} }
Future<Either<String, CategoryAnalyticResponseModel>> getCategory({
required DateTime dateFrom,
required DateTime dateTo,
}) async {
final authData = await AuthLocalDataSource().getAuthData();
final headers = {
'Authorization': 'Bearer ${authData.token}',
'Accept': 'application/json',
};
try {
final response = await dio.get(
'${Variables.baseUrl}/api/v1/analytics/categories',
queryParameters: {
'date_from': DateFormat('dd-MM-yyyy').format(dateFrom),
'date_to': DateFormat('dd-MM-yyyy').format(dateTo),
},
options: Options(headers: headers),
);
if (response.statusCode == 200) {
return right(CategoryAnalyticResponseModel.fromMap(response.data));
} else {
return left('Terjadi Kesalahan, Coba lagi nanti.');
}
} on DioException catch (e) {
log('Dio error: ${e.message}');
return left(e.response?.data.toString() ?? e.message ?? 'Unknown error');
} catch (e) {
log('Unexpected error: $e');
return left('Unexpected error occurred');
}
}
Future<Either<String, InventoryAnalyticResponseModel>> getInventory({
required DateTime dateFrom,
required DateTime dateTo,
}) async {
final authData = await AuthLocalDataSource().getAuthData();
final headers = {
'Authorization': 'Bearer ${authData.token}',
'Accept': 'application/json',
};
try {
final response = await dio.get(
'${Variables.baseUrl}/api/v1/inventory/report/details/${authData.user?.outletId}',
queryParameters: {
'date_from': DateFormat('dd-MM-yyyy').format(dateFrom),
'date_to': DateFormat('dd-MM-yyyy').format(dateTo),
},
options: Options(headers: headers),
);
if (response.statusCode == 200) {
return right(InventoryAnalyticResponseModel.fromMap(response.data));
} else {
return left('Terjadi Kesalahan, Coba lagi nanti.');
}
} on DioException catch (e) {
log('Dio error: ${e.message}');
return left(e.response?.data.toString() ?? e.message ?? 'Unknown error');
} catch (e) {
log('Unexpected error: $e');
return left('Unexpected error occurred');
}
}
} }
@@ -1,496 +0,0 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:enaklo_pos/core/database/database_handler.dart';
import 'package:enaklo_pos/data/models/response/product_response_model.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart' as p;
class ProductLocalDatasource {
static ProductLocalDatasource? _instance;
ProductLocalDatasource._internal();
static ProductLocalDatasource get instance {
_instance ??= ProductLocalDatasource._internal();
return _instance!;
}
Future<Database> get _db async => await DatabaseHelper.instance.database;
// ========================================
// CACHING SYSTEM
// ========================================
final Map<String, List<Product>> _queryCache = {};
final Duration _cacheExpiry = Duration(minutes: 5);
final Map<String, DateTime> _cacheTimestamps = {};
// ========================================
// ENHANCED BATCH SAVE
// ========================================
Future<void> saveProductsBatch(List<Product> products,
{bool clearFirst = false}) async {
final db = await _db;
try {
await db.transaction((txn) async {
if (clearFirst) {
log('🗑️ Clearing existing products...');
await txn.delete('product_variants');
await txn.delete('products');
}
log('đź’ľ Batch saving ${products.length} products...');
// âś… BATCH INSERT PRODUCTS - Much faster than individual inserts
final batch = txn.batch();
for (final product in products) {
batch.insert(
'products',
_productToMap(product),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
await batch.commit(noResult: true);
// âś… BATCH INSERT VARIANTS
final variantBatch = txn.batch();
for (final product in products) {
if (product.variants?.isNotEmpty == true) {
// Delete existing variants in batch
variantBatch.delete(
'product_variants',
where: 'product_id = ?',
whereArgs: [product.id],
);
// Insert new variants
for (final variant in product.variants!) {
variantBatch.insert(
'product_variants',
_variantToMap(variant),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
}
}
await variantBatch.commit(noResult: true);
});
// Clear cache after update
clearCache();
log('âś… Successfully batch saved ${products.length} products');
} catch (e) {
log('❌ Error batch saving products: $e');
rethrow;
}
}
// ========================================
// CACHED QUERY - HIGH PERFORMANCE
// ========================================
Future<List<Product>> getCachedProducts({
int page = 1,
int limit = 10,
String? categoryId,
String? search,
}) async {
final cacheKey = _generateCacheKey(page, limit, categoryId, search);
final now = DateTime.now();
// âś… CHECK CACHE FIRST
if (_queryCache.containsKey(cacheKey) &&
_cacheTimestamps.containsKey(cacheKey)) {
final cacheTime = _cacheTimestamps[cacheKey]!;
if (now.difference(cacheTime) < _cacheExpiry) {
log('🚀 Cache HIT: $cacheKey (${_queryCache[cacheKey]!.length} products)');
return _queryCache[cacheKey]!; // Return from cache - SUPER FAST
}
}
log('đź“€ Cache MISS: $cacheKey, querying database...');
// Cache miss, query database
final products = await getProducts(
page: page,
limit: limit,
categoryId: categoryId,
search: search,
);
// âś… STORE IN CACHE for next time
_queryCache[cacheKey] = products;
_cacheTimestamps[cacheKey] = now;
log('đź’ľ Cached ${products.length} products for key: $cacheKey');
return products;
}
// ========================================
// REGULAR GET PRODUCTS (No Cache)
// ========================================
Future<List<Product>> getProducts({
int page = 1,
int limit = 10,
String? categoryId,
String? search,
}) async {
final db = await _db;
try {
String query = 'SELECT * FROM products WHERE 1=1';
List<dynamic> whereArgs = [];
if (categoryId != null && categoryId.isNotEmpty) {
query += ' AND category_id = ?';
whereArgs.add(categoryId);
}
if (search != null && search.isNotEmpty) {
query += ' AND (name LIKE ? OR sku LIKE ? OR description LIKE ?)';
whereArgs.add('%$search%');
whereArgs.add('%$search%');
whereArgs.add('%$search%');
}
query += ' ORDER BY created_at DESC';
if (limit > 0) {
query += ' LIMIT ?';
whereArgs.add(limit);
if (page > 1) {
query += ' OFFSET ?';
whereArgs.add((page - 1) * limit);
}
}
final List<Map<String, dynamic>> maps =
await db.rawQuery(query, whereArgs);
List<Product> products = [];
for (final map in maps) {
final variants = await _getProductVariants(db, map['id']);
final product = _mapToProduct(map, variants);
products.add(product);
}
log('📊 Retrieved ${products.length} products from database');
return products;
} catch (e) {
log('❌ Error getting products: $e');
return [];
}
}
// ========================================
// OPTIMIZED SEARCH with RANKING
// ========================================
Future<List<Product>> searchProductsOptimized(String query) async {
final db = await _db;
try {
log('🔍 Optimized search for: "$query"');
// âś… Smart query with prioritization
final List<Map<String, dynamic>> maps = await db.rawQuery('''
SELECT * FROM products
WHERE name LIKE ? OR sku LIKE ? OR description LIKE ?
ORDER BY
CASE
WHEN name LIKE ? THEN 1 -- Highest priority: name match
WHEN sku LIKE ? THEN 2 -- Second priority: SKU match
ELSE 3 -- Lowest priority: description
END,
name ASC
LIMIT 50
''', [
'%$query%', '%$query%', '%$query%',
'$query%', '$query%' // Prioritize results that start with query
]);
List<Product> products = [];
for (final map in maps) {
final variants = await _getProductVariants(db, map['id']);
products.add(_mapToProduct(map, variants));
}
log('🎯 Optimized search found ${products.length} results');
return products;
} catch (e) {
log('❌ Error in optimized search: $e');
return [];
}
}
// ========================================
// DATABASE ANALYTICS & MONITORING
// ========================================
Future<Map<String, dynamic>> getDatabaseStats() async {
final db = await _db;
try {
final productCount = Sqflite.firstIntValue(
await db.rawQuery('SELECT COUNT(*) FROM products')) ??
0;
final variantCount = Sqflite.firstIntValue(
await db.rawQuery('SELECT COUNT(*) FROM product_variants')) ??
0;
final categoryCount = Sqflite.firstIntValue(await db.rawQuery(
'SELECT COUNT(DISTINCT category_id) FROM products WHERE category_id IS NOT NULL')) ??
0;
final dbSize = await _getDatabaseSize();
final stats = {
'total_products': productCount,
'total_variants': variantCount,
'total_categories': categoryCount,
'database_size_mb': dbSize,
'cache_entries': _queryCache.length,
'cache_size_mb': _getCacheSize(),
};
log('📊 Database Stats: $stats');
return stats;
} catch (e) {
log('❌ Error getting database stats: $e');
return {};
}
}
Future<double> _getDatabaseSize() async {
try {
final dbPath = p.join(await getDatabasesPath(), 'pos_database.db');
final file = File(dbPath);
if (await file.exists()) {
final size = await file.length();
return size / (1024 * 1024); // Convert to MB
}
} catch (e) {
log('Error getting database size: $e');
}
return 0.0;
}
double _getCacheSize() {
double totalSize = 0;
_queryCache.forEach((key, products) {
totalSize += products.length * 0.001; // Rough estimate in MB
});
return totalSize;
}
// ========================================
// CACHE MANAGEMENT
// ========================================
String _generateCacheKey(
int page, int limit, String? categoryId, String? search) {
return 'products_${page}_${limit}_${categoryId ?? 'null'}_${search ?? 'null'}';
}
void clearCache() {
final count = _queryCache.length;
_queryCache.clear();
_cacheTimestamps.clear();
log('đź§ą Cache cleared: $count entries removed');
}
void clearExpiredCache() {
final now = DateTime.now();
final expiredKeys = <String>[];
_cacheTimestamps.forEach((key, timestamp) {
if (now.difference(timestamp) > _cacheExpiry) {
expiredKeys.add(key);
}
});
for (final key in expiredKeys) {
_queryCache.remove(key);
_cacheTimestamps.remove(key);
}
if (expiredKeys.isNotEmpty) {
log('⏰ Expired cache cleared: ${expiredKeys.length} entries');
}
}
// ========================================
// OTHER METHODS (Same as basic but with enhanced logging)
// ========================================
Future<Product?> getProductById(String id) async {
final db = await _db;
try {
final List<Map<String, dynamic>> maps = await db.query(
'products',
where: 'id = ?',
whereArgs: [id],
);
if (maps.isEmpty) {
log('❌ Product not found: $id');
return null;
}
final variants = await _getProductVariants(db, id);
final product = _mapToProduct(maps.first, variants);
log('âś… Product found: ${product.name}');
return product;
} catch (e) {
log('❌ Error getting product by ID: $e');
return null;
}
}
Future<int> getTotalCount({String? categoryId, String? search}) async {
final db = await _db;
try {
String query = 'SELECT COUNT(*) FROM products WHERE 1=1';
List<dynamic> whereArgs = [];
if (categoryId != null && categoryId.isNotEmpty) {
query += ' AND category_id = ?';
whereArgs.add(categoryId);
}
if (search != null && search.isNotEmpty) {
query += ' AND (name LIKE ? OR sku LIKE ? OR description LIKE ?)';
whereArgs.add('%$search%');
whereArgs.add('%$search%');
whereArgs.add('%$search%');
}
final result = await db.rawQuery(query, whereArgs);
final count = Sqflite.firstIntValue(result) ?? 0;
log('📊 Total count: $count (categoryId: $categoryId, search: $search)');
return count;
} catch (e) {
log('❌ Error getting total count: $e');
return 0;
}
}
Future<bool> hasProducts() async {
final count = await getTotalCount();
final hasData = count > 0;
log('🔍 Has products: $hasData ($count products)');
return hasData;
}
Future<void> clearAllProducts() async {
final db = await _db;
try {
await db.transaction((txn) async {
await txn.delete('product_variants');
await txn.delete('products');
});
clearCache();
log('🗑️ All products cleared from local DB');
} catch (e) {
log('❌ Error clearing products: $e');
rethrow;
}
}
// ========================================
// HELPER METHODS
// ========================================
Future<List<ProductVariant>> _getProductVariants(
Database db, String productId) async {
try {
final List<Map<String, dynamic>> maps = await db.query(
'product_variants',
where: 'product_id = ?',
whereArgs: [productId],
orderBy: 'name ASC',
);
return maps.map((map) => _mapToVariant(map)).toList();
} catch (e) {
log('❌ Error getting variants for product $productId: $e');
return [];
}
}
Map<String, dynamic> _productToMap(Product product) {
return {
'id': product.id,
'organization_id': product.organizationId,
'category_id': product.categoryId,
'sku': product.sku,
'name': product.name,
'description': product.description,
'price': product.price,
'cost': product.cost,
'business_type': product.businessType,
'image_url': product.imageUrl,
'printer_type': product.printerType,
'metadata':
product.metadata != null ? json.encode(product.metadata) : null,
'is_active': product.isActive == true ? 1 : 0,
'created_at': product.createdAt?.toIso8601String(),
'updated_at': product.updatedAt?.toIso8601String(),
};
}
Map<String, dynamic> _variantToMap(ProductVariant variant) {
return {
'id': variant.id,
'product_id': variant.productId,
'name': variant.name,
'price_modifier': variant.priceModifier,
'cost': variant.cost,
'metadata':
variant.metadata != null ? json.encode(variant.metadata) : null,
'created_at': variant.createdAt?.toIso8601String(),
'updated_at': variant.updatedAt?.toIso8601String(),
};
}
Product _mapToProduct(
Map<String, dynamic> map, List<ProductVariant> variants) {
return Product(
id: map['id'],
organizationId: map['organization_id'],
categoryId: map['category_id'],
sku: map['sku'],
name: map['name'],
description: map['description'],
price: map['price'],
cost: map['cost'],
businessType: map['business_type'],
imageUrl: map['image_url'],
printerType: map['printer_type'],
metadata: map['metadata'] != null ? json.decode(map['metadata']) : null,
isActive: map['is_active'] == 1,
createdAt:
map['created_at'] != null ? DateTime.parse(map['created_at']) : null,
updatedAt:
map['updated_at'] != null ? DateTime.parse(map['updated_at']) : null,
variants: variants,
);
}
ProductVariant _mapToVariant(Map<String, dynamic> map) {
return ProductVariant(
id: map['id'],
productId: map['product_id'],
name: map['name'],
priceModifier: map['price_modifier'],
cost: map['cost'],
metadata: map['metadata'] != null ? json.decode(map['metadata']) : null,
createdAt:
map['created_at'] != null ? DateTime.parse(map['created_at']) : null,
updatedAt:
map['updated_at'] != null ? DateTime.parse(map['updated_at']) : null,
);
}
}
@@ -56,7 +56,7 @@ class ProductRemoteDatasource {
log("Dio error: ${e.message}"); log("Dio error: ${e.message}");
return Left(e.response?.data['message'] ?? 'Gagal mengambil produk'); return Left(e.response?.data['message'] ?? 'Gagal mengambil produk');
} catch (e) { } catch (e) {
log("Unexpected error: $e", name: "productRemoteDatasource"); log("Unexpected error: $e");
return const Left('Unexpected error occurred'); return const Left('Unexpected error occurred');
} }
} }
@@ -19,7 +19,7 @@ class SettingsLocalDatasource {
return TaxModel( return TaxModel(
name: 'Tax', name: 'Tax',
type: TaxType.pajak, type: TaxType.pajak,
value: 0, value: 11,
); );
} }
} }
@@ -1,129 +0,0 @@
class CategoryAnalyticResponseModel {
final bool success;
final CategoryAnalyticData? data;
final dynamic errors;
CategoryAnalyticResponseModel({
required this.success,
required this.data,
this.errors,
});
// Dari JSON String ke Model
factory CategoryAnalyticResponseModel.fromJson(Map<String, dynamic> json) {
return CategoryAnalyticResponseModel(
success: json['success'],
data: json['data'] == null
? null
: CategoryAnalyticData.fromMap(json['data']),
errors: json['errors'],
);
}
// Dari Model ke JSON String
Map<String, dynamic> toJson() {
return {
'success': success,
'data': data?.toMap(),
'errors': errors,
};
}
// Dari Map ke Model
factory CategoryAnalyticResponseModel.fromMap(Map<String, dynamic> map) {
return CategoryAnalyticResponseModel(
success: map['success'],
data: CategoryAnalyticData.fromMap(map['data']),
errors: map['errors'],
);
}
// Dari Model ke Map
Map<String, dynamic> toMap() {
return {
'success': success,
'data': data?.toMap(),
'errors': errors,
};
}
}
class CategoryAnalyticData {
final String organizationId;
final String outletId;
final DateTime dateFrom;
final DateTime dateTo;
final List<CategoryAnalyticItem> data;
CategoryAnalyticData({
required this.organizationId,
required this.outletId,
required this.dateFrom,
required this.dateTo,
required this.data,
});
factory CategoryAnalyticData.fromMap(Map<String, dynamic> map) {
return CategoryAnalyticData(
organizationId: map['organization_id'],
outletId: map['outlet_id'],
dateFrom: DateTime.parse(map['date_from']),
dateTo: DateTime.parse(map['date_to']),
data: map['data'] == null
? []
: List<CategoryAnalyticItem>.from(
map['data']?.map((x) => CategoryAnalyticItem.fromMap(x)) ?? [],
),
);
}
Map<String, dynamic> toMap() {
return {
'organization_id': organizationId,
'outlet_id': outletId,
'date_from': dateFrom.toIso8601String(),
'date_to': dateTo.toIso8601String(),
'data': data.map((x) => x.toMap()).toList(),
};
}
}
class CategoryAnalyticItem {
final String categoryId;
final String categoryName;
final int totalRevenue;
final int totalQuantity;
final int productCount;
final int orderCount;
CategoryAnalyticItem({
required this.categoryId,
required this.categoryName,
required this.totalRevenue,
required this.totalQuantity,
required this.productCount,
required this.orderCount,
});
factory CategoryAnalyticItem.fromMap(Map<String, dynamic> map) {
return CategoryAnalyticItem(
categoryId: map['category_id'],
categoryName: map['category_name'],
totalRevenue: map['total_revenue'] ?? 0,
totalQuantity: map['total_quantity'] ?? 0,
productCount: map['product_count'] ?? 0,
orderCount: map['order_count'] ?? 0,
);
}
Map<String, dynamic> toMap() {
return {
'category_id': categoryId,
'category_name': categoryName,
'total_revenue': totalRevenue,
'total_quantity': totalQuantity,
'product_count': productCount,
'order_count': orderCount,
};
}
}
@@ -1,290 +0,0 @@
class InventoryAnalyticResponseModel {
final bool success;
final InventoryAnalyticData? data;
final dynamic errors;
InventoryAnalyticResponseModel({
required this.success,
required this.data,
this.errors,
});
// From JSON
factory InventoryAnalyticResponseModel.fromJson(Map<String, dynamic> json) {
return InventoryAnalyticResponseModel(
success: json['success'],
data: json['data'] != null
? InventoryAnalyticData.fromMap(json['data'])
: null,
errors: json['errors'],
);
}
// To JSON
Map<String, dynamic> toJson() {
return {
'success': success,
'data': data?.toMap(),
'errors': errors,
};
}
// From Map
factory InventoryAnalyticResponseModel.fromMap(Map<String, dynamic> map) {
return InventoryAnalyticResponseModel(
success: map['success'],
data: map['data'] != null
? InventoryAnalyticData.fromMap(map['data'])
: null,
errors: map['errors'],
);
}
// To Map
Map<String, dynamic> toMap() {
return {
'success': success,
'data': data?.toMap(),
'errors': errors,
};
}
}
class InventoryAnalyticData {
final InventorySummary summary;
final List<InventoryProductItem> products;
final List<InventoryIngredientItem> ingredients;
InventoryAnalyticData({
required this.summary,
required this.products,
required this.ingredients,
});
factory InventoryAnalyticData.fromMap(Map<String, dynamic> map) {
return InventoryAnalyticData(
summary: InventorySummary.fromMap(map['summary']),
products: map['products'] == null
? []
: List<InventoryProductItem>.from(
map['products']?.map((x) => InventoryProductItem.fromMap(x)) ??
[],
),
ingredients: map['ingredients'] == null
? []
: List<InventoryIngredientItem>.from(
map['ingredients']
?.map((x) => InventoryIngredientItem.fromMap(x)) ??
[],
),
);
}
Map<String, dynamic> toMap() {
return {
'summary': summary.toMap(),
'products': products.map((x) => x.toMap()).toList(),
'ingredients': ingredients.map((x) => x.toMap()).toList(),
};
}
}
class InventorySummary {
final int totalProducts;
final int totalIngredients;
final int totalValue;
final int lowStockProducts;
final int lowStockIngredients;
final int zeroStockProducts;
final int zeroStockIngredients;
final int totalSoldProducts;
final int totalSoldIngredients;
final String outletId;
final String outletName;
final DateTime generatedAt;
InventorySummary({
required this.totalProducts,
required this.totalIngredients,
required this.totalValue,
required this.lowStockProducts,
required this.lowStockIngredients,
required this.zeroStockProducts,
required this.zeroStockIngredients,
required this.totalSoldProducts,
required this.totalSoldIngredients,
required this.outletId,
required this.outletName,
required this.generatedAt,
});
factory InventorySummary.fromMap(Map<String, dynamic> map) {
return InventorySummary(
totalProducts: map['total_products'] ?? 0,
totalIngredients: map['total_ingredients'] ?? 0,
totalValue: map['total_value'] ?? 0,
lowStockProducts: map['low_stock_products'] ?? 0,
lowStockIngredients: map['low_stock_ingredients'] ?? 0,
zeroStockProducts: map['zero_stock_products'] ?? 0,
zeroStockIngredients: map['zero_stock_ingredients'] ?? 0,
totalSoldProducts: map['total_sold_products'] ?? 0,
totalSoldIngredients: map['total_sold_ingredients'] ?? 0,
outletId: map['outlet_id'],
outletName: map['outlet_name'],
generatedAt: DateTime.parse(map['generated_at']),
);
}
Map<String, dynamic> toMap() {
return {
'total_products': totalProducts,
'total_ingredients': totalIngredients,
'total_value': totalValue,
'low_stock_products': lowStockProducts,
'low_stock_ingredients': lowStockIngredients,
'zero_stock_products': zeroStockProducts,
'zero_stock_ingredients': zeroStockIngredients,
'total_sold_products': totalSoldProducts,
'total_sold_ingredients': totalSoldIngredients,
'outlet_id': outletId,
'outlet_name': outletName,
'generated_at': generatedAt.toIso8601String(),
};
}
}
class InventoryProductItem {
final String id;
final String productId;
final String productName;
final String categoryName;
final int quantity;
final int reorderLevel;
final num unitCost;
final int totalValue;
final int totalIn;
final int totalOut;
final bool isLowStock;
final bool isZeroStock;
final DateTime updatedAt;
InventoryProductItem({
required this.id,
required this.productId,
required this.productName,
required this.categoryName,
required this.quantity,
required this.reorderLevel,
required this.unitCost,
required this.totalValue,
required this.totalIn,
required this.totalOut,
required this.isLowStock,
required this.isZeroStock,
required this.updatedAt,
});
factory InventoryProductItem.fromMap(Map<String, dynamic> map) {
return InventoryProductItem(
id: map['id'],
productId: map['product_id'],
productName: map['product_name'],
categoryName: map['category_name'],
quantity: map['quantity'] ?? 0,
reorderLevel: map['reorder_level'] ?? 0,
unitCost: map['unit_cost'] ?? 0,
totalValue: map['total_value'] ?? 0,
totalIn: map['total_in'] ?? 0,
totalOut: map['total_out'] ?? 0,
isLowStock: map['is_low_stock'] ?? false,
isZeroStock: map['is_zero_stock'] ?? false,
updatedAt: DateTime.parse(map['updated_at']),
);
}
Map<String, dynamic> toMap() {
return {
'id': id,
'product_id': productId,
'product_name': productName,
'category_name': categoryName,
'quantity': quantity,
'reorder_level': reorderLevel,
'unit_cost': unitCost,
'total_value': totalValue,
'total_in': totalIn,
'total_out': totalOut,
'is_low_stock': isLowStock,
'is_zero_stock': isZeroStock,
'updated_at': updatedAt.toIso8601String(),
};
}
}
class InventoryIngredientItem {
final String id;
final String ingredientId;
final String ingredientName;
final String unitName;
final int quantity;
final int reorderLevel;
final num unitCost;
final int totalValue;
final int totalIn;
final int totalOut;
final bool isLowStock;
final bool isZeroStock;
final DateTime updatedAt;
InventoryIngredientItem({
required this.id,
required this.ingredientId,
required this.ingredientName,
required this.unitName,
required this.quantity,
required this.reorderLevel,
required this.unitCost,
required this.totalValue,
required this.totalIn,
required this.totalOut,
required this.isLowStock,
required this.isZeroStock,
required this.updatedAt,
});
factory InventoryIngredientItem.fromMap(Map<String, dynamic> map) {
return InventoryIngredientItem(
id: map['id'],
ingredientId: map['ingredient_id'],
ingredientName: map['ingredient_name'],
unitName: map['unit_name'],
quantity: map['quantity'] ?? 0,
reorderLevel: map['reorder_level'] ?? 0,
unitCost: map['unit_cost'] ?? 0,
totalValue: map['total_value'] ?? 0,
totalIn: map['total_in'] ?? 0,
totalOut: map['total_out'] ?? 0,
isLowStock: map['is_low_stock'] ?? false,
isZeroStock: map['is_zero_stock'] ?? false,
updatedAt: DateTime.parse(map['updated_at']),
);
}
Map<String, dynamic> toMap() {
return {
'id': id,
'ingredient_id': ingredientId,
'ingredient_name': ingredientName,
'unit_name': unitName,
'quantity': quantity,
'reorder_level': reorderLevel,
'unit_cost': unitCost,
'total_value': totalValue,
'total_in': totalIn,
'total_out': totalOut,
'is_low_stock': isLowStock,
'is_zero_stock': isZeroStock,
'updated_at': updatedAt.toIso8601String(),
};
}
}
@@ -1,8 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'package:enaklo_pos/data/models/response/product_response_model.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
class OrderDetailResponseModel { class OrderDetailResponseModel {
final bool? success; final bool? success;
final Order? data; final Order? data;
@@ -118,7 +115,7 @@ class Order {
final int? taxAmount; final int? taxAmount;
final int? discountAmount; final int? discountAmount;
final int? totalAmount; final int? totalAmount;
final num? totalCost; final int? totalCost;
final int? remainingAmount; final int? remainingAmount;
final String? paymentStatus; final String? paymentStatus;
final int? refundAmount; final int? refundAmount;
@@ -383,21 +380,3 @@ class Payment {
}; };
} }
} }
extension OrderItemListExtension on List<OrderItem> {
List<ProductQuantity> toProductQuantities() => map((e) => ProductQuantity(
product: Product(
id: e.productId,
name: e.productName,
price: e.unitPrice,
printerType: e.printerType,
),
variant: ProductVariant(
id: e.productVariantId,
name: e.productVariantName,
productId: e.productId,
),
notes: e.notes ?? '',
quantity: e.quantity ?? 0,
)).toList();
}
@@ -1,6 +1,6 @@
class PaymentMethodAnalyticResponseModel { class PaymentMethodAnalyticResponseModel {
final bool success; final bool success;
final PaymentMethodAnalyticData? data; final PaymentMethodAnalyticData data;
final dynamic errors; final dynamic errors;
PaymentMethodAnalyticResponseModel({ PaymentMethodAnalyticResponseModel({
@@ -18,9 +18,7 @@ class PaymentMethodAnalyticResponseModel {
factory PaymentMethodAnalyticResponseModel.fromMap(Map<String, dynamic> map) { factory PaymentMethodAnalyticResponseModel.fromMap(Map<String, dynamic> map) {
return PaymentMethodAnalyticResponseModel( return PaymentMethodAnalyticResponseModel(
success: map['success'], success: map['success'],
data: map['data'] == null data: PaymentMethodAnalyticData.fromMap(map['data']),
? null
: PaymentMethodAnalyticData.fromMap(map['data']),
errors: map['errors'], errors: map['errors'],
); );
} }
@@ -28,7 +26,7 @@ class PaymentMethodAnalyticResponseModel {
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return { return {
'success': success, 'success': success,
'data': data?.toMap(), 'data': data.toMap(),
'errors': errors, 'errors': errors,
}; };
} }
@@ -132,7 +130,7 @@ class PaymentMethodAnalyticItem {
final int totalAmount; final int totalAmount;
final int orderCount; final int orderCount;
final int paymentCount; final int paymentCount;
final num percentage; final int percentage;
PaymentMethodAnalyticItem({ PaymentMethodAnalyticItem({
required this.paymentMethodId, required this.paymentMethodId,
@@ -1,6 +1,6 @@
class ProductAnalyticResponseModel { class ProductAnalyticResponseModel {
final bool success; final bool success;
final ProductAnalyticData? data; final ProductAnalyticData data;
final dynamic errors; final dynamic errors;
ProductAnalyticResponseModel({ ProductAnalyticResponseModel({
@@ -17,8 +17,7 @@ class ProductAnalyticResponseModel {
factory ProductAnalyticResponseModel.fromMap(Map<String, dynamic> map) { factory ProductAnalyticResponseModel.fromMap(Map<String, dynamic> map) {
return ProductAnalyticResponseModel( return ProductAnalyticResponseModel(
success: map['success'] ?? false, success: map['success'] ?? false,
data: data: ProductAnalyticData.fromMap(map['data']),
map['data'] == null ? null : ProductAnalyticData.fromMap(map['data']),
errors: map['errors'], errors: map['errors'],
); );
} }
@@ -26,7 +25,7 @@ class ProductAnalyticResponseModel {
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return { return {
'success': success, 'success': success,
'data': data?.toMap(), 'data': data.toMap(),
'errors': errors, 'errors': errors,
}; };
} }
@@ -1,14 +1,6 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first // ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert'; import 'dart:convert';
int? parseInt(dynamic value) {
if (value == null) return null;
if (value is int) return value;
if (value is double) return value.toInt();
if (value is String) return int.tryParse(value);
return null;
}
class ProductResponseModel { class ProductResponseModel {
final bool? success; final bool? success;
final ProductData? data; final ProductData? data;
@@ -59,10 +51,10 @@ class ProductData {
? [] ? []
: List<Product>.from( : List<Product>.from(
json["products"].map((x) => Product.fromMap(x))), json["products"].map((x) => Product.fromMap(x))),
totalCount: parseInt(json["total_count"]), totalCount: json["total_count"],
page: parseInt(json["page"]), page: json["page"],
limit: parseInt(json["limit"]), limit: json["limit"],
totalPages: parseInt(json["total_pages"]), totalPages: json["total_pages"],
); );
Map<String, dynamic> toMap() => { Map<String, dynamic> toMap() => {
@@ -124,8 +116,8 @@ class Product {
sku: json["sku"], sku: json["sku"],
name: json["name"], name: json["name"],
description: json["description"], description: json["description"],
price: parseInt(json["price"]), price: json["price"],
cost: parseInt(json["cost"]), cost: json["cost"],
businessType: json["business_type"], businessType: json["business_type"],
imageUrl: json["image_url"], imageUrl: json["image_url"],
printerType: json["printer_type"], printerType: json["printer_type"],
@@ -145,7 +137,7 @@ class Product {
factory Product.fromOrderMap(Map<String, dynamic> json) => Product( factory Product.fromOrderMap(Map<String, dynamic> json) => Product(
id: json["id_product"], id: json["id_product"],
price: parseInt(json["price"]), price: json["price"],
); );
factory Product.fromLocalMap(Map<String, dynamic> json) => Product( factory Product.fromLocalMap(Map<String, dynamic> json) => Product(
@@ -155,8 +147,8 @@ class Product {
sku: json["sku"], sku: json["sku"],
name: json["name"], name: json["name"],
description: json["description"], description: json["description"],
price: parseInt(json["price"]), price: json["price"],
cost: parseInt(json["cost"]), cost: json["cost"],
businessType: json["business_type"], businessType: json["business_type"],
imageUrl: json["image_url"], imageUrl: json["image_url"],
printerType: json["printer_type"], printerType: json["printer_type"],
@@ -215,6 +207,96 @@ class Product {
? [] ? []
: List<dynamic>.from(variants!.map((x) => x.toMap())), : List<dynamic>.from(variants!.map((x) => x.toMap())),
}; };
@override
bool operator ==(covariant Product other) {
if (identical(this, other)) return true;
return other.id == id &&
other.organizationId == organizationId &&
other.categoryId == categoryId &&
other.sku == sku &&
other.name == name &&
other.description == description &&
other.price == price &&
other.cost == cost &&
other.businessType == businessType &&
other.imageUrl == imageUrl &&
other.printerType == printerType &&
other.metadata == metadata &&
other.isActive == isActive &&
other.createdAt == createdAt &&
other.updatedAt == updatedAt &&
_listEquals(other.variants, variants);
}
bool _listEquals(List<ProductVariant>? a, List<ProductVariant>? b) {
if (a == null && b == null) return true;
if (a == null || b == null) return false;
if (a.length != b.length) return false;
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
@override
int get hashCode {
return id.hashCode ^
organizationId.hashCode ^
categoryId.hashCode ^
sku.hashCode ^
name.hashCode ^
description.hashCode ^
price.hashCode ^
cost.hashCode ^
businessType.hashCode ^
imageUrl.hashCode ^
printerType.hashCode ^
metadata.hashCode ^
isActive.hashCode ^
createdAt.hashCode ^
updatedAt.hashCode ^
variants.hashCode;
}
Product copyWith({
String? id,
String? organizationId,
String? categoryId,
String? sku,
String? name,
String? description,
int? price,
int? cost,
String? businessType,
String? imageUrl,
String? printerType,
Map<String, dynamic>? metadata,
bool? isActive,
DateTime? createdAt,
DateTime? updatedAt,
List<ProductVariant>? variants,
}) {
return Product(
id: id ?? this.id,
organizationId: organizationId ?? this.organizationId,
categoryId: categoryId ?? this.categoryId,
sku: sku ?? this.sku,
name: name ?? this.name,
description: description ?? this.description,
price: price ?? this.price,
cost: cost ?? this.cost,
businessType: businessType ?? this.businessType,
imageUrl: imageUrl ?? this.imageUrl,
printerType: printerType ?? this.printerType,
metadata: metadata ?? this.metadata,
isActive: isActive ?? this.isActive,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
variants: variants ?? this.variants,
);
}
} }
class Category { class Category {
@@ -259,6 +341,28 @@ class Category {
"created_at": createdAt?.toIso8601String(), "created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(), "updated_at": updatedAt?.toIso8601String(),
}; };
@override
bool operator ==(covariant Category other) {
if (identical(this, other)) return true;
return other.id == id &&
other.name == name &&
other.description == description &&
other.image == image &&
other.createdAt == createdAt &&
other.updatedAt == updatedAt;
}
@override
int get hashCode {
return id.hashCode ^
name.hashCode ^
description.hashCode ^
image.hashCode ^
createdAt.hashCode ^
updatedAt.hashCode;
}
} }
class ProductVariant { class ProductVariant {
@@ -286,8 +390,8 @@ class ProductVariant {
id: json["id"], id: json["id"],
productId: json["product_id"], productId: json["product_id"],
name: json["name"], name: json["name"],
priceModifier: parseInt(json["price_modifier"]), priceModifier: json["price_modifier"],
cost: parseInt(json["cost"]), cost: json["cost"],
metadata: json["metadata"] ?? {}, metadata: json["metadata"] ?? {},
createdAt: json["created_at"] == null createdAt: json["created_at"] == null
? null ? null
@@ -1,6 +1,6 @@
class ProfitLossResponseModel { class ProfitLossResponseModel {
final bool success; final bool success;
final ProfitLossData? data; final ProfitLossData data;
final dynamic errors; final dynamic errors;
ProfitLossResponseModel({ ProfitLossResponseModel({
@@ -13,7 +13,7 @@ class ProfitLossResponseModel {
factory ProfitLossResponseModel.fromJson(Map<String, dynamic> json) { factory ProfitLossResponseModel.fromJson(Map<String, dynamic> json) {
return ProfitLossResponseModel( return ProfitLossResponseModel(
success: json['success'], success: json['success'],
data: json['data'] == null ? null : ProfitLossData.fromMap(json['data']), data: ProfitLossData.fromMap(json['data']),
errors: json['errors'], errors: json['errors'],
); );
} }
@@ -22,7 +22,7 @@ class ProfitLossResponseModel {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'success': success, 'success': success,
'data': data?.toMap(), 'data': data.toMap(),
'errors': errors, 'errors': errors,
}; };
} }
@@ -40,7 +40,7 @@ class ProfitLossResponseModel {
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return { return {
'success': success, 'success': success,
'data': data?.toMap(), 'data': data.toMap(),
'errors': errors, 'errors': errors,
}; };
} }
@@ -98,15 +98,15 @@ class ProfitLossData {
class ProfitLossSummary { class ProfitLossSummary {
final int totalRevenue; final int totalRevenue;
final num totalCost; final int totalCost;
final num grossProfit; final int grossProfit;
final double grossProfitMargin; final double grossProfitMargin;
final int totalTax; final int totalTax;
final int totalDiscount; final int totalDiscount;
final num netProfit; final int netProfit;
final double netProfitMargin; final double netProfitMargin;
final int totalOrders; final int totalOrders;
final double averageProfit; final int averageProfit;
final double profitabilityRatio; final double profitabilityRatio;
ProfitLossSummary({ ProfitLossSummary({
@@ -134,7 +134,7 @@ class ProfitLossSummary {
netProfit: map['net_profit'], netProfit: map['net_profit'],
netProfitMargin: (map['net_profit_margin'] as num).toDouble(), netProfitMargin: (map['net_profit_margin'] as num).toDouble(),
totalOrders: map['total_orders'], totalOrders: map['total_orders'],
averageProfit: (map['average_profit'] as num).toDouble(), averageProfit: map['average_profit'],
profitabilityRatio: (map['profitability_ratio'] as num).toDouble(), profitabilityRatio: (map['profitability_ratio'] as num).toDouble(),
); );
} }
@@ -159,12 +159,12 @@ class ProfitLossSummary {
class ProfitLossItem { class ProfitLossItem {
final String date; final String date;
final int revenue; final int revenue;
final num cost; final int cost;
final num grossProfit; final int grossProfit;
final double grossProfitMargin; final double grossProfitMargin;
final int tax; final int tax;
final int discount; final int discount;
final num netProfit; final int netProfit;
final double netProfitMargin; final double netProfitMargin;
final int orders; final int orders;
@@ -219,12 +219,12 @@ class ProfitLossProduct {
final String categoryName; final String categoryName;
final int quantitySold; final int quantitySold;
final int revenue; final int revenue;
final num cost; final int cost;
final num grossProfit; final int grossProfit;
final double grossProfitMargin; final double grossProfitMargin;
final int averagePrice; final int averagePrice;
final num averageCost; final int averageCost;
final num profitPerUnit; final int profitPerUnit;
ProfitLossProduct({ ProfitLossProduct({
required this.productId, required this.productId,
@@ -252,8 +252,8 @@ class ProfitLossProduct {
cost: map['cost'], cost: map['cost'],
grossProfit: map['gross_profit'], grossProfit: map['gross_profit'],
grossProfitMargin: (map['gross_profit_margin'] as num).toDouble(), grossProfitMargin: (map['gross_profit_margin'] as num).toDouble(),
averagePrice: (map['average_price'] as num).toInt(), averagePrice: map['average_price'],
averageCost: (map['average_cost'] as num).toInt(), averageCost: map['average_cost'],
profitPerUnit: map['profit_per_unit'], profitPerUnit: map['profit_per_unit'],
); );
} }
@@ -1,145 +0,0 @@
import 'dart:developer';
import 'package:dartz/dartz.dart';
import 'package:enaklo_pos/data/datasources/product/product_local_datasource.dart';
import 'package:enaklo_pos/data/models/response/product_response_model.dart';
class ProductRepository {
static ProductRepository? _instance;
final ProductLocalDatasource _localDatasource;
ProductRepository._internal()
: _localDatasource = ProductLocalDatasource.instance;
static ProductRepository get instance {
_instance ??= ProductRepository._internal();
return _instance!;
}
// ========================================
// PURE LOCAL DATABASE OPERATIONS
// ========================================
Future<Either<String, ProductResponseModel>> getProducts({
int page = 1,
int limit = 10,
String? categoryId,
String? search,
bool forceRefresh = false, // Ignored - kept for compatibility
}) async {
try {
log('📱 Getting products from local database - page: $page, categoryId: $categoryId, search: $search');
// Clean expired cache for optimal performance
_localDatasource.clearExpiredCache();
// Use cached query for maximum performance
final cachedProducts = await _localDatasource.getCachedProducts(
page: page,
limit: limit,
categoryId: categoryId,
search: search,
);
final totalCount = await _localDatasource.getTotalCount(
categoryId: categoryId,
search: search,
);
final productData = ProductData(
products: cachedProducts,
totalCount: totalCount,
page: page,
limit: limit,
totalPages: totalCount > 0 ? (totalCount / limit).ceil() : 0,
);
final response = ProductResponseModel(
success: true,
data: productData,
errors: null,
);
log('âś… Returned ${cachedProducts.length} local products (${totalCount} total)');
return Right(response);
} catch (e) {
log('❌ Error getting local products: $e');
return Left('Gagal memuat produk dari database lokal: $e');
}
}
// ========================================
// OPTIMIZED LOCAL SEARCH
// ========================================
Future<Either<String, List<Product>>> searchProductsOptimized(
String query) async {
try {
log('🔍 Local optimized search for: "$query"');
final products = await _localDatasource.searchProductsOptimized(query);
log('âś… Local search completed: ${products.length} results');
return Right(products);
} catch (e) {
log('❌ Error in local search: $e');
return Left('Pencarian lokal gagal: $e');
}
}
// ========================================
// LOCAL DATABASE OPERATIONS
// ========================================
// Refresh just cleans cache and reloads from local
Future<Either<String, ProductResponseModel>> refreshProducts({
String? categoryId,
String? search,
}) async {
log('🔄 Refreshing local products...');
// Clear cache for fresh local data
clearCache();
return await getProducts(
page: 1,
limit: 10,
categoryId: categoryId,
search: search,
);
}
Future<Product?> getProductById(String id) async {
log('🔍 Getting product by ID from local: $id');
return await _localDatasource.getProductById(id);
}
Future<bool> hasLocalProducts() async {
final hasProducts = await _localDatasource.hasProducts();
log('📊 Has local products: $hasProducts');
return hasProducts;
}
Future<Map<String, dynamic>> getDatabaseStats() async {
final stats = await _localDatasource.getDatabaseStats();
log('📊 Database stats: $stats');
return stats;
}
void clearCache() {
log('đź§ą Clearing local cache');
_localDatasource.clearCache();
}
// Helper method to check if local database is populated
Future<bool> isLocalDatabaseReady() async {
try {
final stats = await getDatabaseStats();
final productCount = stats['total_products'] ?? 0;
final isReady = productCount > 0;
log('🔍 Local database ready: $isReady ($productCount products)');
return isReady;
} catch (e) {
log('❌ Error checking database readiness: $e');
return false;
}
}
}
-79
View File
@@ -1,79 +0,0 @@
import 'dart:async';
import 'dart:developer';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:enaklo_pos/data/repositories/product/product_repository.dart';
class SyncManager {
final ProductRepository _productRepository;
final Connectivity _connectivity = Connectivity();
Timer? _syncTimer;
bool _isSyncing = false;
StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
SyncManager(this._productRepository) {
_startPeriodicSync();
_listenToConnectivityChanges();
}
void _startPeriodicSync() {
// Sync setiap 5 menit jika ada koneksi
_syncTimer = Timer.periodic(Duration(minutes: 5), (timer) {
_performBackgroundSync();
});
}
void _listenToConnectivityChanges() {
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(
(List<ConnectivityResult> results) {
// Check if any connection is available
final hasConnection =
results.any((result) => result != ConnectivityResult.none);
if (hasConnection) {
log('Connection restored, starting background sync');
_performBackgroundSync();
} else {
log('Connection lost');
}
},
);
}
Future<void> _performBackgroundSync() async {
if (_isSyncing) return;
// Check current connectivity before syncing
final connectivityResults = await _connectivity.checkConnectivity();
final hasConnection =
connectivityResults.any((result) => result != ConnectivityResult.none);
if (!hasConnection) {
log('No internet connection, skipping sync');
return;
}
try {
_isSyncing = true;
log('Starting background sync');
await _productRepository.refreshProducts();
log('Background sync completed');
} catch (e) {
log('Background sync failed: $e');
} finally {
_isSyncing = false;
}
}
// Public method untuk manual sync
Future<void> performManualSync() async {
await _performBackgroundSync();
}
void dispose() {
_syncTimer?.cancel();
_connectivitySubscription?.cancel();
}
}
+11 -41
View File
@@ -1,6 +1,4 @@
import 'dart:async';
import 'dart:developer'; import 'dart:developer';
import 'dart:ui';
import 'package:enaklo_pos/core/constants/theme.dart'; import 'package:enaklo_pos/core/constants/theme.dart';
import 'package:enaklo_pos/core/network/dio_client.dart'; import 'package:enaklo_pos/core/network/dio_client.dart';
import 'package:enaklo_pos/data/datasources/analytic_remote_datasource.dart'; import 'package:enaklo_pos/data/datasources/analytic_remote_datasource.dart';
@@ -11,7 +9,6 @@ import 'package:enaklo_pos/data/datasources/table_remote_datasource.dart';
import 'package:enaklo_pos/data/datasources/user_remote_datasource.dart'; import 'package:enaklo_pos/data/datasources/user_remote_datasource.dart';
import 'package:enaklo_pos/presentation/customer/bloc/customer_form/customer_form_bloc.dart'; import 'package:enaklo_pos/presentation/customer/bloc/customer_form/customer_form_bloc.dart';
import 'package:enaklo_pos/presentation/customer/bloc/customer_loader/customer_loader_bloc.dart'; import 'package:enaklo_pos/presentation/customer/bloc/customer_loader/customer_loader_bloc.dart';
import 'package:enaklo_pos/presentation/data_sync/bloc/data_sync_bloc.dart';
import 'package:enaklo_pos/presentation/home/bloc/category_loader/category_loader_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/category_loader/category_loader_bloc.dart';
import 'package:enaklo_pos/presentation/home/bloc/current_outlet/current_outlet_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/current_outlet/current_outlet_bloc.dart';
import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart';
@@ -19,18 +16,13 @@ import 'package:enaklo_pos/presentation/home/bloc/outlet_loader/outlet_loader_bl
import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart';
import 'package:enaklo_pos/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart';
import 'package:enaklo_pos/presentation/refund/bloc/refund_bloc.dart'; import 'package:enaklo_pos/presentation/refund/bloc/refund_bloc.dart';
import 'package:enaklo_pos/presentation/report/blocs/category_report/category_report_bloc.dart';
import 'package:enaklo_pos/presentation/report/blocs/inventory_report/inventory_report_bloc.dart';
import 'package:enaklo_pos/presentation/report/blocs/profit_loss/profit_loss_bloc.dart'; import 'package:enaklo_pos/presentation/report/blocs/profit_loss/profit_loss_bloc.dart';
import 'package:enaklo_pos/presentation/report/blocs/report/report_bloc.dart';
import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart'; import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart';
import 'package:enaklo_pos/presentation/sales/blocs/payment_form/payment_form_bloc.dart'; import 'package:enaklo_pos/presentation/sales/blocs/payment_form/payment_form_bloc.dart';
import 'package:enaklo_pos/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_bloc.dart'; import 'package:enaklo_pos/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_bloc.dart';
import 'package:enaklo_pos/presentation/setting/bloc/upload_file/upload_file_bloc.dart'; import 'package:enaklo_pos/presentation/setting/bloc/upload_file/upload_file_bloc.dart';
import 'package:enaklo_pos/presentation/table/blocs/transfer_table/transfer_table_bloc.dart'; import 'package:enaklo_pos/presentation/table/blocs/transfer_table/transfer_table_bloc.dart';
import 'package:enaklo_pos/presentation/void/bloc/void_order_bloc.dart'; import 'package:enaklo_pos/presentation/void/bloc/void_order_bloc.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/data/datasources/auth_remote_datasource.dart'; import 'package:enaklo_pos/data/datasources/auth_remote_datasource.dart';
@@ -79,6 +71,8 @@ import 'package:enaklo_pos/presentation/setting/bloc/sync_product/sync_product_b
import 'package:enaklo_pos/presentation/setting/bloc/tax_settings/tax_settings_bloc.dart'; import 'package:enaklo_pos/presentation/setting/bloc/tax_settings/tax_settings_bloc.dart';
import 'package:enaklo_pos/presentation/table/blocs/update_table/update_table_bloc.dart'; import 'package:enaklo_pos/presentation/table/blocs/update_table/update_table_bloc.dart';
import 'package:enaklo_pos/presentation/home/bloc/add_order_items/add_order_items_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/add_order_items/add_order_items_bloc.dart';
import 'package:flutter/services.dart';
import 'presentation/auth/bloc/login/login_bloc.dart'; import 'presentation/auth/bloc/login/login_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@@ -86,28 +80,16 @@ import 'presentation/home/pages/dashboard_page.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() async { void main() async {
runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await SystemChrome.setPreferredOrientations([
await Firebase.initializeApp(); DeviceOrientation.landscapeLeft,
await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true); DeviceOrientation.landscapeRight,
]);
// âś… Tambah custom logs // await LamanPrint.init();
FlutterError.onError = (errorDetails) { // final dir = await getApplicationDocumentsDirectory();
FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails); // Hive.init(dir.path);
}; // Hive.registerAdapter(TableDataAdapter());
// âś… Catch platform errors juga
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
runApp(ProviderScope(child: MyApp())); runApp(ProviderScope(child: MyApp()));
}, (error, stack) {
// âś… Ini udah bener
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
});
} }
class MyApp extends StatefulWidget { class MyApp extends StatefulWidget {
@@ -262,7 +244,7 @@ class _MyAppState extends State<MyApp> {
create: (context) => AddOrderItemsBloc(OrderRemoteDatasource()), create: (context) => AddOrderItemsBloc(OrderRemoteDatasource()),
), ),
BlocProvider( BlocProvider(
create: (context) => ProductLoaderBloc(), create: (context) => ProductLoaderBloc(ProductRemoteDatasource()),
), ),
BlocProvider( BlocProvider(
create: (context) => OrderFormBloc(OrderRemoteDatasource()), create: (context) => OrderFormBloc(OrderRemoteDatasource()),
@@ -306,18 +288,6 @@ class _MyAppState extends State<MyApp> {
BlocProvider( BlocProvider(
create: (context) => TransferTableBloc(TableRemoteDataSource()), create: (context) => TransferTableBloc(TableRemoteDataSource()),
), ),
BlocProvider(
create: (context) => ReportBloc(AnalyticRemoteDatasource()),
),
BlocProvider(
create: (context) => InventoryReportBloc(AnalyticRemoteDatasource()),
),
BlocProvider(
create: (context) => CategoryReportBloc(AnalyticRemoteDatasource()),
),
BlocProvider(
create: (context) => DataSyncBloc(),
),
], ],
child: MaterialApp( child: MaterialApp(
navigatorKey: AuthInterceptor.navigatorKey, navigatorKey: AuthInterceptor.navigatorKey,
+2 -2
View File
@@ -1,4 +1,3 @@
import 'package:enaklo_pos/presentation/data_sync/pages/data_sync_page.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/auth_local_datasource.dart'; import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
@@ -10,6 +9,7 @@ import '../../core/components/buttons.dart';
import '../../core/components/custom_text_field.dart'; import '../../core/components/custom_text_field.dart';
import '../../core/components/spaces.dart'; import '../../core/components/spaces.dart';
import '../../core/constants/colors.dart'; import '../../core/constants/colors.dart';
import '../home/pages/dashboard_page.dart';
import 'bloc/login/login_bloc.dart'; import 'bloc/login/login_bloc.dart';
class LoginPage extends StatefulWidget { class LoginPage extends StatefulWidget {
@@ -104,7 +104,7 @@ class _LoginPageState extends State<LoginPage> {
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const DataSyncPage(), builder: (context) => const DashboardPage(),
), ),
); );
}, },
@@ -1,185 +0,0 @@
import 'dart:async';
import 'dart:developer';
import 'package:bloc/bloc.dart';
import 'package:enaklo_pos/data/datasources/product/product_local_datasource.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../../data/datasources/product_remote_datasource.dart';
part 'data_sync_event.dart';
part 'data_sync_state.dart';
part 'data_sync_bloc.freezed.dart';
enum SyncStep { products, categories, variants, completed }
class SyncStats {
final int totalProducts;
final int totalCategories;
final int totalVariants;
final double databaseSizeMB;
SyncStats({
required this.totalProducts,
required this.totalCategories,
required this.totalVariants,
required this.databaseSizeMB,
});
}
class DataSyncBloc extends Bloc<DataSyncEvent, DataSyncState> {
final ProductRemoteDatasource _remoteDatasource = ProductRemoteDatasource();
final ProductLocalDatasource _localDatasource =
ProductLocalDatasource.instance;
Timer? _progressTimer;
bool _isCancelled = false;
DataSyncBloc() : super(const DataSyncState.initial()) {
on<_StartSync>(_onStartSync);
on<_CancelSync>(_onCancelSync);
}
@override
Future<void> close() {
_progressTimer?.cancel();
return super.close();
}
Future<void> _onStartSync(
_StartSync event,
Emitter<DataSyncState> emit,
) async {
log('🔄 Starting data sync...');
_isCancelled = false;
try {
// Step 1: Clear existing local data
emit(const DataSyncState.syncing(
SyncStep.products, 0.1, 'Membersihkan data lama...'));
await _localDatasource.clearAllProducts();
if (_isCancelled) return;
// Step 2: Sync products
await _syncProducts(emit);
if (_isCancelled) return;
// Step 3: Generate final stats
emit(const DataSyncState.syncing(
SyncStep.completed, 0.9, 'Menyelesaikan sinkronisasi...'));
final stats = await _generateSyncStats();
emit(DataSyncState.completed(stats));
log('âś… Sync completed successfully');
} catch (e) {
log('❌ Sync failed: $e');
emit(DataSyncState.error('Gagal sinkronisasi: $e'));
}
}
Future<void> _syncProducts(Emitter<DataSyncState> emit) async {
log('📦 Syncing products...');
int page = 1;
int totalSynced = 0;
int? totalCount;
int? totalPages;
bool shouldContinue = true;
while (!_isCancelled && shouldContinue) {
// Calculate accurate progress based on total count
double progress = 0.2;
if (totalCount != null && (totalCount ?? 0) > 0) {
progress = 0.2 + (totalSynced / (totalCount ?? 0)) * 0.6;
}
emit(DataSyncState.syncing(
SyncStep.products,
progress,
totalCount != null
? 'Mengunduh produk... ($totalSynced dari $totalCount)'
: 'Mengunduh produk... ($totalSynced produk)',
));
final result = await _remoteDatasource.getProducts(
page: page,
limit: 50, // Bigger batch for sync
);
await result.fold(
(failure) async {
throw Exception(failure);
},
(response) async {
final products = response.data?.products ?? [];
final responseData = response.data;
// Get pagination info from first response
if (page == 1 && responseData != null) {
totalCount = responseData.totalCount;
totalPages = responseData.totalPages;
log('📊 Total products to sync: $totalCount (${totalPages} pages)');
}
if (products.isEmpty) {
shouldContinue = false;
return;
}
// Save to local database in batches
await _localDatasource.saveProductsBatch(products);
totalSynced += products.length;
page++;
log('📦 Synced page ${page - 1}: ${products.length} products (Total: $totalSynced)');
// Check if we reached the end using pagination info
if (totalPages != null && page > (totalPages ?? 0)) {
shouldContinue = false;
return;
}
// Fallback check if pagination info not available
if (products.length < 50) {
shouldContinue = false;
return;
}
// Small delay to prevent overwhelming the server
await Future.delayed(Duration(milliseconds: 100));
},
);
}
emit(DataSyncState.syncing(
SyncStep.completed,
0.8,
'Produk berhasil diunduh ($totalSynced dari ${totalCount ?? totalSynced})',
));
log('âś… Products sync completed: $totalSynced products synced');
}
Future<SyncStats> _generateSyncStats() async {
final dbStats = await _localDatasource.getDatabaseStats();
return SyncStats(
totalProducts: dbStats['total_products'] ?? 0,
totalCategories: dbStats['total_categories'] ?? 0,
totalVariants: dbStats['total_variants'] ?? 0,
databaseSizeMB: dbStats['database_size_mb'] ?? 0.0,
);
}
Future<void> _onCancelSync(
_CancelSync event,
Emitter<DataSyncState> emit,
) async {
log('⏹️ Cancelling sync...');
_isCancelled = true;
_progressTimer?.cancel();
emit(const DataSyncState.initial());
}
}
@@ -1,962 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'data_sync_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$DataSyncEvent {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() startSync,
required TResult Function() cancelSync,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? startSync,
TResult? Function()? cancelSync,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? startSync,
TResult Function()? cancelSync,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_StartSync value) startSync,
required TResult Function(_CancelSync value) cancelSync,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_StartSync value)? startSync,
TResult? Function(_CancelSync value)? cancelSync,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_StartSync value)? startSync,
TResult Function(_CancelSync value)? cancelSync,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $DataSyncEventCopyWith<$Res> {
factory $DataSyncEventCopyWith(
DataSyncEvent value, $Res Function(DataSyncEvent) then) =
_$DataSyncEventCopyWithImpl<$Res, DataSyncEvent>;
}
/// @nodoc
class _$DataSyncEventCopyWithImpl<$Res, $Val extends DataSyncEvent>
implements $DataSyncEventCopyWith<$Res> {
_$DataSyncEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of DataSyncEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$StartSyncImplCopyWith<$Res> {
factory _$$StartSyncImplCopyWith(
_$StartSyncImpl value, $Res Function(_$StartSyncImpl) then) =
__$$StartSyncImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$StartSyncImplCopyWithImpl<$Res>
extends _$DataSyncEventCopyWithImpl<$Res, _$StartSyncImpl>
implements _$$StartSyncImplCopyWith<$Res> {
__$$StartSyncImplCopyWithImpl(
_$StartSyncImpl _value, $Res Function(_$StartSyncImpl) _then)
: super(_value, _then);
/// Create a copy of DataSyncEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$StartSyncImpl implements _StartSync {
const _$StartSyncImpl();
@override
String toString() {
return 'DataSyncEvent.startSync()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$StartSyncImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() startSync,
required TResult Function() cancelSync,
}) {
return startSync();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? startSync,
TResult? Function()? cancelSync,
}) {
return startSync?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? startSync,
TResult Function()? cancelSync,
required TResult orElse(),
}) {
if (startSync != null) {
return startSync();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_StartSync value) startSync,
required TResult Function(_CancelSync value) cancelSync,
}) {
return startSync(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_StartSync value)? startSync,
TResult? Function(_CancelSync value)? cancelSync,
}) {
return startSync?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_StartSync value)? startSync,
TResult Function(_CancelSync value)? cancelSync,
required TResult orElse(),
}) {
if (startSync != null) {
return startSync(this);
}
return orElse();
}
}
abstract class _StartSync implements DataSyncEvent {
const factory _StartSync() = _$StartSyncImpl;
}
/// @nodoc
abstract class _$$CancelSyncImplCopyWith<$Res> {
factory _$$CancelSyncImplCopyWith(
_$CancelSyncImpl value, $Res Function(_$CancelSyncImpl) then) =
__$$CancelSyncImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$CancelSyncImplCopyWithImpl<$Res>
extends _$DataSyncEventCopyWithImpl<$Res, _$CancelSyncImpl>
implements _$$CancelSyncImplCopyWith<$Res> {
__$$CancelSyncImplCopyWithImpl(
_$CancelSyncImpl _value, $Res Function(_$CancelSyncImpl) _then)
: super(_value, _then);
/// Create a copy of DataSyncEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$CancelSyncImpl implements _CancelSync {
const _$CancelSyncImpl();
@override
String toString() {
return 'DataSyncEvent.cancelSync()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$CancelSyncImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() startSync,
required TResult Function() cancelSync,
}) {
return cancelSync();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? startSync,
TResult? Function()? cancelSync,
}) {
return cancelSync?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? startSync,
TResult Function()? cancelSync,
required TResult orElse(),
}) {
if (cancelSync != null) {
return cancelSync();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_StartSync value) startSync,
required TResult Function(_CancelSync value) cancelSync,
}) {
return cancelSync(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_StartSync value)? startSync,
TResult? Function(_CancelSync value)? cancelSync,
}) {
return cancelSync?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_StartSync value)? startSync,
TResult Function(_CancelSync value)? cancelSync,
required TResult orElse(),
}) {
if (cancelSync != null) {
return cancelSync(this);
}
return orElse();
}
}
abstract class _CancelSync implements DataSyncEvent {
const factory _CancelSync() = _$CancelSyncImpl;
}
/// @nodoc
mixin _$DataSyncState {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function(SyncStep step, double progress, String message)
syncing,
required TResult Function(SyncStats stats) completed,
required TResult Function(String message) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function(SyncStep step, double progress, String message)? syncing,
TResult? Function(SyncStats stats)? completed,
TResult? Function(String message)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function(SyncStep step, double progress, String message)? syncing,
TResult Function(SyncStats stats)? completed,
TResult Function(String message)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Syncing value) syncing,
required TResult Function(_Completed value) completed,
required TResult Function(_Error value) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Syncing value)? syncing,
TResult? Function(_Completed value)? completed,
TResult? Function(_Error value)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Syncing value)? syncing,
TResult Function(_Completed value)? completed,
TResult Function(_Error value)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $DataSyncStateCopyWith<$Res> {
factory $DataSyncStateCopyWith(
DataSyncState value, $Res Function(DataSyncState) then) =
_$DataSyncStateCopyWithImpl<$Res, DataSyncState>;
}
/// @nodoc
class _$DataSyncStateCopyWithImpl<$Res, $Val extends DataSyncState>
implements $DataSyncStateCopyWith<$Res> {
_$DataSyncStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of DataSyncState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$InitialImplCopyWith<$Res> {
factory _$$InitialImplCopyWith(
_$InitialImpl value, $Res Function(_$InitialImpl) then) =
__$$InitialImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$InitialImplCopyWithImpl<$Res>
extends _$DataSyncStateCopyWithImpl<$Res, _$InitialImpl>
implements _$$InitialImplCopyWith<$Res> {
__$$InitialImplCopyWithImpl(
_$InitialImpl _value, $Res Function(_$InitialImpl) _then)
: super(_value, _then);
/// Create a copy of DataSyncState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$InitialImpl implements _Initial {
const _$InitialImpl();
@override
String toString() {
return 'DataSyncState.initial()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$InitialImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function(SyncStep step, double progress, String message)
syncing,
required TResult Function(SyncStats stats) completed,
required TResult Function(String message) error,
}) {
return initial();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function(SyncStep step, double progress, String message)? syncing,
TResult? Function(SyncStats stats)? completed,
TResult? Function(String message)? error,
}) {
return initial?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function(SyncStep step, double progress, String message)? syncing,
TResult Function(SyncStats stats)? completed,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Syncing value) syncing,
required TResult Function(_Completed value) completed,
required TResult Function(_Error value) error,
}) {
return initial(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Syncing value)? syncing,
TResult? Function(_Completed value)? completed,
TResult? Function(_Error value)? error,
}) {
return initial?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Syncing value)? syncing,
TResult Function(_Completed value)? completed,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial(this);
}
return orElse();
}
}
abstract class _Initial implements DataSyncState {
const factory _Initial() = _$InitialImpl;
}
/// @nodoc
abstract class _$$SyncingImplCopyWith<$Res> {
factory _$$SyncingImplCopyWith(
_$SyncingImpl value, $Res Function(_$SyncingImpl) then) =
__$$SyncingImplCopyWithImpl<$Res>;
@useResult
$Res call({SyncStep step, double progress, String message});
}
/// @nodoc
class __$$SyncingImplCopyWithImpl<$Res>
extends _$DataSyncStateCopyWithImpl<$Res, _$SyncingImpl>
implements _$$SyncingImplCopyWith<$Res> {
__$$SyncingImplCopyWithImpl(
_$SyncingImpl _value, $Res Function(_$SyncingImpl) _then)
: super(_value, _then);
/// Create a copy of DataSyncState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? step = null,
Object? progress = null,
Object? message = null,
}) {
return _then(_$SyncingImpl(
null == step
? _value.step
: step // ignore: cast_nullable_to_non_nullable
as SyncStep,
null == progress
? _value.progress
: progress // ignore: cast_nullable_to_non_nullable
as double,
null == message
? _value.message
: message // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
class _$SyncingImpl implements _Syncing {
const _$SyncingImpl(this.step, this.progress, this.message);
@override
final SyncStep step;
@override
final double progress;
@override
final String message;
@override
String toString() {
return 'DataSyncState.syncing(step: $step, progress: $progress, message: $message)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SyncingImpl &&
(identical(other.step, step) || other.step == step) &&
(identical(other.progress, progress) ||
other.progress == progress) &&
(identical(other.message, message) || other.message == message));
}
@override
int get hashCode => Object.hash(runtimeType, step, progress, message);
/// Create a copy of DataSyncState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SyncingImplCopyWith<_$SyncingImpl> get copyWith =>
__$$SyncingImplCopyWithImpl<_$SyncingImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function(SyncStep step, double progress, String message)
syncing,
required TResult Function(SyncStats stats) completed,
required TResult Function(String message) error,
}) {
return syncing(step, progress, message);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function(SyncStep step, double progress, String message)? syncing,
TResult? Function(SyncStats stats)? completed,
TResult? Function(String message)? error,
}) {
return syncing?.call(step, progress, message);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function(SyncStep step, double progress, String message)? syncing,
TResult Function(SyncStats stats)? completed,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (syncing != null) {
return syncing(step, progress, message);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Syncing value) syncing,
required TResult Function(_Completed value) completed,
required TResult Function(_Error value) error,
}) {
return syncing(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Syncing value)? syncing,
TResult? Function(_Completed value)? completed,
TResult? Function(_Error value)? error,
}) {
return syncing?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Syncing value)? syncing,
TResult Function(_Completed value)? completed,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (syncing != null) {
return syncing(this);
}
return orElse();
}
}
abstract class _Syncing implements DataSyncState {
const factory _Syncing(
final SyncStep step, final double progress, final String message) =
_$SyncingImpl;
SyncStep get step;
double get progress;
String get message;
/// Create a copy of DataSyncState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SyncingImplCopyWith<_$SyncingImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$CompletedImplCopyWith<$Res> {
factory _$$CompletedImplCopyWith(
_$CompletedImpl value, $Res Function(_$CompletedImpl) then) =
__$$CompletedImplCopyWithImpl<$Res>;
@useResult
$Res call({SyncStats stats});
}
/// @nodoc
class __$$CompletedImplCopyWithImpl<$Res>
extends _$DataSyncStateCopyWithImpl<$Res, _$CompletedImpl>
implements _$$CompletedImplCopyWith<$Res> {
__$$CompletedImplCopyWithImpl(
_$CompletedImpl _value, $Res Function(_$CompletedImpl) _then)
: super(_value, _then);
/// Create a copy of DataSyncState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? stats = null,
}) {
return _then(_$CompletedImpl(
null == stats
? _value.stats
: stats // ignore: cast_nullable_to_non_nullable
as SyncStats,
));
}
}
/// @nodoc
class _$CompletedImpl implements _Completed {
const _$CompletedImpl(this.stats);
@override
final SyncStats stats;
@override
String toString() {
return 'DataSyncState.completed(stats: $stats)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$CompletedImpl &&
(identical(other.stats, stats) || other.stats == stats));
}
@override
int get hashCode => Object.hash(runtimeType, stats);
/// Create a copy of DataSyncState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$CompletedImplCopyWith<_$CompletedImpl> get copyWith =>
__$$CompletedImplCopyWithImpl<_$CompletedImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function(SyncStep step, double progress, String message)
syncing,
required TResult Function(SyncStats stats) completed,
required TResult Function(String message) error,
}) {
return completed(stats);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function(SyncStep step, double progress, String message)? syncing,
TResult? Function(SyncStats stats)? completed,
TResult? Function(String message)? error,
}) {
return completed?.call(stats);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function(SyncStep step, double progress, String message)? syncing,
TResult Function(SyncStats stats)? completed,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (completed != null) {
return completed(stats);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Syncing value) syncing,
required TResult Function(_Completed value) completed,
required TResult Function(_Error value) error,
}) {
return completed(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Syncing value)? syncing,
TResult? Function(_Completed value)? completed,
TResult? Function(_Error value)? error,
}) {
return completed?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Syncing value)? syncing,
TResult Function(_Completed value)? completed,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (completed != null) {
return completed(this);
}
return orElse();
}
}
abstract class _Completed implements DataSyncState {
const factory _Completed(final SyncStats stats) = _$CompletedImpl;
SyncStats get stats;
/// Create a copy of DataSyncState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$CompletedImplCopyWith<_$CompletedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$ErrorImplCopyWith<$Res> {
factory _$$ErrorImplCopyWith(
_$ErrorImpl value, $Res Function(_$ErrorImpl) then) =
__$$ErrorImplCopyWithImpl<$Res>;
@useResult
$Res call({String message});
}
/// @nodoc
class __$$ErrorImplCopyWithImpl<$Res>
extends _$DataSyncStateCopyWithImpl<$Res, _$ErrorImpl>
implements _$$ErrorImplCopyWith<$Res> {
__$$ErrorImplCopyWithImpl(
_$ErrorImpl _value, $Res Function(_$ErrorImpl) _then)
: super(_value, _then);
/// Create a copy of DataSyncState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? message = null,
}) {
return _then(_$ErrorImpl(
null == message
? _value.message
: message // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
class _$ErrorImpl implements _Error {
const _$ErrorImpl(this.message);
@override
final String message;
@override
String toString() {
return 'DataSyncState.error(message: $message)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ErrorImpl &&
(identical(other.message, message) || other.message == message));
}
@override
int get hashCode => Object.hash(runtimeType, message);
/// Create a copy of DataSyncState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ErrorImplCopyWith<_$ErrorImpl> get copyWith =>
__$$ErrorImplCopyWithImpl<_$ErrorImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function(SyncStep step, double progress, String message)
syncing,
required TResult Function(SyncStats stats) completed,
required TResult Function(String message) error,
}) {
return error(message);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function(SyncStep step, double progress, String message)? syncing,
TResult? Function(SyncStats stats)? completed,
TResult? Function(String message)? error,
}) {
return error?.call(message);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function(SyncStep step, double progress, String message)? syncing,
TResult Function(SyncStats stats)? completed,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(message);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Syncing value) syncing,
required TResult Function(_Completed value) completed,
required TResult Function(_Error value) error,
}) {
return error(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Syncing value)? syncing,
TResult? Function(_Completed value)? completed,
TResult? Function(_Error value)? error,
}) {
return error?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Syncing value)? syncing,
TResult Function(_Completed value)? completed,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(this);
}
return orElse();
}
}
abstract class _Error implements DataSyncState {
const factory _Error(final String message) = _$ErrorImpl;
String get message;
/// Create a copy of DataSyncState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ErrorImplCopyWith<_$ErrorImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1,7 +0,0 @@
part of 'data_sync_bloc.dart';
@freezed
class DataSyncEvent with _$DataSyncEvent {
const factory DataSyncEvent.startSync() = _StartSync;
const factory DataSyncEvent.cancelSync() = _CancelSync;
}
@@ -1,13 +0,0 @@
part of 'data_sync_bloc.dart';
@freezed
class DataSyncState with _$DataSyncState {
const factory DataSyncState.initial() = _Initial;
const factory DataSyncState.syncing(
SyncStep step,
double progress,
String message,
) = _Syncing;
const factory DataSyncState.completed(SyncStats stats) = _Completed;
const factory DataSyncState.error(String message) = _Error;
}
@@ -1,791 +0,0 @@
import 'dart:async';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../core/components/buttons.dart';
import '../../../core/constants/colors.dart';
import '../bloc/data_sync_bloc.dart';
class DataSyncPage extends StatefulWidget {
const DataSyncPage({super.key});
@override
State<DataSyncPage> createState() => _DataSyncPageState();
}
class _DataSyncPageState extends State<DataSyncPage>
with TickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _progressAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: Duration(milliseconds: 500),
vsync: this,
);
_progressAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
));
// Auto start sync
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<DataSyncBloc>().add(const DataSyncEvent.startSync());
});
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
final isLandscape = mediaQuery.orientation == Orientation.landscape;
final screenHeight = mediaQuery.size.height;
final screenWidth = mediaQuery.size.width;
return Scaffold(
backgroundColor: Colors.grey.shade50,
body: SafeArea(
child: BlocConsumer<DataSyncBloc, DataSyncState>(
listener: (context, state) {
state.maybeWhen(
orElse: () {},
syncing: (step, progress, message) {
_animationController.animateTo(progress);
},
completed: (stats) {
_animationController.animateTo(1.0);
// Navigate to home after delay
Future.delayed(Duration(seconds: 2), () {
context.pushReplacement(DashboardPage());
});
},
error: (message) {
_animationController.stop();
},
);
},
builder: (context, state) {
if (isLandscape) {
return _buildLandscapeLayout(state, screenWidth, screenHeight);
} else {
return _buildPortraitLayout(state, screenHeight);
}
},
),
),
);
}
// Portrait layout (original)
Widget _buildPortraitLayout(DataSyncState state, double screenHeight) {
return Padding(
padding: EdgeInsets.all(24),
child: Column(
children: [
SizedBox(height: screenHeight * 0.08), // Responsive spacing
// Header
_buildHeader(false),
SizedBox(height: screenHeight * 0.08),
// Sync progress
Expanded(
child: state.when(
initial: () => _buildInitialState(false),
syncing: (step, progress, message) =>
_buildSyncingState(step, progress, message, false),
completed: (stats) => _buildCompletedState(stats, false),
error: (message) => _buildErrorState(message, false),
),
),
SizedBox(height: 20),
// Actions
_buildActions(state),
],
),
);
}
// Landscape layout (side by side)
Widget _buildLandscapeLayout(
DataSyncState state, double screenWidth, double screenHeight) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
child: Row(
children: [
// Left side - Header and info
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildHeader(true),
SizedBox(height: 20),
_buildActions(state),
],
),
),
SizedBox(width: 40),
// Right side - Sync progress
Expanded(
flex: 3,
child: Container(
height: screenHeight * 0.8,
child: state.when(
initial: () => _buildInitialState(true),
syncing: (step, progress, message) =>
_buildSyncingState(step, progress, message, true),
completed: (stats) => _buildCompletedState(stats, true),
error: (message) => _buildErrorState(message, true),
),
),
),
],
),
);
}
Widget _buildHeader(bool isLandscape) {
return Column(
children: [
Container(
width: isLandscape ? 60 : 80,
height: isLandscape ? 60 : 80,
decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(isLandscape ? 15 : 20),
),
child: Icon(
Icons.sync,
size: isLandscape ? 30 : 40,
color: AppColors.primary,
),
),
SizedBox(height: isLandscape ? 12 : 20),
Text(
'Sinkronisasi Data',
style: TextStyle(
fontSize: isLandscape ? 20 : 24,
fontWeight: FontWeight.bold,
color: Colors.grey.shade800,
),
),
SizedBox(height: isLandscape ? 4 : 8),
Text(
'Mengunduh data terbaru ke perangkat',
style: TextStyle(
fontSize: isLandscape ? 14 : 16,
color: Colors.grey.shade600,
),
textAlign: TextAlign.center,
),
],
);
}
Widget _buildInitialState(bool isLandscape) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.download_rounded,
size: isLandscape ? 48 : 64,
color: Colors.grey.shade400,
),
SizedBox(height: isLandscape ? 12 : 20),
Text(
'Siap untuk sinkronisasi',
style: TextStyle(
fontSize: isLandscape ? 16 : 18,
fontWeight: FontWeight.w500,
),
),
SizedBox(height: isLandscape ? 4 : 8),
Text(
'Tekan tombol mulai untuk mengunduh data',
style: TextStyle(
fontSize: isLandscape ? 12 : 14,
color: Colors.grey.shade600,
),
textAlign: TextAlign.center,
),
],
);
}
Widget _buildSyncingState(
SyncStep step, double progress, String message, bool isLandscape) {
return SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Progress circle
Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: isLandscape ? 100 : 120,
height: isLandscape ? 100 : 120,
child: AnimatedBuilder(
animation: _progressAnimation,
builder: (context, child) {
return CircularProgressIndicator(
value: _progressAnimation.value,
strokeWidth: isLandscape ? 6 : 8,
backgroundColor: Colors.grey.shade200,
valueColor:
AlwaysStoppedAnimation<Color>(AppColors.primary),
);
},
),
),
Column(
children: [
Icon(
_getSyncIcon(step),
size: isLandscape ? 24 : 32,
color: AppColors.primary,
),
SizedBox(height: 2),
AnimatedBuilder(
animation: _progressAnimation,
builder: (context, child) {
return Text(
'${(_progressAnimation.value * 100).toInt()}%',
style: TextStyle(
fontSize: isLandscape ? 14 : 16,
fontWeight: FontWeight.bold,
color: AppColors.primary,
),
);
},
),
],
),
],
),
SizedBox(height: isLandscape ? 20 : 30),
// Step indicator
_buildStepIndicator(step, isLandscape),
SizedBox(height: isLandscape ? 12 : 20),
// Current message
Container(
padding: EdgeInsets.symmetric(
horizontal: isLandscape ? 16 : 20,
vertical: isLandscape ? 8 : 12),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8),
),
child: Text(
message,
style: TextStyle(
color: Colors.blue.shade700,
fontSize: isLandscape ? 12 : 14,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
),
SizedBox(height: isLandscape ? 12 : 20),
// Sync details
_buildSyncDetails(step, progress, isLandscape),
],
),
);
}
Widget _buildStepIndicator(SyncStep currentStep, bool isLandscape) {
final steps = [
('Produk', SyncStep.products, Icons.inventory_2),
('Kategori', SyncStep.categories, Icons.category),
('Variant', SyncStep.variants, Icons.tune),
('Selesai', SyncStep.completed, Icons.check_circle),
];
if (isLandscape) {
// Vertical layout for landscape
return Column(
children: steps.map((stepData) {
final (label, step, icon) = stepData;
final isActive = step == currentStep;
final isCompleted = step.index < currentStep.index;
return Container(
margin: EdgeInsets.symmetric(vertical: 2),
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: isActive
? AppColors.primary.withOpacity(0.1)
: isCompleted
? Colors.green.shade50
: Colors.grey.shade100,
borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isCompleted ? Icons.check : icon,
size: 12,
color: isActive
? AppColors.primary
: isCompleted
? Colors.green.shade600
: Colors.grey.shade500,
),
SizedBox(width: 4),
Text(
label,
style: TextStyle(
fontSize: 10,
fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
color: isActive
? AppColors.primary
: isCompleted
? Colors.green.shade600
: Colors.grey.shade600,
),
),
],
),
);
}).toList(),
);
} else {
// Horizontal layout for portrait
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: steps.map((stepData) {
final (label, step, icon) = stepData;
final isActive = step == currentStep;
final isCompleted = step.index < currentStep.index;
return Container(
margin: EdgeInsets.symmetric(horizontal: 4),
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: isActive
? AppColors.primary.withOpacity(0.1)
: isCompleted
? Colors.green.shade50
: Colors.grey.shade100,
borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isCompleted ? Icons.check : icon,
size: 14,
color: isActive
? AppColors.primary
: isCompleted
? Colors.green.shade600
: Colors.grey.shade500,
),
SizedBox(width: 4),
Text(
label,
style: TextStyle(
fontSize: 11,
fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
color: isActive
? AppColors.primary
: isCompleted
? Colors.green.shade600
: Colors.grey.shade600,
),
),
],
),
);
}).toList(),
);
}
}
Widget _buildSyncDetails(SyncStep step, double progress, bool isLandscape) {
return Container(
padding: EdgeInsets.all(isLandscape ? 12 : 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.shade200),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Status:',
style: TextStyle(
fontSize: isLandscape ? 12 : 14,
fontWeight: FontWeight.w500,
color: Colors.grey.shade700,
),
),
Text(
_getStepLabel(step),
style: TextStyle(
fontSize: isLandscape ? 12 : 14,
fontWeight: FontWeight.w600,
color: AppColors.primary,
),
),
],
),
SizedBox(height: isLandscape ? 6 : 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Progress:',
style: TextStyle(
fontSize: isLandscape ? 12 : 14,
fontWeight: FontWeight.w500,
color: Colors.grey.shade700,
),
),
Text(
'${(progress * 100).toInt()}%',
style: TextStyle(
fontSize: isLandscape ? 12 : 14,
fontWeight: FontWeight.w600,
color: AppColors.primary,
),
),
],
),
],
),
);
}
Widget _buildCompletedState(SyncStats stats, bool isLandscape) {
return SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Success icon
Container(
width: isLandscape ? 80 : 100,
height: isLandscape ? 80 : 100,
decoration: BoxDecoration(
color: Colors.green.shade50,
borderRadius: BorderRadius.circular(isLandscape ? 40 : 50),
),
child: Icon(
Icons.check_circle,
size: isLandscape ? 48 : 60,
color: Colors.green.shade600,
),
),
SizedBox(height: isLandscape ? 20 : 30),
Text(
'Sinkronisasi Berhasil!',
style: TextStyle(
fontSize: isLandscape ? 18 : 22,
fontWeight: FontWeight.bold,
color: Colors.green.shade700,
),
),
SizedBox(height: isLandscape ? 8 : 16),
Text(
'Data berhasil diunduh ke perangkat',
style: TextStyle(
fontSize: isLandscape ? 14 : 16,
color: Colors.grey.shade600,
),
),
SizedBox(height: isLandscape ? 20 : 30),
// Stats cards
Container(
padding: EdgeInsets.all(isLandscape ? 16 : 20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200),
),
child: Column(
children: [
Text(
'Data yang Diunduh',
style: TextStyle(
fontSize: isLandscape ? 14 : 16,
fontWeight: FontWeight.w600,
color: Colors.grey.shade700,
),
),
SizedBox(height: isLandscape ? 12 : 16),
if (isLandscape)
// Vertical layout for landscape
Column(
children: [
_buildStatItem('Produk', '${stats.totalProducts}',
Icons.inventory_2, Colors.blue, isLandscape),
SizedBox(height: 8),
_buildStatItem('Kategori', '${stats.totalCategories}',
Icons.category, Colors.green, isLandscape),
SizedBox(height: 8),
_buildStatItem('Variant', '${stats.totalVariants}',
Icons.tune, Colors.orange, isLandscape),
],
)
else
// Horizontal layout for portrait
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildStatItem('Produk', '${stats.totalProducts}',
Icons.inventory_2, Colors.blue, isLandscape),
_buildStatItem('Kategori', '${stats.totalCategories}',
Icons.category, Colors.green, isLandscape),
_buildStatItem('Variant', '${stats.totalVariants}',
Icons.tune, Colors.orange, isLandscape),
],
),
],
),
),
SizedBox(height: isLandscape ? 12 : 20),
Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(6),
),
child: Text(
'Mengalihkan ke halaman utama...',
style: TextStyle(
color: Colors.grey.shade600,
fontSize: isLandscape ? 10 : 12,
),
),
),
],
),
);
}
Widget _buildErrorState(String message, bool isLandscape) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
size: isLandscape ? 48 : 64,
color: Colors.red.shade400,
),
SizedBox(height: isLandscape ? 12 : 20),
Text(
'Sinkronisasi Gagal',
style: TextStyle(
fontSize: isLandscape ? 16 : 20,
fontWeight: FontWeight.bold,
color: Colors.red.shade600,
),
),
SizedBox(height: isLandscape ? 8 : 12),
Container(
padding: EdgeInsets.all(isLandscape ? 12 : 16),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(8),
),
child: Text(
message,
style: TextStyle(
fontSize: isLandscape ? 12 : 14,
color: Colors.red.shade700,
),
textAlign: TextAlign.center,
),
),
SizedBox(height: isLandscape ? 12 : 20),
Text(
'Periksa koneksi internet dan coba lagi',
style: TextStyle(
fontSize: isLandscape ? 12 : 14,
color: Colors.grey.shade600,
),
textAlign: TextAlign.center,
),
],
);
}
Widget _buildStatItem(String label, String value, IconData icon, Color color,
bool isLandscape) {
if (isLandscape) {
// Horizontal layout for landscape
return Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 20, color: color),
SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
value,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: color,
),
),
Text(
label,
style: TextStyle(
fontSize: 10,
color: Colors.grey.shade600,
),
),
],
),
],
),
);
} else {
// Vertical layout for portrait
return Column(
children: [
Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, size: 24, color: color),
),
SizedBox(height: 8),
Text(
value,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: color,
),
),
Text(
label,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
);
}
}
Widget _buildActions(DataSyncState state) {
return state.when(
initial: () => Button.filled(
onPressed: () {
context.read<DataSyncBloc>().add(const DataSyncEvent.startSync());
},
label: 'Mulai Sinkronisasi',
),
syncing: (step, progress, message) => Button.outlined(
onPressed: () {
context.read<DataSyncBloc>().add(const DataSyncEvent.cancelSync());
},
label: 'Batalkan',
),
completed: (stats) => Button.filled(
onPressed: () {
context.pushReplacement(DashboardPage());
},
label: 'Lanjutkan ke Aplikasi',
),
error: (message) => Row(
children: [
Expanded(
child: Button.outlined(
onPressed: () {
context.pushReplacement(DashboardPage());
},
label: 'Lewati',
),
),
SizedBox(width: 16),
Expanded(
child: Button.filled(
onPressed: () {
context
.read<DataSyncBloc>()
.add(const DataSyncEvent.startSync());
},
label: 'Coba Lagi',
),
),
],
),
);
}
IconData _getSyncIcon(SyncStep step) {
switch (step) {
case SyncStep.products:
return Icons.inventory_2;
case SyncStep.categories:
return Icons.category;
case SyncStep.variants:
return Icons.tune;
case SyncStep.completed:
return Icons.check_circle;
}
}
String _getStepLabel(SyncStep step) {
switch (step) {
case SyncStep.products:
return 'Mengunduh Produk';
case SyncStep.categories:
return 'Mengunduh Kategori';
case SyncStep.variants:
return 'Mengunduh Variant';
case SyncStep.completed:
return 'Selesai';
}
}
}
@@ -131,7 +131,7 @@ class CheckoutBloc extends Bloc<CheckoutEvent, CheckoutState> {
final serviceCharge = await settingsLocalDatasource.getServiceCharge(); final serviceCharge = await settingsLocalDatasource.getServiceCharge();
emit(_Loaded( emit(_Loaded(
event.items, [],
null, null,
0, 0,
0, 0,
@@ -18,7 +18,7 @@ final _privateConstructorUsedError = UnsupportedError(
mixin _$CheckoutEvent { mixin _$CheckoutEvent {
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started, required TResult Function() started,
required TResult Function(Product product, ProductVariant? variant) addItem, required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant) required TResult Function(Product product, ProductVariant? variant)
removeItem, removeItem,
@@ -41,7 +41,7 @@ mixin _$CheckoutEvent {
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started, TResult? Function()? started,
TResult? Function(Product product, ProductVariant? variant)? addItem, TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem, TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Product product, ProductVariant? variant)? deleteItem,
@@ -61,7 +61,7 @@ mixin _$CheckoutEvent {
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started, TResult Function()? started,
TResult Function(Product product, ProductVariant? variant)? addItem, TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem, TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Product product, ProductVariant? variant)? deleteItem,
@@ -166,8 +166,6 @@ abstract class _$$StartedImplCopyWith<$Res> {
factory _$$StartedImplCopyWith( factory _$$StartedImplCopyWith(
_$StartedImpl value, $Res Function(_$StartedImpl) then) = _$StartedImpl value, $Res Function(_$StartedImpl) then) =
__$$StartedImplCopyWithImpl<$Res>; __$$StartedImplCopyWithImpl<$Res>;
@useResult
$Res call({List<ProductQuantity> items});
} }
/// @nodoc /// @nodoc
@@ -180,62 +178,31 @@ class __$$StartedImplCopyWithImpl<$Res>
/// Create a copy of CheckoutEvent /// Create a copy of CheckoutEvent
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? items = null,
}) {
return _then(_$StartedImpl(
null == items
? _value._items
: items // ignore: cast_nullable_to_non_nullable
as List<ProductQuantity>,
));
}
} }
/// @nodoc /// @nodoc
class _$StartedImpl implements _Started { class _$StartedImpl implements _Started {
const _$StartedImpl(final List<ProductQuantity> items) : _items = items; const _$StartedImpl();
final List<ProductQuantity> _items;
@override
List<ProductQuantity> get items {
if (_items is EqualUnmodifiableListView) return _items;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_items);
}
@override @override
String toString() { String toString() {
return 'CheckoutEvent.started(items: $items)'; return 'CheckoutEvent.started()';
} }
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType && other is _$StartedImpl);
other is _$StartedImpl &&
const DeepCollectionEquality().equals(other._items, _items));
} }
@override @override
int get hashCode => int get hashCode => runtimeType.hashCode;
Object.hash(runtimeType, const DeepCollectionEquality().hash(_items));
/// Create a copy of CheckoutEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$StartedImplCopyWith<_$StartedImpl> get copyWith =>
__$$StartedImplCopyWithImpl<_$StartedImpl>(this, _$identity);
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started, required TResult Function() started,
required TResult Function(Product product, ProductVariant? variant) addItem, required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant) required TResult Function(Product product, ProductVariant? variant)
removeItem, removeItem,
@@ -255,13 +222,13 @@ class _$StartedImpl implements _Started {
required TResult Function(DraftOrderModel data) loadDraftOrder, required TResult Function(DraftOrderModel data) loadDraftOrder,
required TResult Function(DeliveryModel delivery) updateDeliveryType, required TResult Function(DeliveryModel delivery) updateDeliveryType,
}) { }) {
return started(items); return started();
} }
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started, TResult? Function()? started,
TResult? Function(Product product, ProductVariant? variant)? addItem, TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem, TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Product product, ProductVariant? variant)? deleteItem,
@@ -278,13 +245,13 @@ class _$StartedImpl implements _Started {
TResult? Function(DraftOrderModel data)? loadDraftOrder, TResult? Function(DraftOrderModel data)? loadDraftOrder,
TResult? Function(DeliveryModel delivery)? updateDeliveryType, TResult? Function(DeliveryModel delivery)? updateDeliveryType,
}) { }) {
return started?.call(items); return started?.call();
} }
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started, TResult Function()? started,
TResult Function(Product product, ProductVariant? variant)? addItem, TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem, TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Product product, ProductVariant? variant)? deleteItem,
@@ -303,7 +270,7 @@ class _$StartedImpl implements _Started {
required TResult orElse(), required TResult orElse(),
}) { }) {
if (started != null) { if (started != null) {
return started(items); return started();
} }
return orElse(); return orElse();
} }
@@ -380,15 +347,7 @@ class _$StartedImpl implements _Started {
} }
abstract class _Started implements CheckoutEvent { abstract class _Started implements CheckoutEvent {
const factory _Started(final List<ProductQuantity> items) = _$StartedImpl; const factory _Started() = _$StartedImpl;
List<ProductQuantity> get items;
/// Create a copy of CheckoutEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$StartedImplCopyWith<_$StartedImpl> get copyWith =>
throw _privateConstructorUsedError;
} }
/// @nodoc /// @nodoc
@@ -467,7 +426,7 @@ class _$AddItemImpl implements _AddItem {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started, required TResult Function() started,
required TResult Function(Product product, ProductVariant? variant) addItem, required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant) required TResult Function(Product product, ProductVariant? variant)
removeItem, removeItem,
@@ -493,7 +452,7 @@ class _$AddItemImpl implements _AddItem {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started, TResult? Function()? started,
TResult? Function(Product product, ProductVariant? variant)? addItem, TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem, TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Product product, ProductVariant? variant)? deleteItem,
@@ -516,7 +475,7 @@ class _$AddItemImpl implements _AddItem {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started, TResult Function()? started,
TResult Function(Product product, ProductVariant? variant)? addItem, TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem, TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Product product, ProductVariant? variant)? deleteItem,
@@ -701,7 +660,7 @@ class _$RemoveItemImpl implements _RemoveItem {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started, required TResult Function() started,
required TResult Function(Product product, ProductVariant? variant) addItem, required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant) required TResult Function(Product product, ProductVariant? variant)
removeItem, removeItem,
@@ -727,7 +686,7 @@ class _$RemoveItemImpl implements _RemoveItem {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started, TResult? Function()? started,
TResult? Function(Product product, ProductVariant? variant)? addItem, TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem, TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Product product, ProductVariant? variant)? deleteItem,
@@ -750,7 +709,7 @@ class _$RemoveItemImpl implements _RemoveItem {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started, TResult Function()? started,
TResult Function(Product product, ProductVariant? variant)? addItem, TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem, TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Product product, ProductVariant? variant)? deleteItem,
@@ -935,7 +894,7 @@ class _$DeleteItemImpl implements _DeleteItem {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started, required TResult Function() started,
required TResult Function(Product product, ProductVariant? variant) addItem, required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant) required TResult Function(Product product, ProductVariant? variant)
removeItem, removeItem,
@@ -961,7 +920,7 @@ class _$DeleteItemImpl implements _DeleteItem {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started, TResult? Function()? started,
TResult? Function(Product product, ProductVariant? variant)? addItem, TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem, TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Product product, ProductVariant? variant)? deleteItem,
@@ -984,7 +943,7 @@ class _$DeleteItemImpl implements _DeleteItem {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started, TResult Function()? started,
TResult Function(Product product, ProductVariant? variant)? addItem, TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem, TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Product product, ProductVariant? variant)? deleteItem,
@@ -1162,7 +1121,7 @@ class _$AddDiscountImpl implements _AddDiscount {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started, required TResult Function() started,
required TResult Function(Product product, ProductVariant? variant) addItem, required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant) required TResult Function(Product product, ProductVariant? variant)
removeItem, removeItem,
@@ -1188,7 +1147,7 @@ class _$AddDiscountImpl implements _AddDiscount {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started, TResult? Function()? started,
TResult? Function(Product product, ProductVariant? variant)? addItem, TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem, TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Product product, ProductVariant? variant)? deleteItem,
@@ -1211,7 +1170,7 @@ class _$AddDiscountImpl implements _AddDiscount {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started, TResult Function()? started,
TResult Function(Product product, ProductVariant? variant)? addItem, TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem, TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Product product, ProductVariant? variant)? deleteItem,
@@ -1359,7 +1318,7 @@ class _$RemoveDiscountImpl implements _RemoveDiscount {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started, required TResult Function() started,
required TResult Function(Product product, ProductVariant? variant) addItem, required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant) required TResult Function(Product product, ProductVariant? variant)
removeItem, removeItem,
@@ -1385,7 +1344,7 @@ class _$RemoveDiscountImpl implements _RemoveDiscount {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started, TResult? Function()? started,
TResult? Function(Product product, ProductVariant? variant)? addItem, TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem, TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Product product, ProductVariant? variant)? deleteItem,
@@ -1408,7 +1367,7 @@ class _$RemoveDiscountImpl implements _RemoveDiscount {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started, TResult Function()? started,
TResult Function(Product product, ProductVariant? variant)? addItem, TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem, TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Product product, ProductVariant? variant)? deleteItem,
@@ -1575,7 +1534,7 @@ class _$AddTaxImpl implements _AddTax {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started, required TResult Function() started,
required TResult Function(Product product, ProductVariant? variant) addItem, required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant) required TResult Function(Product product, ProductVariant? variant)
removeItem, removeItem,
@@ -1601,7 +1560,7 @@ class _$AddTaxImpl implements _AddTax {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started, TResult? Function()? started,
TResult? Function(Product product, ProductVariant? variant)? addItem, TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem, TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Product product, ProductVariant? variant)? deleteItem,
@@ -1624,7 +1583,7 @@ class _$AddTaxImpl implements _AddTax {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started, TResult Function()? started,
TResult Function(Product product, ProductVariant? variant)? addItem, TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem, TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Product product, ProductVariant? variant)? deleteItem,
@@ -1801,7 +1760,7 @@ class _$AddServiceChargeImpl implements _AddServiceCharge {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started, required TResult Function() started,
required TResult Function(Product product, ProductVariant? variant) addItem, required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant) required TResult Function(Product product, ProductVariant? variant)
removeItem, removeItem,
@@ -1827,7 +1786,7 @@ class _$AddServiceChargeImpl implements _AddServiceCharge {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started, TResult? Function()? started,
TResult? Function(Product product, ProductVariant? variant)? addItem, TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem, TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Product product, ProductVariant? variant)? deleteItem,
@@ -1850,7 +1809,7 @@ class _$AddServiceChargeImpl implements _AddServiceCharge {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started, TResult Function()? started,
TResult Function(Product product, ProductVariant? variant)? addItem, TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem, TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Product product, ProductVariant? variant)? deleteItem,
@@ -1999,7 +1958,7 @@ class _$RemoveTaxImpl implements _RemoveTax {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started, required TResult Function() started,
required TResult Function(Product product, ProductVariant? variant) addItem, required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant) required TResult Function(Product product, ProductVariant? variant)
removeItem, removeItem,
@@ -2025,7 +1984,7 @@ class _$RemoveTaxImpl implements _RemoveTax {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started, TResult? Function()? started,
TResult? Function(Product product, ProductVariant? variant)? addItem, TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem, TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Product product, ProductVariant? variant)? deleteItem,
@@ -2048,7 +2007,7 @@ class _$RemoveTaxImpl implements _RemoveTax {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started, TResult Function()? started,
TResult Function(Product product, ProductVariant? variant)? addItem, TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem, TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Product product, ProductVariant? variant)? deleteItem,
@@ -2189,7 +2148,7 @@ class _$RemoveServiceChargeImpl implements _RemoveServiceCharge {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started, required TResult Function() started,
required TResult Function(Product product, ProductVariant? variant) addItem, required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant) required TResult Function(Product product, ProductVariant? variant)
removeItem, removeItem,
@@ -2215,7 +2174,7 @@ class _$RemoveServiceChargeImpl implements _RemoveServiceCharge {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started, TResult? Function()? started,
TResult? Function(Product product, ProductVariant? variant)? addItem, TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem, TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Product product, ProductVariant? variant)? deleteItem,
@@ -2238,7 +2197,7 @@ class _$RemoveServiceChargeImpl implements _RemoveServiceCharge {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started, TResult Function()? started,
TResult Function(Product product, ProductVariant? variant)? addItem, TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem, TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Product product, ProductVariant? variant)? deleteItem,
@@ -2407,7 +2366,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started, required TResult Function() started,
required TResult Function(Product product, ProductVariant? variant) addItem, required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant) required TResult Function(Product product, ProductVariant? variant)
removeItem, removeItem,
@@ -2433,7 +2392,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started, TResult? Function()? started,
TResult? Function(Product product, ProductVariant? variant)? addItem, TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem, TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Product product, ProductVariant? variant)? deleteItem,
@@ -2456,7 +2415,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started, TResult Function()? started,
TResult Function(Product product, ProductVariant? variant)? addItem, TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem, TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Product product, ProductVariant? variant)? deleteItem,
@@ -2641,7 +2600,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started, required TResult Function() started,
required TResult Function(Product product, ProductVariant? variant) addItem, required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant) required TResult Function(Product product, ProductVariant? variant)
removeItem, removeItem,
@@ -2667,7 +2626,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started, TResult? Function()? started,
TResult? Function(Product product, ProductVariant? variant)? addItem, TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem, TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Product product, ProductVariant? variant)? deleteItem,
@@ -2690,7 +2649,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started, TResult Function()? started,
TResult Function(Product product, ProductVariant? variant)? addItem, TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem, TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Product product, ProductVariant? variant)? deleteItem,
@@ -2889,7 +2848,7 @@ class _$SaveDraftOrderImpl implements _SaveDraftOrder {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started, required TResult Function() started,
required TResult Function(Product product, ProductVariant? variant) addItem, required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant) required TResult Function(Product product, ProductVariant? variant)
removeItem, removeItem,
@@ -2915,7 +2874,7 @@ class _$SaveDraftOrderImpl implements _SaveDraftOrder {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started, TResult? Function()? started,
TResult? Function(Product product, ProductVariant? variant)? addItem, TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem, TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Product product, ProductVariant? variant)? deleteItem,
@@ -2938,7 +2897,7 @@ class _$SaveDraftOrderImpl implements _SaveDraftOrder {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started, TResult Function()? started,
TResult Function(Product product, ProductVariant? variant)? addItem, TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem, TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Product product, ProductVariant? variant)? deleteItem,
@@ -3117,7 +3076,7 @@ class _$LoadDraftOrderImpl implements _LoadDraftOrder {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started, required TResult Function() started,
required TResult Function(Product product, ProductVariant? variant) addItem, required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant) required TResult Function(Product product, ProductVariant? variant)
removeItem, removeItem,
@@ -3143,7 +3102,7 @@ class _$LoadDraftOrderImpl implements _LoadDraftOrder {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started, TResult? Function()? started,
TResult? Function(Product product, ProductVariant? variant)? addItem, TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem, TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Product product, ProductVariant? variant)? deleteItem,
@@ -3166,7 +3125,7 @@ class _$LoadDraftOrderImpl implements _LoadDraftOrder {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started, TResult Function()? started,
TResult Function(Product product, ProductVariant? variant)? addItem, TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem, TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Product product, ProductVariant? variant)? deleteItem,
@@ -3344,7 +3303,7 @@ class _$UpdateDeliveryTypeImpl implements _UpdateDeliveryType {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function(List<ProductQuantity> items) started, required TResult Function() started,
required TResult Function(Product product, ProductVariant? variant) addItem, required TResult Function(Product product, ProductVariant? variant) addItem,
required TResult Function(Product product, ProductVariant? variant) required TResult Function(Product product, ProductVariant? variant)
removeItem, removeItem,
@@ -3370,7 +3329,7 @@ class _$UpdateDeliveryTypeImpl implements _UpdateDeliveryType {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function(List<ProductQuantity> items)? started, TResult? Function()? started,
TResult? Function(Product product, ProductVariant? variant)? addItem, TResult? Function(Product product, ProductVariant? variant)? addItem,
TResult? Function(Product product, ProductVariant? variant)? removeItem, TResult? Function(Product product, ProductVariant? variant)? removeItem,
TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Product product, ProductVariant? variant)? deleteItem,
@@ -3393,7 +3352,7 @@ class _$UpdateDeliveryTypeImpl implements _UpdateDeliveryType {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function(List<ProductQuantity> items)? started, TResult Function()? started,
TResult Function(Product product, ProductVariant? variant)? addItem, TResult Function(Product product, ProductVariant? variant)? addItem,
TResult Function(Product product, ProductVariant? variant)? removeItem, TResult Function(Product product, ProductVariant? variant)? removeItem,
TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Product product, ProductVariant? variant)? deleteItem,
@@ -2,7 +2,7 @@ part of 'checkout_bloc.dart';
@freezed @freezed
class CheckoutEvent with _$CheckoutEvent { class CheckoutEvent with _$CheckoutEvent {
const factory CheckoutEvent.started(List<ProductQuantity> items) = _Started; const factory CheckoutEvent.started() = _Started;
//add item //add item
const factory CheckoutEvent.addItem( const factory CheckoutEvent.addItem(
Product product, ProductVariant? variant) = _AddItem; Product product, ProductVariant? variant) = _AddItem;
@@ -1,8 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'package:bloc/bloc.dart';
import 'package:enaklo_pos/data/datasources/product_remote_datasource.dart';
import 'package:enaklo_pos/data/models/response/product_response_model.dart'; import 'package:enaklo_pos/data/models/response/product_response_model.dart';
import 'package:enaklo_pos/data/repositories/product/product_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
part 'product_loader_event.dart'; part 'product_loader_event.dart';
@@ -10,114 +10,102 @@ part 'product_loader_state.dart';
part 'product_loader_bloc.freezed.dart'; part 'product_loader_bloc.freezed.dart';
class ProductLoaderBloc extends Bloc<ProductLoaderEvent, ProductLoaderState> { class ProductLoaderBloc extends Bloc<ProductLoaderEvent, ProductLoaderState> {
final ProductRepository _productRepository = ProductRepository.instance; final ProductRemoteDatasource _productRemoteDatasource;
// Debouncing untuk mencegah multiple load more calls
Timer? _loadMoreDebounce; Timer? _loadMoreDebounce;
Timer? _searchDebounce;
bool _isLoadingMore = false; bool _isLoadingMore = false;
ProductLoaderBloc() : super(const ProductLoaderState.initial()) { ProductLoaderBloc(this._productRemoteDatasource)
: super(ProductLoaderState.initial()) {
on<_GetProduct>(_onGetProduct); on<_GetProduct>(_onGetProduct);
on<_LoadMore>(_onLoadMore); on<_LoadMore>(_onLoadMore);
on<_Refresh>(_onRefresh); on<_Refresh>(_onRefresh);
on<_SearchProduct>(_onSearchProduct);
on<_GetDatabaseStats>(_onGetDatabaseStats);
on<_ClearCache>(_onClearCache);
} }
@override @override
Future<void> close() { Future<void> close() {
_loadMoreDebounce?.cancel(); _loadMoreDebounce?.cancel();
_searchDebounce?.cancel();
return super.close(); return super.close();
} }
// Pure local product loading // Debounce transformer untuk load more
// EventTransformer<T> _debounceTransformer<T>() {
// return (events, mapper) {
// return events
// .debounceTime(const Duration(milliseconds: 300))
// .asyncExpand(mapper);
// };
// }
// Initial load
Future<void> _onGetProduct( Future<void> _onGetProduct(
_GetProduct event, _GetProduct event,
Emitter<ProductLoaderState> emit, Emitter<ProductLoaderState> emit,
) async { ) async {
emit(const ProductLoaderState.loading()); emit(const _Loading());
_isLoadingMore = false; _isLoadingMore = false; // Reset loading state
log('📱 Loading local products - categoryId: ${event.categoryId}'); final result = await _productRemoteDatasource.getProducts(
// Check if local database is ready
final isReady = await _productRepository.isLocalDatabaseReady();
if (!isReady) {
emit(const ProductLoaderState.error(
'Database lokal belum siap. Silakan lakukan sinkronisasi data terlebih dahulu.'));
return;
}
final result = await _productRepository.getProducts(
page: 1, page: 1,
limit: 10, limit: 10,
categoryId: event.categoryId, categoryId: event.categoryId,
search: event.search,
); );
await result.fold( await result.fold(
(failure) async { (failure) async => emit(_Error(failure)),
log('❌ Error loading local products: $failure');
emit(ProductLoaderState.error(failure));
},
(response) async { (response) async {
final products = response.data?.products ?? []; final products = response.data?.products ?? [];
final totalPages = response.data?.totalPages ?? 1; final hasReachedMax = products.length < 10;
final hasReachedMax = products.length < 10 || 1 >= totalPages;
log('âś… Local products loaded: ${products.length}, hasReachedMax: $hasReachedMax, totalPages: $totalPages'); emit(_Loaded(
emit(ProductLoaderState.loaded(
products: products, products: products,
hasReachedMax: hasReachedMax, hasReachedMax: hasReachedMax,
currentPage: 1, currentPage: 1,
isLoadingMore: false, isLoadingMore: false,
categoryId: event.categoryId,
searchQuery: event.search,
)); ));
}, },
); );
} }
// Pure local load more // Load more with enhanced debouncing
Future<void> _onLoadMore( Future<void> _onLoadMore(
_LoadMore event, _LoadMore event,
Emitter<ProductLoaderState> emit, Emitter<ProductLoaderState> emit,
) async { ) async {
final currentState = state; final currentState = state;
// Enhanced validation
if (currentState is! _Loaded || if (currentState is! _Loaded ||
currentState.hasReachedMax || currentState.hasReachedMax ||
_isLoadingMore || _isLoadingMore ||
currentState.isLoadingMore) { currentState.isLoadingMore) {
log('⏹️ Load more blocked - state: ${currentState.runtimeType}, isLoadingMore: $_isLoadingMore');
return; return;
} }
_isLoadingMore = true; _isLoadingMore = true;
// Emit loading more state
emit(currentState.copyWith(isLoadingMore: true)); emit(currentState.copyWith(isLoadingMore: true));
final nextPage = currentState.currentPage + 1; final nextPage = currentState.currentPage + 1;
log('đź“„ Loading more local products - page: $nextPage');
try { try {
final result = await _productRepository.getProducts( final result = await _productRemoteDatasource.getProducts(
page: nextPage, page: nextPage,
limit: 10, limit: 10,
categoryId: currentState.categoryId, categoryId: event.categoryId,
search: currentState.searchQuery,
); );
await result.fold( await result.fold(
(failure) async { (failure) async {
log('❌ Error loading more local products: $failure'); // On error, revert loading state but don't show error
// Just silently fail and allow retry
emit(currentState.copyWith(isLoadingMore: false)); emit(currentState.copyWith(isLoadingMore: false));
_isLoadingMore = false;
}, },
(response) async { (response) async {
final newProducts = response.data?.products ?? []; final newProducts = response.data?.products ?? [];
final totalPages = response.data?.totalPages ?? 1;
// Prevent duplicate products // Prevent duplicate products
final currentProductIds = final currentProductIds =
@@ -129,130 +117,32 @@ class ProductLoaderBloc extends Bloc<ProductLoaderEvent, ProductLoaderState> {
final allProducts = List<Product>.from(currentState.products) final allProducts = List<Product>.from(currentState.products)
..addAll(filteredNewProducts); ..addAll(filteredNewProducts);
final hasReachedMax = final hasReachedMax = newProducts.length < 10;
newProducts.length < 10 || nextPage >= totalPages;
log('âś… More local products loaded: ${filteredNewProducts.length} new, total: ${allProducts.length}'); emit(_Loaded(
emit(ProductLoaderState.loaded(
products: allProducts, products: allProducts,
hasReachedMax: hasReachedMax, hasReachedMax: hasReachedMax,
currentPage: nextPage, currentPage: nextPage,
isLoadingMore: false, isLoadingMore: false,
categoryId: currentState.categoryId,
searchQuery: currentState.searchQuery,
)); ));
_isLoadingMore = false;
}, },
); );
} catch (e) { } catch (e) {
log('❌ Exception loading more local products: $e'); // Handle unexpected errors
emit(currentState.copyWith(isLoadingMore: false)); emit(currentState.copyWith(isLoadingMore: false));
} finally {
_isLoadingMore = false; _isLoadingMore = false;
} }
} }
// Pure local refresh // Refresh data
Future<void> _onRefresh( Future<void> _onRefresh(
_Refresh event, _Refresh event,
Emitter<ProductLoaderState> emit, Emitter<ProductLoaderState> emit,
) async { ) async {
final currentState = state;
String? categoryId;
String? searchQuery;
if (currentState is _Loaded) {
categoryId = currentState.categoryId;
searchQuery = currentState.searchQuery;
}
_isLoadingMore = false; _isLoadingMore = false;
_loadMoreDebounce?.cancel(); _loadMoreDebounce?.cancel();
_searchDebounce?.cancel(); add(const _GetProduct());
log('🔄 Refreshing local products');
// Clear local cache
_productRepository.clearCache();
add(ProductLoaderEvent.getProduct(
categoryId: categoryId,
search: searchQuery,
));
}
// Fast local search (no debouncing needed for local data)
Future<void> _onSearchProduct(
_SearchProduct event,
Emitter<ProductLoaderState> emit,
) async {
// Cancel previous search
_searchDebounce?.cancel();
// Minimal debounce for local search (much faster)
_searchDebounce = Timer(Duration(milliseconds: 150), () async {
emit(const ProductLoaderState.loading());
_isLoadingMore = false;
log('🔍 Local search: "${event.query}"');
final result = await _productRepository.getProducts(
page: 1,
limit: 20, // More results for search
categoryId: event.categoryId,
search: event.query,
);
await result.fold(
(failure) async {
log('❌ Local search error: $failure');
emit(ProductLoaderState.error(failure));
},
(response) async {
final products = response.data?.products ?? [];
final totalPages = response.data?.totalPages ?? 1;
final hasReachedMax = products.length < 20 || 1 >= totalPages;
log('âś… Local search results: ${products.length} products found');
emit(ProductLoaderState.loaded(
products: products,
hasReachedMax: hasReachedMax,
currentPage: 1,
isLoadingMore: false,
categoryId: event.categoryId,
searchQuery: event.query,
));
},
);
});
}
// Get local database statistics
Future<void> _onGetDatabaseStats(
_GetDatabaseStats event,
Emitter<ProductLoaderState> emit,
) async {
try {
final stats = await _productRepository.getDatabaseStats();
log('📊 Local database stats retrieved: $stats');
// You can emit a special state here if needed for UI updates
// For now, just log the stats
} catch (e) {
log('❌ Error getting local database stats: $e');
}
}
// Clear local cache
Future<void> _onClearCache(
_ClearCache event,
Emitter<ProductLoaderState> emit,
) async {
log('đź§ą Manually clearing local cache');
_productRepository.clearCache();
// Refresh current data after cache clear
add(const ProductLoaderEvent.refresh());
} }
} }
File diff suppressed because it is too large Load Diff
@@ -2,25 +2,9 @@ part of 'product_loader_bloc.dart';
@freezed @freezed
class ProductLoaderEvent with _$ProductLoaderEvent { class ProductLoaderEvent with _$ProductLoaderEvent {
const factory ProductLoaderEvent.getProduct({ const factory ProductLoaderEvent.getProduct(
String? categoryId, {String? categoryId, String? search}) = _GetProduct;
String? search, // Added search parameter const factory ProductLoaderEvent.loadMore(
bool? forceRefresh, // Kept for compatibility but ignored {String? categoryId, String? search}) = _LoadMore;
}) = _GetProduct;
const factory ProductLoaderEvent.loadMore({
String? categoryId,
String? search,
}) = _LoadMore;
const factory ProductLoaderEvent.refresh() = _Refresh; const factory ProductLoaderEvent.refresh() = _Refresh;
const factory ProductLoaderEvent.searchProduct({
String? query,
String? categoryId,
}) = _SearchProduct;
const factory ProductLoaderEvent.getDatabaseStats() = _GetDatabaseStats;
const factory ProductLoaderEvent.clearCache() = _ClearCache;
} }
@@ -9,8 +9,6 @@ class ProductLoaderState with _$ProductLoaderState {
required bool hasReachedMax, required bool hasReachedMax,
required int currentPage, required int currentPage,
required bool isLoadingMore, required bool isLoadingMore,
String? categoryId,
String? searchQuery,
}) = _Loaded; }) = _Loaded;
const factory ProductLoaderState.error(String message) = _Error; const factory ProductLoaderState.error(String message) = _Error;
} }
@@ -12,7 +12,7 @@ import 'package:intl/intl.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/presentation/home/bloc/qris/qris_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/qris/qris_bloc.dart';
// import 'package:enaklo_pos/presentation/home/widgets/success_payment_dialog.dart'; import 'package:enaklo_pos/presentation/home/widgets/success_payment_dialog.dart';
import 'package:widgets_to_image/widgets_to_image.dart'; import 'package:widgets_to_image/widgets_to_image.dart';
import 'package:enaklo_pos/core/utils/printer_service.dart'; import 'package:enaklo_pos/core/utils/printer_service.dart';
@@ -124,36 +124,36 @@ class _PaymentQrisDialogState extends State<PaymentQrisDialog> {
)); ));
}); });
}, success: (message) async { }, success: (message) async {
// context.read<OrderBloc>().add(OrderEvent.order( context.read<OrderBloc>().add(OrderEvent.order(
// widget.items, widget.items,
// widget.discount, widget.discount,
// widget.discountAmount, widget.discountAmount,
// widget.tax, widget.tax,
// widget.serviceCharge, widget.serviceCharge,
// widget.paymentAmount, widget.paymentAmount,
// widget.customerName, widget.customerName,
// widget.tableNumber, widget.tableNumber,
// 'completed', 'completed',
// 'paid', 'paid',
// 'Qris', 'Qris',
// widget.price, widget.price,
// OrderType.dineIn)); OrderType.dineIn));
// await showDialog( await showDialog(
// context: context, context: context,
// barrierDismissible: false, barrierDismissible: false,
// builder: (context) => SuccessPaymentDialog( builder: (context) => SuccessPaymentDialog(
// isTablePaymentPage: widget.isTablePaymentPage, isTablePaymentPage: widget.isTablePaymentPage,
// data: widget.items, data: widget.items,
// totalQty: widget.totalQty, totalQty: widget.totalQty,
// totalPrice: widget.price, totalPrice: widget.price,
// totalTax: widget.tax, totalTax: widget.tax,
// totalDiscount: widget.discountAmount, totalDiscount: widget.discountAmount,
// subTotal: widget.subTotal, subTotal: widget.subTotal,
// normalPrice: widget.price, normalPrice: widget.price,
// totalService: widget.serviceCharge, totalService: widget.serviceCharge,
// draftName: widget.customerName, draftName: widget.customerName,
// ), ),
// ); );
}); });
}, },
child: BlocBuilder<QrisBloc, QrisState>( child: BlocBuilder<QrisBloc, QrisState>(
@@ -13,7 +13,7 @@ import 'package:enaklo_pos/presentation/home/bloc/order/order_bloc.dart';
import 'package:enaklo_pos/presentation/home/bloc/payment_methods/payment_methods_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/payment_methods/payment_methods_bloc.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import 'package:enaklo_pos/presentation/home/models/order_type.dart'; import 'package:enaklo_pos/presentation/home/models/order_type.dart';
// import 'package:enaklo_pos/presentation/home/widgets/save_order_dialog.dart'; import 'package:enaklo_pos/presentation/home/widgets/save_order_dialog.dart';
import 'package:enaklo_pos/data/models/response/payment_methods_response_model.dart'; import 'package:enaklo_pos/data/models/response/payment_methods_response_model.dart';
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
@@ -4,7 +4,6 @@ import 'dart:developer';
import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:enaklo_pos/presentation/customer/pages/customer_page.dart'; import 'package:enaklo_pos/presentation/customer/pages/customer_page.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import 'package:enaklo_pos/presentation/setting/pages/setting_page.dart'; import 'package:enaklo_pos/presentation/setting/pages/setting_page.dart';
import 'package:enaklo_pos/presentation/table/pages/table_page.dart'; import 'package:enaklo_pos/presentation/table/pages/table_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -27,13 +26,10 @@ import 'home_page.dart';
class DashboardPage extends StatefulWidget { class DashboardPage extends StatefulWidget {
final int? index; final int? index;
final TableModel? table; final TableModel? table;
final List<ProductQuantity>? items;
const DashboardPage({ const DashboardPage({
super.key, super.key,
this.index = 0, this.index = 0,
this.table, this.table,
this.items,
}); });
@override @override
@@ -60,11 +56,8 @@ class _DashboardPageState extends State<DashboardPage> {
HomePage( HomePage(
isTable: false, isTable: false,
table: widget.table, table: widget.table,
items: widget.items ?? [],
),
TablePage(
items: widget.items ?? [],
), ),
const TablePage(),
const ReportPage(), const ReportPage(),
const CustomerPage(), const CustomerPage(),
const SettingPage(), const SettingPage(),
+188 -584
View File
@@ -1,16 +1,9 @@
// ======================================== // ignore_for_file: public_member_api_docs, sort_constructors_first
// OFFLINE-ONLY HOMEPAGE - NO API CALLS
// lib/presentation/home/pages/home_page.dart
// ========================================
import 'dart:developer';
import 'package:enaklo_pos/core/components/flushbar.dart'; import 'package:enaklo_pos/core/components/flushbar.dart';
import 'package:enaklo_pos/data/datasources/product/product_local_datasource.dart';
import 'package:enaklo_pos/presentation/home/bloc/category_loader/category_loader_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/category_loader/category_loader_bloc.dart';
import 'package:enaklo_pos/presentation/home/bloc/current_outlet/current_outlet_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/current_outlet/current_outlet_bloc.dart';
import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart';
import 'package:enaklo_pos/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import 'package:enaklo_pos/presentation/home/widgets/category_tab_bar.dart'; import 'package:enaklo_pos/presentation/home/widgets/category_tab_bar.dart';
import 'package:enaklo_pos/presentation/home/widgets/home_right_title.dart'; import 'package:enaklo_pos/presentation/home/widgets/home_right_title.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -20,6 +13,7 @@ import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart'; import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/data/models/response/table_model.dart'; import 'package:enaklo_pos/data/models/response/table_model.dart';
import 'package:enaklo_pos/presentation/home/pages/confirm_payment_page.dart'; import 'package:enaklo_pos/presentation/home/pages/confirm_payment_page.dart';
import 'package:enaklo_pos/data/models/response/product_response_model.dart';
import '../../../core/assets/assets.gen.dart'; import '../../../core/assets/assets.gen.dart';
import '../../../core/components/buttons.dart'; import '../../../core/components/buttons.dart';
@@ -33,12 +27,10 @@ import '../widgets/product_card.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
final bool isTable; final bool isTable;
final TableModel? table; final TableModel? table;
final List<ProductQuantity> items;
const HomePage({ const HomePage({
super.key, super.key,
required this.isTable, required this.isTable,
this.table, this.table,
required this.items,
}); });
@override @override
@@ -50,68 +42,46 @@ class _HomePageState extends State<HomePage> {
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
String searchQuery = ''; String searchQuery = '';
// Local database only test() async {
Map<String, dynamic> _databaseStats = {}; // await AuthLocalDataSource().removeAuthData();
final ProductLocalDatasource _localDatasource = }
ProductLocalDatasource.instance;
bool _isLoadingStats = true;
@override @override
void initState() { void initState() {
test();
// First sync products from API, then load local products
_syncAndLoadProducts();
super.initState(); super.initState();
_initializeLocalData();
_loadProducts();
} }
@override @override
void dispose() { void dispose() {
// Properly dispose controllers
searchController.dispose(); searchController.dispose();
scrollController.dispose(); scrollController.dispose();
super.dispose(); super.dispose();
} }
// Initialize local data only void _syncAndLoadProducts() {
void _initializeLocalData() { // Trigger sync from API first
_loadDatabaseStats(); // context.read<SyncProductBloc>().add(const SyncProductEvent.syncProduct());
}
// Load database statistics // Also load local products initially in case sync fails or takes time
void _loadDatabaseStats() async { // context
try { // .read<LocalProductBloc>()
final stats = await _localDatasource.getDatabaseStats(); // .add(const LocalProductEvent.getLocalProduct());
if (mounted) {
setState(() {
_databaseStats = stats;
_isLoadingStats = false;
});
}
log('📊 Local database stats: $stats');
} catch (e) {
log('❌ Error loading local stats: $e');
setState(() {
_isLoadingStats = false;
});
}
}
void _loadProducts() {
log('📱 Loading products from local database only...');
// Load products from local database only
context context
.read<ProductLoaderBloc>() .read<ProductLoaderBloc>()
.add(const ProductLoaderEvent.getProduct()); .add(const ProductLoaderEvent.getProduct());
// Initialize other components // Initialize checkout with tax and service charge settings
context.read<CheckoutBloc>().add(CheckoutEvent.started(widget.items)); context.read<CheckoutBloc>().add(const CheckoutEvent.started());
context.read<CategoryLoaderBloc>().add(CategoryLoaderEvent.get());
context.read<CurrentOutletBloc>().add(CurrentOutletEvent.currentOutlet());
}
void _refreshLocalData() { // Get Category
log('🔄 Refreshing local data...'); context.read<CategoryLoaderBloc>().add(CategoryLoaderEvent.get());
context.read<ProductLoaderBloc>().add(const ProductLoaderEvent.refresh());
_loadDatabaseStats(); // Get Outlets
context.read<CurrentOutletBloc>().add(CurrentOutletEvent.currentOutlet());
} }
void onCategoryTap(int index) { void onCategoryTap(int index) {
@@ -121,14 +91,31 @@ class _HomePageState extends State<HomePage> {
}); });
} }
List<Product> _filterProducts(List<Product> products) {
if (searchQuery.isEmpty) {
return products;
}
return products.where((product) {
final productName = product.name?.toLowerCase() ?? '';
final queryLower = searchQuery.toLowerCase();
return productName.contains(queryLower);
}).toList();
}
bool _handleScrollNotification( bool _handleScrollNotification(
ScrollNotification notification, String? categoryId) { ScrollNotification notification, String? categoryId) {
// Check if the ScrollController is attached before accessing position
if (!scrollController.hasClients) {
return false;
}
if (notification is ScrollEndNotification && if (notification is ScrollEndNotification &&
scrollController.position.extentAfter == 0) { scrollController.position.extentAfter == 0) {
log('đź“„ Loading more local products for category: $categoryId');
context.read<ProductLoaderBloc>().add( context.read<ProductLoaderBloc>().add(
ProductLoaderEvent.loadMore( ProductLoaderEvent.loadMore(
categoryId: categoryId, categoryId: categoryId,
search: searchQuery,
), ),
); );
return true; return true;
@@ -158,562 +145,128 @@ class _HomePageState extends State<HomePage> {
tag: 'confirmation_screen', tag: 'confirmation_screen',
child: Scaffold( child: Scaffold(
backgroundColor: AppColors.white, backgroundColor: AppColors.white,
body: Column( body: Row(
children: [ children: [
// Local database indicator
_buildLocalModeIndicator(),
// Main content
Expanded(
child: Row(
children: [
// Left panel - Products
Expanded( Expanded(
flex: 3, flex: 3,
child: Align( child: Align(
alignment: AlignmentDirectional.topStart, alignment: AlignmentDirectional.topStart,
child: BlocBuilder<CategoryLoaderBloc, child: BlocBuilder<CategoryLoaderBloc, CategoryLoaderState>(
CategoryLoaderState>(
builder: (context, state) { builder: (context, state) {
return state.maybeWhen( return state.maybeWhen(
orElse: () => orElse: () => Center(
Center(child: CircularProgressIndicator()), child: CircularProgressIndicator(),
),
loaded: (categories, categoryId) => Column( loaded: (categories, categoryId) => Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
// Enhanced home title with local stats
_buildLocalHomeTitle(categoryId),
// Products section
Expanded(
child: BlocBuilder<ProductLoaderBloc,
ProductLoaderState>(
builder: (context, productState) {
return CategoryTabBar(
categories: categories,
tabViews: categories.map((category) {
return SizedBox(
child: productState.maybeWhen(
orElse: () =>
_buildLoadingState(),
loading: () =>
_buildLoadingState(),
loaded: (products,
hasReachedMax,
currentPage,
isLoadingMore,
categoryId,
searchQuery) {
if (products.isEmpty) {
return _buildEmptyState(
categoryId);
}
return _buildProductGrid(
products,
hasReachedMax,
isLoadingMore,
categoryId,
currentPage,
);
},
error: (message) =>
_buildErrorState(
message, categoryId),
),
);
}).toList(),
);
},
),
),
],
),
);
},
),
),
),
// Right panel - Cart (unchanged)
Expanded(
flex: 2,
child: _buildCartSection(),
),
],
),
),
],
),
),
),
);
}
// Local mode indicator
Widget _buildLocalModeIndicator() {
return Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 16),
color: Colors.blue.shade600,
child: Row(
children: [
Icon(Icons.storage, color: Colors.white, size: 16),
SizedBox(width: 8),
Expanded(
child: Text(
_isLoadingStats
? 'Mode Lokal - Memuat data...'
: 'Mode Lokal - ${_databaseStats['total_products'] ?? 0} produk tersimpan',
style: TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
),
if (_databaseStats.isNotEmpty) ...[
Text(
'${(_databaseStats['database_size_mb'] ?? 0.0).toStringAsFixed(1)} MB',
style: TextStyle(
color: Colors.white.withOpacity(0.8),
fontSize: 11,
),
),
SizedBox(width: 8),
],
InkWell(
onTap: _refreshLocalData,
child: Container(
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(4),
),
child: Icon(Icons.refresh, color: Colors.white, size: 14),
),
),
],
),
);
}
// Enhanced home title with local stats only
Widget _buildLocalHomeTitle(String? categoryId) {
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: Colors.grey.shade200)),
),
child: Column(
children: [
// Original HomeTitle with faster search
HomeTitle( HomeTitle(
controller: searchController, controller: searchController,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
searchQuery = value; searchQuery = value;
}); });
Future.delayed(Duration(milliseconds: 600), () {
// Fast local search - no debounce needed for local data
Future.delayed(Duration(milliseconds: 200), () {
if (value == searchController.text) {
log('🔍 Local search: "$value"');
context.read<ProductLoaderBloc>().add( context.read<ProductLoaderBloc>().add(
ProductLoaderEvent.searchProduct( ProductLoaderEvent.getProduct(
categoryId: categoryId, categoryId: categoryId,
query: value, search: value,
), ),
); );
}
}); });
}, },
), ),
BlocBuilder<ProductLoaderBloc, ProductLoaderState>(
// Local database stats builder: (context, state) {
if (_databaseStats.isNotEmpty) ...[ return NotificationListener<ScrollNotification>(
SizedBox(height: 8), onNotification: (notification) {
Row( return state.maybeWhen(
children: [ orElse: () => false,
// Local storage indicator loaded: (products, hasReachedMax,
Container( currentPage, isLoadingMore) {
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 3), return _handleScrollNotification(
decoration: BoxDecoration( notification, categoryId);
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.storage,
size: 12, color: Colors.blue.shade600),
SizedBox(width: 3),
Text(
'Lokal',
style: TextStyle(
fontSize: 10,
color: Colors.blue.shade600,
fontWeight: FontWeight.w500,
),
),
],
),
),
SizedBox(width: 8),
// Database stats chips
_buildStatChip(
'${_databaseStats['total_products'] ?? 0}',
'produk',
Icons.inventory_2,
Colors.green,
),
SizedBox(width: 6),
_buildStatChip(
'${_databaseStats['total_variants'] ?? 0}',
'varian',
Icons.tune,
Colors.orange,
),
SizedBox(width: 6),
_buildStatChip(
'${_databaseStats['cache_entries'] ?? 0}',
'cache',
Icons.memory,
Colors.purple,
),
Spacer(),
// Clear cache button
InkWell(
onTap: () {
_localDatasource.clearExpiredCache();
_loadDatabaseStats();
AppFlushbar.showSuccess(context, 'Cache dibersihkan');
}, },
child: Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(5),
),
child: Icon(
Icons.clear_all,
size: 14,
color: Colors.grey.shade600,
),
),
),
SizedBox(width: 4),
// Refresh button
InkWell(
onTap: _refreshLocalData,
child: Container(
padding: EdgeInsets.all(5),
decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(5),
),
child: Icon(
Icons.refresh,
size: 14,
color: AppColors.primary,
),
),
),
],
),
],
],
),
);
}
Widget _buildStatChip(
String value, String label, IconData icon, Color color) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 5, vertical: 2),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 10, color: color),
SizedBox(width: 2),
Text(
value,
style: TextStyle(
fontSize: 9,
fontWeight: FontWeight.w600,
color: color,
),
),
SizedBox(width: 1),
Text(
label,
style: TextStyle(
fontSize: 8,
color: color.withOpacity(0.8),
),
),
],
),
);
}
Widget _buildLoadingState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(color: AppColors.primary),
SizedBox(height: 16),
Text(
'Memuat data lokal...',
style: TextStyle(color: Colors.grey.shade600),
),
],
),
);
}
Widget _buildEmptyState(String? categoryId) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Assets.icons.noProduct.svg(),
SizedBox(height: 20),
Text(
searchQuery.isNotEmpty
? 'Produk "$searchQuery" tidak ditemukan'
: 'Belum ada data produk lokal',
textAlign: TextAlign.center,
),
SizedBox(height: 8),
Text(
'Tambahkan produk ke database lokal terlebih dahulu',
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 12,
),
textAlign: TextAlign.center,
),
SpaceHeight(20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (searchQuery.isNotEmpty) ...[
Button.filled(
width: 100,
onPressed: () {
searchController.clear();
setState(() => searchQuery = '');
context.read<ProductLoaderBloc>().add(
ProductLoaderEvent.getProduct(categoryId: categoryId),
); );
}, },
label: 'Reset', child: Expanded(
), child: CategoryTabBar(
SizedBox(width: 12), categories: categories,
], tabViews: categories.map((category) {
Button.filled( return SizedBox(
width: 120, child: state.maybeWhen(orElse: () {
onPressed: () { return const Center(
context.read<ProductLoaderBloc>().add( child:
ProductLoaderEvent.getProduct(categoryId: categoryId), CircularProgressIndicator(),
); );
}, }, loading: () {
label: 'Muat Ulang', return const Center(
), child:
], CircularProgressIndicator(),
), );
], }, loaded: (products,
), hashasReachedMax,
currentPage,
isLoadingMore) {
final filteredProducts =
_filterProducts(products);
if (filteredProducts.isEmpty) {
return const Center(
child: Text('No Items Found'),
); );
} }
return GridView.builder(
Widget _buildErrorState(String message, String? categoryId) { itemCount:
return Center( filteredProducts.length,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
size: 48,
color: Colors.red.shade400,
),
SizedBox(height: 16),
Text(
'Error Database Lokal',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 8),
Padding(
padding: EdgeInsets.symmetric(horizontal: 32),
child: Text(
message,
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey.shade600),
),
),
SizedBox(height: 16),
Button.filled(
width: 120,
onPressed: () {
context.read<ProductLoaderBloc>().add(
ProductLoaderEvent.getProduct(categoryId: categoryId),
);
},
label: 'Coba Lagi',
),
],
),
);
}
Widget _buildProductGrid(
List products,
bool hasReachedMax,
bool isLoadingMore,
String? categoryId,
int currentPage,
) {
return Column(
children: [
// Product count with local indicator
if (products.isNotEmpty)
Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
child: Row(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 1),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(4),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.storage,
size: 10, color: Colors.blue.shade600),
SizedBox(width: 2),
Text(
'${products.length}',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Colors.blue.shade600,
),
),
],
),
),
SizedBox(width: 6),
Text(
'produk dari database lokal',
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 11,
),
),
if (currentPage > 1) ...[
SizedBox(width: 6),
Container(
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 1),
decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
),
child: Text(
'Hal $currentPage',
style: TextStyle(
color: AppColors.primary,
fontSize: 9,
fontWeight: FontWeight.w500,
),
),
),
],
Spacer(),
if (isLoadingMore)
SizedBox(
width: 12,
height: 12,
child: CircularProgressIndicator(
strokeWidth: 2,
color: AppColors.primary,
),
),
],
),
),
// Products grid - faster loading from local DB
Expanded(
child: NotificationListener<ScrollNotification>(
onNotification: (notification) =>
_handleScrollNotification(notification, categoryId),
child: GridView.builder(
itemCount: products.length,
controller: scrollController, controller: scrollController,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
cacheExtent: 200.0, // Bigger cache for smooth scrolling gridDelegate:
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 180, maxCrossAxisExtent: 180,
mainAxisSpacing: 30, mainAxisSpacing: 30,
crossAxisSpacing: 30, crossAxisSpacing: 30,
childAspectRatio: 180 / 240, childAspectRatio: 180 / 240,
), ),
itemBuilder: (context, index) => ProductCard( itemBuilder: (context, index) =>
data: products[index], ProductCard(
onCartButton: () { data: filteredProducts[index],
// Cart functionality onCartButton: () {},
),
);
}),
);
}).toList(),
),
),
);
},
),
],
),
);
}, },
), ),
), ),
), ),
), Expanded(
flex: 2,
// End of data indicator child: Align(
if (hasReachedMax && products.isNotEmpty)
Container(
padding: EdgeInsets.all(8),
child: Text(
'Semua produk lokal telah dimuat',
style: TextStyle(
color: Colors.grey.shade500,
fontSize: 11,
),
),
),
],
);
}
// Cart section (unchanged from original)
Widget _buildCartSection() {
return Align(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
child: Material( child: Material(
color: Colors.white, color: Colors.white,
child: Column( child: Column(
children: [ children: [
HomeRightTitle(table: widget.table), HomeRightTitle(
table: widget.table,
),
Padding( Padding(
padding: const EdgeInsets.all(16.0).copyWith(bottom: 0, top: 27), padding: const EdgeInsets.all(16.0)
.copyWith(bottom: 0, top: 27),
child: Column( child: Column(
children: [ children: [
const Row( const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'Item', 'Item',
@@ -723,7 +276,9 @@ class _HomePageState extends State<HomePage> {
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
SizedBox(width: 130), SizedBox(
width: 130,
),
SizedBox( SizedBox(
width: 50.0, width: 50.0,
child: Text( child: Text(
@@ -756,8 +311,11 @@ class _HomePageState extends State<HomePage> {
child: BlocBuilder<CheckoutBloc, CheckoutState>( child: BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) { builder: (context, state) {
return state.maybeWhen( return state.maybeWhen(
orElse: () => const Center(child: Text('No Items')), orElse: () => const Center(
loaded: (products, child: Text('No Items'),
),
loaded: (
products,
discountModel, discountModel,
discount, discount,
discountAmount, discountAmount,
@@ -767,13 +325,17 @@ class _HomePageState extends State<HomePage> {
totalPrice, totalPrice,
draftName, draftName,
orderType, orderType,
deliveryType) { deliveryType,
) {
if (products.isEmpty) { if (products.isEmpty) {
return const Center(child: Text('No Items')); return const Center(
child: Text('No Items'),
);
} }
return ListView.separated( return ListView.separated(
shrinkWrap: true, shrinkWrap: true,
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(
horizontal: 16),
itemBuilder: (context, index) => itemBuilder: (context, index) =>
OrderMenu(data: products[index]), OrderMenu(data: products[index]),
separatorBuilder: (context, index) => separatorBuilder: (context, index) =>
@@ -785,8 +347,6 @@ class _HomePageState extends State<HomePage> {
}, },
), ),
), ),
// Payment section (unchanged)
Padding( Padding(
padding: const EdgeInsets.all(16.0).copyWith(top: 0), padding: const EdgeInsets.all(16.0).copyWith(top: 0),
child: Column( child: Column(
@@ -794,7 +354,8 @@ class _HomePageState extends State<HomePage> {
const Divider(), const Divider(),
const SpaceHeight(16.0), const SpaceHeight(16.0),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [ children: [
const Text( const Text(
'Pajak', 'Pajak',
@@ -807,7 +368,8 @@ class _HomePageState extends State<HomePage> {
builder: (context, state) { builder: (context, state) {
final tax = state.maybeWhen( final tax = state.maybeWhen(
orElse: () => 0, orElse: () => 0,
loaded: (products, loaded: (
products,
discountModel, discountModel,
discount, discount,
discountAmount, discountAmount,
@@ -817,11 +379,13 @@ class _HomePageState extends State<HomePage> {
totalPrice, totalPrice,
draftName, draftName,
orderType, orderType,
deliveryType) { deliveryType,
if (products.isEmpty) return 0; ) {
if (products.isEmpty) {
return 0;
}
return tax; return tax;
}, });
);
return Text( return Text(
'$tax %', '$tax %',
style: const TextStyle( style: const TextStyle(
@@ -835,7 +399,8 @@ class _HomePageState extends State<HomePage> {
), ),
const SpaceHeight(16.0), const SpaceHeight(16.0),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [ children: [
const Text( const Text(
'Sub total', 'Sub total',
@@ -859,14 +424,19 @@ class _HomePageState extends State<HomePage> {
draftName, draftName,
orderType, orderType,
deliveryType) { deliveryType) {
if (products.isEmpty) return 0; if (products.isEmpty) {
return 0;
}
return products return products
.map((e) => .map((e) =>
(e.product.price! * e.quantity) + (e.product.price! *
(e.variant?.priceModifier ?? 0)) e.quantity) +
.reduce((value, element) => value + element); (e.variant?.priceModifier ??
}, 0))
); .reduce((value, element) =>
value + element);
});
return Text( return Text(
price.currencyFormatRp, price.currencyFormatRp,
style: const TextStyle( style: const TextStyle(
@@ -890,7 +460,9 @@ class _HomePageState extends State<HomePage> {
disabled: true, disabled: true,
onPressed: () { onPressed: () {
context.push(ConfirmPaymentPage( context.push(ConfirmPaymentPage(
isTable: widget.table == null ? false : true, isTable: widget.table == null
? false
: true,
table: widget.table, table: widget.table,
)); ));
}, },
@@ -921,14 +493,18 @@ class _HomePageState extends State<HomePage> {
'Mohon pilih meja terlebih dahulu'); 'Mohon pilih meja terlebih dahulu');
return; return;
} }
if (orderType.name == 'delivery' && if (orderType.name == 'delivery' &&
deliveryType == null) { deliveryType == null) {
AppFlushbar.showError(context, AppFlushbar.showError(context,
'Mohon pilih pengiriman terlebih dahulu'); 'Mohon pilih pengiriman terlebih dahulu');
return; return;
} }
context.push(ConfirmPaymentPage( context.push(ConfirmPaymentPage(
isTable: widget.table == null ? false : true, isTable: widget.table == null
? false
: true,
table: widget.table, table: widget.table,
)); ));
}, },
@@ -944,6 +520,34 @@ class _HomePageState extends State<HomePage> {
], ],
), ),
), ),
),
),
],
),
),
),
);
}
}
// ignore: unused_element
class _IsEmpty extends StatelessWidget {
const _IsEmpty();
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SpaceHeight(40),
Assets.icons.noProduct.svg(),
const SizedBox(height: 40.0),
const Text(
'Belum Ada Produk',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16),
),
],
); );
} }
} }
@@ -135,7 +135,6 @@ class HomeRightTitle extends StatelessWidget {
if (table == null) { if (table == null) {
context.push(DashboardPage( context.push(DashboardPage(
index: 1, index: 1,
items: items,
)); ));
} }
}, },
@@ -60,8 +60,6 @@ class ProductCard extends StatelessWidget {
fit: BoxFit.cover, fit: BoxFit.cover,
width: double.infinity, width: double.infinity,
height: 120, height: 120,
memCacheHeight: 120,
memCacheWidth: 120,
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
width: double.infinity, width: double.infinity,
height: 120, height: 120,
@@ -0,0 +1,211 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/data/dataoutputs/print_dataoutputs.dart';
import 'package:enaklo_pos/data/models/response/table_model.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import '../../../core/assets/assets.gen.dart';
import '../../../core/components/buttons.dart';
import '../../../core/components/spaces.dart';
import '../../table/blocs/get_table/get_table_bloc.dart';
import '../bloc/checkout/checkout_bloc.dart';
import '../models/order_type.dart';
import 'package:enaklo_pos/core/utils/printer_service.dart';
class SaveOrderDialog extends StatefulWidget {
const SaveOrderDialog({
super.key,
required this.data,
required this.totalQty,
required this.totalPrice,
required this.totalTax,
required this.totalDiscount,
required this.subTotal,
required this.normalPrice,
required this.table,
required this.draftName,
});
final List<ProductQuantity> data;
final int totalQty;
final int totalPrice;
final int totalTax;
final int totalDiscount;
final int subTotal;
final int normalPrice;
final TableModel table;
final String draftName;
@override
State<SaveOrderDialog> createState() => _SaveOrderDialogState();
}
class _SaveOrderDialogState extends State<SaveOrderDialog> {
// List<ProductQuantity> data = [];
// int totalQty = 0;
// int totalPrice = 0;
@override
Widget build(BuildContext context) {
return AlertDialog(
content: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(child: Assets.icons.success.svg()),
const SpaceHeight(16.0),
const Center(
child: Text(
'Order Berhasil Disimpan',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
),
),
),
const SpaceHeight(20.0),
Row(
children: [
Flexible(
child: Button.outlined(
onPressed: () {
context
.read<CheckoutBloc>()
.add(const CheckoutEvent.started());
context
.read<GetTableBloc>()
.add(const GetTableEvent.getTables());
context.popToRoot();
},
label: 'Kembali',
),
),
const SpaceWidth(8.0),
Flexible(
child: BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
final orderType = state.maybeWhen(
orElse: () => OrderType.dineIn,
loaded: (
items,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType,
deliveryType,
) =>
orderType,
);
return Button.filled(
onPressed: () async {
final checkerPrinter = await ProductLocalDatasource
.instance
.getPrinterByCode('checker');
final kitchenPrinter = await ProductLocalDatasource
.instance
.getPrinterByCode('kitchen');
final barPrinter = await ProductLocalDatasource
.instance
.getPrinterByCode('bar');
log("Checker printer: ${checkerPrinter?.toMap()}");
log("Kitchen printer: ${kitchenPrinter?.toMap()}");
log("Bar printer: ${barPrinter?.toMap()}");
// Checker printer
if (checkerPrinter != null) {
try {
final printValue = await PrintDataoutputs.instance
.printChecker(
widget.data,
widget.table.tableName!,
widget.draftName,
'kasir',
checkerPrinter.paper.toIntegerFromText,
orderType.value);
await PrinterService().printWithPrinter(
checkerPrinter, printValue, context);
} catch (e) {
log("Error printing checker: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text('Error printing checker: $e')),
);
}
}
// Kitchen printer
if (kitchenPrinter != null) {
try {
final printValue =
await PrintDataoutputs.instance.printKitchen(
widget.data,
widget.table.tableName!,
widget.draftName,
'kasir',
kitchenPrinter.paper.toIntegerFromText,
orderType.value,
);
await PrinterService().printWithPrinter(
kitchenPrinter, printValue, context);
} catch (e) {
log("Error printing kitchen order: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Error printing kitchen order: $e')),
);
}
}
// Bar printer
if (barPrinter != null) {
try {
final printValue =
await PrintDataoutputs.instance.printBar(
widget.data,
widget.table.tableName!,
widget.draftName,
'kasir',
barPrinter.paper.toIntegerFromText,
orderType.value,
);
await PrinterService().printWithPrinter(
barPrinter, printValue, context);
} catch (e) {
log("Error printing bar order: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text('Error printing bar order: $e')),
);
}
}
},
label: 'Print Checker',
);
},
),
),
],
),
],
),
),
);
}
}
@@ -0,0 +1,349 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/data/dataoutputs/print_dataoutputs.dart';
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import 'package:enaklo_pos/presentation/home/models/order_type.dart';
import 'package:enaklo_pos/core/utils/printer_service.dart';
import 'package:enaklo_pos/data/datasources/settings_local_datasource.dart';
import '../../../core/assets/assets.gen.dart';
import '../../../core/components/buttons.dart';
import '../../../core/components/spaces.dart';
import '../../table/blocs/get_table/get_table_bloc.dart';
import '../bloc/checkout/checkout_bloc.dart';
import '../bloc/order/order_bloc.dart';
class SuccessPaymentDialog extends StatefulWidget {
const SuccessPaymentDialog({
Key? key,
required this.data,
required this.totalQty,
required this.totalPrice,
required this.totalTax,
required this.totalDiscount,
required this.subTotal,
required this.normalPrice,
required this.totalService,
required this.draftName,
this.isTablePaymentPage = false,
}) : super(key: key);
final List<ProductQuantity> data;
final int totalQty;
final int totalPrice;
final int totalTax;
final int totalDiscount;
final int subTotal;
final int normalPrice;
final int totalService;
final String draftName;
final bool? isTablePaymentPage;
@override
State<SuccessPaymentDialog> createState() => _SuccessPaymentDialogState();
}
class _SuccessPaymentDialogState extends State<SuccessPaymentDialog> {
// List<ProductQuantity> data = [];
// int totalQty = 0;
// int totalPrice = 0;
@override
Widget build(BuildContext context) {
return AlertDialog(
content: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(child: Assets.icons.success.svg()),
const SpaceHeight(16.0),
const Center(
child: Text(
'Pembayaran telah sukses dilakukan',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
),
),
),
const SpaceHeight(20.0),
const Text('METODE BAYAR'),
const SpaceHeight(5.0),
BlocBuilder<OrderBloc, OrderState>(
builder: (context, state) {
final paymentMethod = state.maybeWhen(
orElse: () => 'Cash',
loaded: (model, orderId) => model.paymentMethod,
);
return Text(
paymentMethod,
style: const TextStyle(
fontWeight: FontWeight.w700,
),
);
},
),
const SpaceHeight(10.0),
const Divider(),
const SpaceHeight(8.0),
const Text('TOTAL TAGIHAN'),
const SpaceHeight(5.0),
BlocBuilder<OrderBloc, OrderState>(
builder: (context, state) {
state.maybeWhen(
orElse: () => 0,
loaded: (model, orderId) => model.total,
);
return Text(
widget.totalPrice.currencyFormatRp,
style: const TextStyle(
fontWeight: FontWeight.w700,
),
);
},
),
const SpaceHeight(10.0),
const Divider(),
const SpaceHeight(8.0),
const Text('NOMINAL BAYAR'),
const SpaceHeight(5.0),
BlocBuilder<OrderBloc, OrderState>(
builder: (context, state) {
final paymentAmount = state.maybeWhen(
orElse: () => 0,
loaded: (model, orderId) => model.paymentAmount,
);
return Text(
paymentAmount.ceil().currencyFormatRp,
style: const TextStyle(
fontWeight: FontWeight.w700,
),
);
},
),
const Divider(),
const SpaceHeight(8.0),
const Text('KEMBALIAN'),
const SpaceHeight(5.0),
BlocBuilder<OrderBloc, OrderState>(
builder: (context, state) {
final paymentAmount = state.maybeWhen(
orElse: () => 0,
loaded: (model, orderId) => model.paymentAmount,
);
final total = state.maybeWhen(
orElse: () => 0,
loaded: (model, orderId) => model.total,
);
final diff = paymentAmount - total;
log("DIFF: $diff paymentAmount: $paymentAmount total: $total");
return Text(
diff.ceil().currencyFormatRp,
style: const TextStyle(
fontWeight: FontWeight.w700,
),
);
},
),
const SpaceHeight(10.0),
const Divider(),
const SpaceHeight(8.0),
const Text('WAKTU PEMBAYARAN'),
const SpaceHeight(5.0),
Text(
DateFormat('dd MMMM yyyy, HH:mm').format(DateTime.now()),
style: const TextStyle(
fontWeight: FontWeight.w700,
),
),
const SpaceHeight(20.0),
Row(
children: [
Flexible(
child: Button.outlined(
onPressed: () {
// For table payment page, just close the dialog
// The cleanup and navigation is handled by the payment page
if (widget.isTablePaymentPage == true) {
Navigator.of(context).pop(); // Close dialog only
} else {
// For regular payment flow, reset and go to root
context
.read<CheckoutBloc>()
.add(const CheckoutEvent.started());
context
.read<GetTableBloc>()
.add(const GetTableEvent.getTables());
context.popToRoot();
}
},
label: 'Kembali',
),
),
const SpaceWidth(8.0),
Flexible(
child: BlocBuilder<OrderBloc, OrderState>(
builder: (context, state) {
final paymentAmount = state.maybeWhen(
orElse: () => 0,
loaded: (model, orderId) => model.paymentAmount,
);
final kembalian = paymentAmount - widget.totalPrice;
return BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, checkoutState) {
final orderType = checkoutState.maybeWhen(
orElse: () => OrderType.dineIn,
loaded: (
items,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType,
deliveryType,
) =>
orderType,
);
return Button.filled(
onPressed: () async {
final receiptPrinter =
await ProductLocalDatasource.instance
.getPrinterByCode('receipt');
final kitchenPrinter =
await ProductLocalDatasource.instance
.getPrinterByCode('kitchen');
final barPrinter = await ProductLocalDatasource
.instance
.getPrinterByCode('bar');
// Receipt Printer
if (receiptPrinter != null) {
try {
final settingsLocalDatasource =
SettingsLocalDatasource();
final taxModel =
await settingsLocalDatasource.getTax();
final serviceChargeValue =
await settingsLocalDatasource
.getServiceCharge();
// Get the actual payment method from OrderBloc
final paymentMethod = state.maybeWhen(
orElse: () => 'Cash',
loaded: (model, orderId) =>
model.paymentMethod,
);
final printValue = await PrintDataoutputs
.instance
.printOrderV3(
widget.data,
widget.totalQty,
widget.totalPrice,
paymentMethod,
paymentAmount,
kembalian,
widget.subTotal,
widget.totalDiscount,
widget.totalTax,
widget.totalService,
'kasir',
widget.draftName,
receiptPrinter.paper.toIntegerFromText,
taxPercentage: taxModel.value,
serviceChargePercentage: serviceChargeValue,
);
await PrinterService().printWithPrinter(
receiptPrinter, printValue, context);
} catch (e) {
log("Error printing receipt: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text('Error printing receipt: $e')),
);
}
}
// Kitchen Printer
if (kitchenPrinter != null &&
widget.isTablePaymentPage == false) {
try {
final printValue = await PrintDataoutputs
.instance
.printKitchen(
widget.data,
'',
widget.draftName,
'kasir',
kitchenPrinter.paper.toIntegerFromText,
orderType.value,
);
await PrinterService().printWithPrinter(
kitchenPrinter, printValue, context);
} catch (e) {
log("Error printing kitchen order: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Error printing kitchen order: $e')),
);
}
}
// Bar printer
if (barPrinter != null &&
widget.isTablePaymentPage == false) {
try {
final printValue =
await PrintDataoutputs.instance.printBar(
widget.data,
'',
widget.draftName,
'kasir',
barPrinter.paper.toIntegerFromText,
orderType.value,
);
await PrinterService().printWithPrinter(
barPrinter, printValue, context);
} catch (e) {
log("Error printing bar order: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Error printing bar order: $e')),
);
}
}
},
label: 'Print',
);
},
);
},
),
),
],
),
],
),
),
);
}
}
@@ -372,7 +372,8 @@ class _PaymentPageState extends State<PaymentPage> {
success: (data) { success: (data) {
context.pushReplacement(SuccessPaymentPage( context.pushReplacement(SuccessPaymentPage(
productQuantity: widget.order.orderItems productQuantity: widget.order.orderItems
?.map( ?.where((item) => item.status == "pending")
.map(
(item) => ProductQuantity( (item) => ProductQuantity(
product: Product( product: Product(
name: item.productName, name: item.productName,
@@ -1,28 +0,0 @@
import 'package:bloc/bloc.dart';
import 'package:enaklo_pos/data/datasources/analytic_remote_datasource.dart';
import 'package:enaklo_pos/data/models/response/category_analytic_response_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'category_report_event.dart';
part 'category_report_state.dart';
part 'category_report_bloc.freezed.dart';
class CategoryReportBloc
extends Bloc<CategoryReportEvent, CategoryReportState> {
final AnalyticRemoteDatasource _datasource;
CategoryReportBloc(this._datasource) : super(CategoryReportState.initial()) {
on<_Get>((event, emit) async {
emit(_Loading());
final result = await _datasource.getCategory(
dateFrom: event.startDate,
dateTo: event.endDate,
);
result.fold(
(l) => emit(_Error(l)),
(r) => emit(_Loaded(r.data!)),
);
});
}
}
@@ -1,861 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'category_report_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$CategoryReportEvent {
DateTime get startDate => throw _privateConstructorUsedError;
DateTime get endDate => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(DateTime startDate, DateTime endDate) get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(DateTime startDate, DateTime endDate)? get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(DateTime startDate, DateTime endDate)? get,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Get value) get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Get value)? get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Get value)? get,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
/// Create a copy of CategoryReportEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$CategoryReportEventCopyWith<CategoryReportEvent> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $CategoryReportEventCopyWith<$Res> {
factory $CategoryReportEventCopyWith(
CategoryReportEvent value, $Res Function(CategoryReportEvent) then) =
_$CategoryReportEventCopyWithImpl<$Res, CategoryReportEvent>;
@useResult
$Res call({DateTime startDate, DateTime endDate});
}
/// @nodoc
class _$CategoryReportEventCopyWithImpl<$Res, $Val extends CategoryReportEvent>
implements $CategoryReportEventCopyWith<$Res> {
_$CategoryReportEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of CategoryReportEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? startDate = null,
Object? endDate = null,
}) {
return _then(_value.copyWith(
startDate: null == startDate
? _value.startDate
: startDate // ignore: cast_nullable_to_non_nullable
as DateTime,
endDate: null == endDate
? _value.endDate
: endDate // ignore: cast_nullable_to_non_nullable
as DateTime,
) as $Val);
}
}
/// @nodoc
abstract class _$$GetImplCopyWith<$Res>
implements $CategoryReportEventCopyWith<$Res> {
factory _$$GetImplCopyWith(_$GetImpl value, $Res Function(_$GetImpl) then) =
__$$GetImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({DateTime startDate, DateTime endDate});
}
/// @nodoc
class __$$GetImplCopyWithImpl<$Res>
extends _$CategoryReportEventCopyWithImpl<$Res, _$GetImpl>
implements _$$GetImplCopyWith<$Res> {
__$$GetImplCopyWithImpl(_$GetImpl _value, $Res Function(_$GetImpl) _then)
: super(_value, _then);
/// Create a copy of CategoryReportEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? startDate = null,
Object? endDate = null,
}) {
return _then(_$GetImpl(
startDate: null == startDate
? _value.startDate
: startDate // ignore: cast_nullable_to_non_nullable
as DateTime,
endDate: null == endDate
? _value.endDate
: endDate // ignore: cast_nullable_to_non_nullable
as DateTime,
));
}
}
/// @nodoc
class _$GetImpl implements _Get {
const _$GetImpl({required this.startDate, required this.endDate});
@override
final DateTime startDate;
@override
final DateTime endDate;
@override
String toString() {
return 'CategoryReportEvent.get(startDate: $startDate, endDate: $endDate)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$GetImpl &&
(identical(other.startDate, startDate) ||
other.startDate == startDate) &&
(identical(other.endDate, endDate) || other.endDate == endDate));
}
@override
int get hashCode => Object.hash(runtimeType, startDate, endDate);
/// Create a copy of CategoryReportEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$GetImplCopyWith<_$GetImpl> get copyWith =>
__$$GetImplCopyWithImpl<_$GetImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(DateTime startDate, DateTime endDate) get,
}) {
return get(startDate, endDate);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(DateTime startDate, DateTime endDate)? get,
}) {
return get?.call(startDate, endDate);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(DateTime startDate, DateTime endDate)? get,
required TResult orElse(),
}) {
if (get != null) {
return get(startDate, endDate);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Get value) get,
}) {
return get(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Get value)? get,
}) {
return get?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Get value)? get,
required TResult orElse(),
}) {
if (get != null) {
return get(this);
}
return orElse();
}
}
abstract class _Get implements CategoryReportEvent {
const factory _Get(
{required final DateTime startDate,
required final DateTime endDate}) = _$GetImpl;
@override
DateTime get startDate;
@override
DateTime get endDate;
/// Create a copy of CategoryReportEvent
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$GetImplCopyWith<_$GetImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$CategoryReportState {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(CategoryAnalyticData data) loaded,
required TResult Function(String message) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(CategoryAnalyticData data)? loaded,
TResult? Function(String message)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(CategoryAnalyticData data)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $CategoryReportStateCopyWith<$Res> {
factory $CategoryReportStateCopyWith(
CategoryReportState value, $Res Function(CategoryReportState) then) =
_$CategoryReportStateCopyWithImpl<$Res, CategoryReportState>;
}
/// @nodoc
class _$CategoryReportStateCopyWithImpl<$Res, $Val extends CategoryReportState>
implements $CategoryReportStateCopyWith<$Res> {
_$CategoryReportStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of CategoryReportState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$InitialImplCopyWith<$Res> {
factory _$$InitialImplCopyWith(
_$InitialImpl value, $Res Function(_$InitialImpl) then) =
__$$InitialImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$InitialImplCopyWithImpl<$Res>
extends _$CategoryReportStateCopyWithImpl<$Res, _$InitialImpl>
implements _$$InitialImplCopyWith<$Res> {
__$$InitialImplCopyWithImpl(
_$InitialImpl _value, $Res Function(_$InitialImpl) _then)
: super(_value, _then);
/// Create a copy of CategoryReportState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$InitialImpl implements _Initial {
const _$InitialImpl();
@override
String toString() {
return 'CategoryReportState.initial()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$InitialImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(CategoryAnalyticData data) loaded,
required TResult Function(String message) error,
}) {
return initial();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(CategoryAnalyticData data)? loaded,
TResult? Function(String message)? error,
}) {
return initial?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(CategoryAnalyticData data)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return initial(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return initial?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial(this);
}
return orElse();
}
}
abstract class _Initial implements CategoryReportState {
const factory _Initial() = _$InitialImpl;
}
/// @nodoc
abstract class _$$LoadingImplCopyWith<$Res> {
factory _$$LoadingImplCopyWith(
_$LoadingImpl value, $Res Function(_$LoadingImpl) then) =
__$$LoadingImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$LoadingImplCopyWithImpl<$Res>
extends _$CategoryReportStateCopyWithImpl<$Res, _$LoadingImpl>
implements _$$LoadingImplCopyWith<$Res> {
__$$LoadingImplCopyWithImpl(
_$LoadingImpl _value, $Res Function(_$LoadingImpl) _then)
: super(_value, _then);
/// Create a copy of CategoryReportState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$LoadingImpl implements _Loading {
const _$LoadingImpl();
@override
String toString() {
return 'CategoryReportState.loading()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$LoadingImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(CategoryAnalyticData data) loaded,
required TResult Function(String message) error,
}) {
return loading();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(CategoryAnalyticData data)? loaded,
TResult? Function(String message)? error,
}) {
return loading?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(CategoryAnalyticData data)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return loading(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return loading?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading(this);
}
return orElse();
}
}
abstract class _Loading implements CategoryReportState {
const factory _Loading() = _$LoadingImpl;
}
/// @nodoc
abstract class _$$LoadedImplCopyWith<$Res> {
factory _$$LoadedImplCopyWith(
_$LoadedImpl value, $Res Function(_$LoadedImpl) then) =
__$$LoadedImplCopyWithImpl<$Res>;
@useResult
$Res call({CategoryAnalyticData data});
}
/// @nodoc
class __$$LoadedImplCopyWithImpl<$Res>
extends _$CategoryReportStateCopyWithImpl<$Res, _$LoadedImpl>
implements _$$LoadedImplCopyWith<$Res> {
__$$LoadedImplCopyWithImpl(
_$LoadedImpl _value, $Res Function(_$LoadedImpl) _then)
: super(_value, _then);
/// Create a copy of CategoryReportState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? data = null,
}) {
return _then(_$LoadedImpl(
null == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as CategoryAnalyticData,
));
}
}
/// @nodoc
class _$LoadedImpl implements _Loaded {
const _$LoadedImpl(this.data);
@override
final CategoryAnalyticData data;
@override
String toString() {
return 'CategoryReportState.loaded(data: $data)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LoadedImpl &&
(identical(other.data, data) || other.data == data));
}
@override
int get hashCode => Object.hash(runtimeType, data);
/// Create a copy of CategoryReportState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$LoadedImplCopyWith<_$LoadedImpl> get copyWith =>
__$$LoadedImplCopyWithImpl<_$LoadedImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(CategoryAnalyticData data) loaded,
required TResult Function(String message) error,
}) {
return loaded(data);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(CategoryAnalyticData data)? loaded,
TResult? Function(String message)? error,
}) {
return loaded?.call(data);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(CategoryAnalyticData data)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(data);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return loaded(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return loaded?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(this);
}
return orElse();
}
}
abstract class _Loaded implements CategoryReportState {
const factory _Loaded(final CategoryAnalyticData data) = _$LoadedImpl;
CategoryAnalyticData get data;
/// Create a copy of CategoryReportState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$LoadedImplCopyWith<_$LoadedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$ErrorImplCopyWith<$Res> {
factory _$$ErrorImplCopyWith(
_$ErrorImpl value, $Res Function(_$ErrorImpl) then) =
__$$ErrorImplCopyWithImpl<$Res>;
@useResult
$Res call({String message});
}
/// @nodoc
class __$$ErrorImplCopyWithImpl<$Res>
extends _$CategoryReportStateCopyWithImpl<$Res, _$ErrorImpl>
implements _$$ErrorImplCopyWith<$Res> {
__$$ErrorImplCopyWithImpl(
_$ErrorImpl _value, $Res Function(_$ErrorImpl) _then)
: super(_value, _then);
/// Create a copy of CategoryReportState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? message = null,
}) {
return _then(_$ErrorImpl(
null == message
? _value.message
: message // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
class _$ErrorImpl implements _Error {
const _$ErrorImpl(this.message);
@override
final String message;
@override
String toString() {
return 'CategoryReportState.error(message: $message)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ErrorImpl &&
(identical(other.message, message) || other.message == message));
}
@override
int get hashCode => Object.hash(runtimeType, message);
/// Create a copy of CategoryReportState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ErrorImplCopyWith<_$ErrorImpl> get copyWith =>
__$$ErrorImplCopyWithImpl<_$ErrorImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(CategoryAnalyticData data) loaded,
required TResult Function(String message) error,
}) {
return error(message);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(CategoryAnalyticData data)? loaded,
TResult? Function(String message)? error,
}) {
return error?.call(message);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(CategoryAnalyticData data)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(message);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return error(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return error?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(this);
}
return orElse();
}
}
abstract class _Error implements CategoryReportState {
const factory _Error(final String message) = _$ErrorImpl;
String get message;
/// Create a copy of CategoryReportState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ErrorImplCopyWith<_$ErrorImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1,9 +0,0 @@
part of 'category_report_bloc.dart';
@freezed
class CategoryReportEvent with _$CategoryReportEvent {
const factory CategoryReportEvent.get({
required DateTime startDate,
required DateTime endDate,
}) = _Get;
}
@@ -1,9 +0,0 @@
part of 'category_report_bloc.dart';
@freezed
class CategoryReportState with _$CategoryReportState {
const factory CategoryReportState.initial() = _Initial;
const factory CategoryReportState.loading() = _Loading;
const factory CategoryReportState.loaded(CategoryAnalyticData data) = _Loaded;
const factory CategoryReportState.error(String message) = _Error;
}
@@ -1,29 +0,0 @@
import 'package:bloc/bloc.dart';
import 'package:enaklo_pos/data/datasources/analytic_remote_datasource.dart';
import 'package:enaklo_pos/data/models/response/inventory_analytic_response_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'inventory_report_event.dart';
part 'inventory_report_state.dart';
part 'inventory_report_bloc.freezed.dart';
class InventoryReportBloc
extends Bloc<InventoryReportEvent, InventoryReportState> {
final AnalyticRemoteDatasource _datasource;
InventoryReportBloc(this._datasource)
: super(InventoryReportState.initial()) {
on<_Get>((event, emit) async {
emit(_Loading());
final result = await _datasource.getInventory(
dateFrom: event.startDate, dateTo: event.endDate);
result.fold(
(f) => emit(_Error(f)),
(r) => emit(
_Loaded(r.data!),
),
);
});
}
}
@@ -1,863 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'inventory_report_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$InventoryReportEvent {
DateTime get startDate => throw _privateConstructorUsedError;
DateTime get endDate => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(DateTime startDate, DateTime endDate) get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(DateTime startDate, DateTime endDate)? get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(DateTime startDate, DateTime endDate)? get,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Get value) get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Get value)? get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Get value)? get,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
/// Create a copy of InventoryReportEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$InventoryReportEventCopyWith<InventoryReportEvent> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $InventoryReportEventCopyWith<$Res> {
factory $InventoryReportEventCopyWith(InventoryReportEvent value,
$Res Function(InventoryReportEvent) then) =
_$InventoryReportEventCopyWithImpl<$Res, InventoryReportEvent>;
@useResult
$Res call({DateTime startDate, DateTime endDate});
}
/// @nodoc
class _$InventoryReportEventCopyWithImpl<$Res,
$Val extends InventoryReportEvent>
implements $InventoryReportEventCopyWith<$Res> {
_$InventoryReportEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of InventoryReportEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? startDate = null,
Object? endDate = null,
}) {
return _then(_value.copyWith(
startDate: null == startDate
? _value.startDate
: startDate // ignore: cast_nullable_to_non_nullable
as DateTime,
endDate: null == endDate
? _value.endDate
: endDate // ignore: cast_nullable_to_non_nullable
as DateTime,
) as $Val);
}
}
/// @nodoc
abstract class _$$GetImplCopyWith<$Res>
implements $InventoryReportEventCopyWith<$Res> {
factory _$$GetImplCopyWith(_$GetImpl value, $Res Function(_$GetImpl) then) =
__$$GetImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({DateTime startDate, DateTime endDate});
}
/// @nodoc
class __$$GetImplCopyWithImpl<$Res>
extends _$InventoryReportEventCopyWithImpl<$Res, _$GetImpl>
implements _$$GetImplCopyWith<$Res> {
__$$GetImplCopyWithImpl(_$GetImpl _value, $Res Function(_$GetImpl) _then)
: super(_value, _then);
/// Create a copy of InventoryReportEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? startDate = null,
Object? endDate = null,
}) {
return _then(_$GetImpl(
startDate: null == startDate
? _value.startDate
: startDate // ignore: cast_nullable_to_non_nullable
as DateTime,
endDate: null == endDate
? _value.endDate
: endDate // ignore: cast_nullable_to_non_nullable
as DateTime,
));
}
}
/// @nodoc
class _$GetImpl implements _Get {
const _$GetImpl({required this.startDate, required this.endDate});
@override
final DateTime startDate;
@override
final DateTime endDate;
@override
String toString() {
return 'InventoryReportEvent.get(startDate: $startDate, endDate: $endDate)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$GetImpl &&
(identical(other.startDate, startDate) ||
other.startDate == startDate) &&
(identical(other.endDate, endDate) || other.endDate == endDate));
}
@override
int get hashCode => Object.hash(runtimeType, startDate, endDate);
/// Create a copy of InventoryReportEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$GetImplCopyWith<_$GetImpl> get copyWith =>
__$$GetImplCopyWithImpl<_$GetImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(DateTime startDate, DateTime endDate) get,
}) {
return get(startDate, endDate);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(DateTime startDate, DateTime endDate)? get,
}) {
return get?.call(startDate, endDate);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(DateTime startDate, DateTime endDate)? get,
required TResult orElse(),
}) {
if (get != null) {
return get(startDate, endDate);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Get value) get,
}) {
return get(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Get value)? get,
}) {
return get?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Get value)? get,
required TResult orElse(),
}) {
if (get != null) {
return get(this);
}
return orElse();
}
}
abstract class _Get implements InventoryReportEvent {
const factory _Get(
{required final DateTime startDate,
required final DateTime endDate}) = _$GetImpl;
@override
DateTime get startDate;
@override
DateTime get endDate;
/// Create a copy of InventoryReportEvent
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$GetImplCopyWith<_$GetImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$InventoryReportState {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(InventoryAnalyticData data) loaded,
required TResult Function(String message) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(InventoryAnalyticData data)? loaded,
TResult? Function(String message)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(InventoryAnalyticData data)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $InventoryReportStateCopyWith<$Res> {
factory $InventoryReportStateCopyWith(InventoryReportState value,
$Res Function(InventoryReportState) then) =
_$InventoryReportStateCopyWithImpl<$Res, InventoryReportState>;
}
/// @nodoc
class _$InventoryReportStateCopyWithImpl<$Res,
$Val extends InventoryReportState>
implements $InventoryReportStateCopyWith<$Res> {
_$InventoryReportStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of InventoryReportState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$InitialImplCopyWith<$Res> {
factory _$$InitialImplCopyWith(
_$InitialImpl value, $Res Function(_$InitialImpl) then) =
__$$InitialImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$InitialImplCopyWithImpl<$Res>
extends _$InventoryReportStateCopyWithImpl<$Res, _$InitialImpl>
implements _$$InitialImplCopyWith<$Res> {
__$$InitialImplCopyWithImpl(
_$InitialImpl _value, $Res Function(_$InitialImpl) _then)
: super(_value, _then);
/// Create a copy of InventoryReportState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$InitialImpl implements _Initial {
const _$InitialImpl();
@override
String toString() {
return 'InventoryReportState.initial()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$InitialImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(InventoryAnalyticData data) loaded,
required TResult Function(String message) error,
}) {
return initial();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(InventoryAnalyticData data)? loaded,
TResult? Function(String message)? error,
}) {
return initial?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(InventoryAnalyticData data)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return initial(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return initial?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial(this);
}
return orElse();
}
}
abstract class _Initial implements InventoryReportState {
const factory _Initial() = _$InitialImpl;
}
/// @nodoc
abstract class _$$LoadingImplCopyWith<$Res> {
factory _$$LoadingImplCopyWith(
_$LoadingImpl value, $Res Function(_$LoadingImpl) then) =
__$$LoadingImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$LoadingImplCopyWithImpl<$Res>
extends _$InventoryReportStateCopyWithImpl<$Res, _$LoadingImpl>
implements _$$LoadingImplCopyWith<$Res> {
__$$LoadingImplCopyWithImpl(
_$LoadingImpl _value, $Res Function(_$LoadingImpl) _then)
: super(_value, _then);
/// Create a copy of InventoryReportState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$LoadingImpl implements _Loading {
const _$LoadingImpl();
@override
String toString() {
return 'InventoryReportState.loading()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$LoadingImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(InventoryAnalyticData data) loaded,
required TResult Function(String message) error,
}) {
return loading();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(InventoryAnalyticData data)? loaded,
TResult? Function(String message)? error,
}) {
return loading?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(InventoryAnalyticData data)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return loading(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return loading?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading(this);
}
return orElse();
}
}
abstract class _Loading implements InventoryReportState {
const factory _Loading() = _$LoadingImpl;
}
/// @nodoc
abstract class _$$LoadedImplCopyWith<$Res> {
factory _$$LoadedImplCopyWith(
_$LoadedImpl value, $Res Function(_$LoadedImpl) then) =
__$$LoadedImplCopyWithImpl<$Res>;
@useResult
$Res call({InventoryAnalyticData data});
}
/// @nodoc
class __$$LoadedImplCopyWithImpl<$Res>
extends _$InventoryReportStateCopyWithImpl<$Res, _$LoadedImpl>
implements _$$LoadedImplCopyWith<$Res> {
__$$LoadedImplCopyWithImpl(
_$LoadedImpl _value, $Res Function(_$LoadedImpl) _then)
: super(_value, _then);
/// Create a copy of InventoryReportState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? data = null,
}) {
return _then(_$LoadedImpl(
null == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as InventoryAnalyticData,
));
}
}
/// @nodoc
class _$LoadedImpl implements _Loaded {
const _$LoadedImpl(this.data);
@override
final InventoryAnalyticData data;
@override
String toString() {
return 'InventoryReportState.loaded(data: $data)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LoadedImpl &&
(identical(other.data, data) || other.data == data));
}
@override
int get hashCode => Object.hash(runtimeType, data);
/// Create a copy of InventoryReportState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$LoadedImplCopyWith<_$LoadedImpl> get copyWith =>
__$$LoadedImplCopyWithImpl<_$LoadedImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(InventoryAnalyticData data) loaded,
required TResult Function(String message) error,
}) {
return loaded(data);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(InventoryAnalyticData data)? loaded,
TResult? Function(String message)? error,
}) {
return loaded?.call(data);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(InventoryAnalyticData data)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(data);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return loaded(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return loaded?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(this);
}
return orElse();
}
}
abstract class _Loaded implements InventoryReportState {
const factory _Loaded(final InventoryAnalyticData data) = _$LoadedImpl;
InventoryAnalyticData get data;
/// Create a copy of InventoryReportState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$LoadedImplCopyWith<_$LoadedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$ErrorImplCopyWith<$Res> {
factory _$$ErrorImplCopyWith(
_$ErrorImpl value, $Res Function(_$ErrorImpl) then) =
__$$ErrorImplCopyWithImpl<$Res>;
@useResult
$Res call({String message});
}
/// @nodoc
class __$$ErrorImplCopyWithImpl<$Res>
extends _$InventoryReportStateCopyWithImpl<$Res, _$ErrorImpl>
implements _$$ErrorImplCopyWith<$Res> {
__$$ErrorImplCopyWithImpl(
_$ErrorImpl _value, $Res Function(_$ErrorImpl) _then)
: super(_value, _then);
/// Create a copy of InventoryReportState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? message = null,
}) {
return _then(_$ErrorImpl(
null == message
? _value.message
: message // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
class _$ErrorImpl implements _Error {
const _$ErrorImpl(this.message);
@override
final String message;
@override
String toString() {
return 'InventoryReportState.error(message: $message)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ErrorImpl &&
(identical(other.message, message) || other.message == message));
}
@override
int get hashCode => Object.hash(runtimeType, message);
/// Create a copy of InventoryReportState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ErrorImplCopyWith<_$ErrorImpl> get copyWith =>
__$$ErrorImplCopyWithImpl<_$ErrorImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(InventoryAnalyticData data) loaded,
required TResult Function(String message) error,
}) {
return error(message);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(InventoryAnalyticData data)? loaded,
TResult? Function(String message)? error,
}) {
return error?.call(message);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(InventoryAnalyticData data)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(message);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return error(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return error?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(this);
}
return orElse();
}
}
abstract class _Error implements InventoryReportState {
const factory _Error(final String message) = _$ErrorImpl;
String get message;
/// Create a copy of InventoryReportState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ErrorImplCopyWith<_$ErrorImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -1,9 +0,0 @@
part of 'inventory_report_bloc.dart';
@freezed
class InventoryReportEvent with _$InventoryReportEvent {
const factory InventoryReportEvent.get({
required DateTime startDate,
required DateTime endDate,
}) = _Get;
}
@@ -1,10 +0,0 @@
part of 'inventory_report_bloc.dart';
@freezed
class InventoryReportState with _$InventoryReportState {
const factory InventoryReportState.initial() = _Initial;
const factory InventoryReportState.loading() = _Loading;
const factory InventoryReportState.loaded(InventoryAnalyticData data) =
_Loaded;
const factory InventoryReportState.error(String message) = _Error;
}
@@ -22,7 +22,7 @@ class PaymentMethodReportBloc
(l) => emit(_Error(l)), (l) => emit(_Error(l)),
(r) => emit( (r) => emit(
_Loaded( _Loaded(
r.data!, r.data,
), ),
), ),
); );
@@ -16,7 +16,7 @@ class ProfitLossBloc extends Bloc<ProfitLossEvent, ProfitLossState> {
dateFrom: event.startDate, dateFrom: event.startDate,
dateTo: event.endDate, dateTo: event.endDate,
); );
result.fold((l) => emit(_Error(l)), (r) => emit(_Success(r.data!))); result.fold((l) => emit(_Error(l)), (r) => emit(_Success(r.data)));
}); });
} }
} }
@@ -1,63 +0,0 @@
import 'package:bloc/bloc.dart';
import 'package:enaklo_pos/data/datasources/analytic_remote_datasource.dart';
import 'package:enaklo_pos/data/datasources/outlet_local_datasource.dart';
import 'package:enaklo_pos/data/models/response/category_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/payment_method_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/product_analytic_response_model.dart';
import 'package:enaklo_pos/data/models/response/profit_loss_response_model.dart';
import 'package:enaklo_pos/presentation/home/models/outlet_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'report_event.dart';
part 'report_state.dart';
part 'report_bloc.freezed.dart';
class ReportBloc extends Bloc<ReportEvent, ReportState> {
final AnalyticRemoteDatasource _datasource;
ReportBloc(this._datasource) : super(ReportState.initial()) {
on<_Get>((event, emit) async {
emit(_Loading());
final outlet = await OutletLocalDatasource().get();
final category = await _datasource.getCategory(
dateFrom: event.startDate, dateTo: event.endDate);
final product = await _datasource.getProduct(
dateFrom: event.startDate, dateTo: event.endDate);
final paymentMethod = await _datasource.getPaymentMethod(
dateFrom: event.startDate, dateTo: event.endDate);
final profitLoss = await _datasource.getProfitLoss(
dateFrom: event.startDate, dateTo: event.endDate);
if (category.isLeft() ||
product.isLeft() ||
paymentMethod.isLeft() ||
profitLoss.isLeft()) {
emit(_Error());
}
emit(_Loaded(
outlet,
category
.getOrElse(
() => CategoryAnalyticResponseModel(success: false, data: null))
.data,
profitLoss
.getOrElse(
() => ProfitLossResponseModel(success: false, data: null))
.data,
paymentMethod
.getOrElse(() =>
PaymentMethodAnalyticResponseModel(success: false, data: null))
.data,
product
.getOrElse(
() => ProductAnalyticResponseModel(success: false, data: null))
.data,
));
});
}
}
@@ -1,975 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'report_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$ReportEvent {
DateTime get startDate => throw _privateConstructorUsedError;
DateTime get endDate => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(DateTime startDate, DateTime endDate) get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(DateTime startDate, DateTime endDate)? get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(DateTime startDate, DateTime endDate)? get,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Get value) get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Get value)? get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Get value)? get,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
/// Create a copy of ReportEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ReportEventCopyWith<ReportEvent> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ReportEventCopyWith<$Res> {
factory $ReportEventCopyWith(
ReportEvent value, $Res Function(ReportEvent) then) =
_$ReportEventCopyWithImpl<$Res, ReportEvent>;
@useResult
$Res call({DateTime startDate, DateTime endDate});
}
/// @nodoc
class _$ReportEventCopyWithImpl<$Res, $Val extends ReportEvent>
implements $ReportEventCopyWith<$Res> {
_$ReportEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ReportEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? startDate = null,
Object? endDate = null,
}) {
return _then(_value.copyWith(
startDate: null == startDate
? _value.startDate
: startDate // ignore: cast_nullable_to_non_nullable
as DateTime,
endDate: null == endDate
? _value.endDate
: endDate // ignore: cast_nullable_to_non_nullable
as DateTime,
) as $Val);
}
}
/// @nodoc
abstract class _$$GetImplCopyWith<$Res> implements $ReportEventCopyWith<$Res> {
factory _$$GetImplCopyWith(_$GetImpl value, $Res Function(_$GetImpl) then) =
__$$GetImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({DateTime startDate, DateTime endDate});
}
/// @nodoc
class __$$GetImplCopyWithImpl<$Res>
extends _$ReportEventCopyWithImpl<$Res, _$GetImpl>
implements _$$GetImplCopyWith<$Res> {
__$$GetImplCopyWithImpl(_$GetImpl _value, $Res Function(_$GetImpl) _then)
: super(_value, _then);
/// Create a copy of ReportEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? startDate = null,
Object? endDate = null,
}) {
return _then(_$GetImpl(
startDate: null == startDate
? _value.startDate
: startDate // ignore: cast_nullable_to_non_nullable
as DateTime,
endDate: null == endDate
? _value.endDate
: endDate // ignore: cast_nullable_to_non_nullable
as DateTime,
));
}
}
/// @nodoc
class _$GetImpl implements _Get {
const _$GetImpl({required this.startDate, required this.endDate});
@override
final DateTime startDate;
@override
final DateTime endDate;
@override
String toString() {
return 'ReportEvent.get(startDate: $startDate, endDate: $endDate)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$GetImpl &&
(identical(other.startDate, startDate) ||
other.startDate == startDate) &&
(identical(other.endDate, endDate) || other.endDate == endDate));
}
@override
int get hashCode => Object.hash(runtimeType, startDate, endDate);
/// Create a copy of ReportEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$GetImplCopyWith<_$GetImpl> get copyWith =>
__$$GetImplCopyWithImpl<_$GetImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(DateTime startDate, DateTime endDate) get,
}) {
return get(startDate, endDate);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(DateTime startDate, DateTime endDate)? get,
}) {
return get?.call(startDate, endDate);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(DateTime startDate, DateTime endDate)? get,
required TResult orElse(),
}) {
if (get != null) {
return get(startDate, endDate);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Get value) get,
}) {
return get(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Get value)? get,
}) {
return get?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Get value)? get,
required TResult orElse(),
}) {
if (get != null) {
return get(this);
}
return orElse();
}
}
abstract class _Get implements ReportEvent {
const factory _Get(
{required final DateTime startDate,
required final DateTime endDate}) = _$GetImpl;
@override
DateTime get startDate;
@override
DateTime get endDate;
/// Create a copy of ReportEvent
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$GetImplCopyWith<_$GetImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$ReportState {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)
loaded,
required TResult Function() error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult? Function()? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult Function()? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ReportStateCopyWith<$Res> {
factory $ReportStateCopyWith(
ReportState value, $Res Function(ReportState) then) =
_$ReportStateCopyWithImpl<$Res, ReportState>;
}
/// @nodoc
class _$ReportStateCopyWithImpl<$Res, $Val extends ReportState>
implements $ReportStateCopyWith<$Res> {
_$ReportStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ReportState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$InitialImplCopyWith<$Res> {
factory _$$InitialImplCopyWith(
_$InitialImpl value, $Res Function(_$InitialImpl) then) =
__$$InitialImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$InitialImplCopyWithImpl<$Res>
extends _$ReportStateCopyWithImpl<$Res, _$InitialImpl>
implements _$$InitialImplCopyWith<$Res> {
__$$InitialImplCopyWithImpl(
_$InitialImpl _value, $Res Function(_$InitialImpl) _then)
: super(_value, _then);
/// Create a copy of ReportState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$InitialImpl implements _Initial {
const _$InitialImpl();
@override
String toString() {
return 'ReportState.initial()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$InitialImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)
loaded,
required TResult Function() error,
}) {
return initial();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult? Function()? error,
}) {
return initial?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult Function()? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return initial(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return initial?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial(this);
}
return orElse();
}
}
abstract class _Initial implements ReportState {
const factory _Initial() = _$InitialImpl;
}
/// @nodoc
abstract class _$$LoadingImplCopyWith<$Res> {
factory _$$LoadingImplCopyWith(
_$LoadingImpl value, $Res Function(_$LoadingImpl) then) =
__$$LoadingImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$LoadingImplCopyWithImpl<$Res>
extends _$ReportStateCopyWithImpl<$Res, _$LoadingImpl>
implements _$$LoadingImplCopyWith<$Res> {
__$$LoadingImplCopyWithImpl(
_$LoadingImpl _value, $Res Function(_$LoadingImpl) _then)
: super(_value, _then);
/// Create a copy of ReportState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$LoadingImpl implements _Loading {
const _$LoadingImpl();
@override
String toString() {
return 'ReportState.loading()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$LoadingImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)
loaded,
required TResult Function() error,
}) {
return loading();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult? Function()? error,
}) {
return loading?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult Function()? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return loading(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return loading?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading(this);
}
return orElse();
}
}
abstract class _Loading implements ReportState {
const factory _Loading() = _$LoadingImpl;
}
/// @nodoc
abstract class _$$LoadedImplCopyWith<$Res> {
factory _$$LoadedImplCopyWith(
_$LoadedImpl value, $Res Function(_$LoadedImpl) then) =
__$$LoadedImplCopyWithImpl<$Res>;
@useResult
$Res call(
{Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData});
}
/// @nodoc
class __$$LoadedImplCopyWithImpl<$Res>
extends _$ReportStateCopyWithImpl<$Res, _$LoadedImpl>
implements _$$LoadedImplCopyWith<$Res> {
__$$LoadedImplCopyWithImpl(
_$LoadedImpl _value, $Res Function(_$LoadedImpl) _then)
: super(_value, _then);
/// Create a copy of ReportState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? outlet = null,
Object? categoryAnalyticData = freezed,
Object? profitLossData = freezed,
Object? paymentMethodAnalyticData = freezed,
Object? productAnalyticData = freezed,
}) {
return _then(_$LoadedImpl(
null == outlet
? _value.outlet
: outlet // ignore: cast_nullable_to_non_nullable
as Outlet,
freezed == categoryAnalyticData
? _value.categoryAnalyticData
: categoryAnalyticData // ignore: cast_nullable_to_non_nullable
as CategoryAnalyticData?,
freezed == profitLossData
? _value.profitLossData
: profitLossData // ignore: cast_nullable_to_non_nullable
as ProfitLossData?,
freezed == paymentMethodAnalyticData
? _value.paymentMethodAnalyticData
: paymentMethodAnalyticData // ignore: cast_nullable_to_non_nullable
as PaymentMethodAnalyticData?,
freezed == productAnalyticData
? _value.productAnalyticData
: productAnalyticData // ignore: cast_nullable_to_non_nullable
as ProductAnalyticData?,
));
}
}
/// @nodoc
class _$LoadedImpl implements _Loaded {
const _$LoadedImpl(
this.outlet,
this.categoryAnalyticData,
this.profitLossData,
this.paymentMethodAnalyticData,
this.productAnalyticData);
@override
final Outlet outlet;
@override
final CategoryAnalyticData? categoryAnalyticData;
@override
final ProfitLossData? profitLossData;
@override
final PaymentMethodAnalyticData? paymentMethodAnalyticData;
@override
final ProductAnalyticData? productAnalyticData;
@override
String toString() {
return 'ReportState.loaded(outlet: $outlet, categoryAnalyticData: $categoryAnalyticData, profitLossData: $profitLossData, paymentMethodAnalyticData: $paymentMethodAnalyticData, productAnalyticData: $productAnalyticData)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LoadedImpl &&
(identical(other.outlet, outlet) || other.outlet == outlet) &&
(identical(other.categoryAnalyticData, categoryAnalyticData) ||
other.categoryAnalyticData == categoryAnalyticData) &&
(identical(other.profitLossData, profitLossData) ||
other.profitLossData == profitLossData) &&
(identical(other.paymentMethodAnalyticData,
paymentMethodAnalyticData) ||
other.paymentMethodAnalyticData == paymentMethodAnalyticData) &&
(identical(other.productAnalyticData, productAnalyticData) ||
other.productAnalyticData == productAnalyticData));
}
@override
int get hashCode => Object.hash(runtimeType, outlet, categoryAnalyticData,
profitLossData, paymentMethodAnalyticData, productAnalyticData);
/// Create a copy of ReportState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$LoadedImplCopyWith<_$LoadedImpl> get copyWith =>
__$$LoadedImplCopyWithImpl<_$LoadedImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)
loaded,
required TResult Function() error,
}) {
return loaded(outlet, categoryAnalyticData, profitLossData,
paymentMethodAnalyticData, productAnalyticData);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult? Function()? error,
}) {
return loaded?.call(outlet, categoryAnalyticData, profitLossData,
paymentMethodAnalyticData, productAnalyticData);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult Function()? error,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(outlet, categoryAnalyticData, profitLossData,
paymentMethodAnalyticData, productAnalyticData);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return loaded(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return loaded?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(this);
}
return orElse();
}
}
abstract class _Loaded implements ReportState {
const factory _Loaded(
final Outlet outlet,
final CategoryAnalyticData? categoryAnalyticData,
final ProfitLossData? profitLossData,
final PaymentMethodAnalyticData? paymentMethodAnalyticData,
final ProductAnalyticData? productAnalyticData) = _$LoadedImpl;
Outlet get outlet;
CategoryAnalyticData? get categoryAnalyticData;
ProfitLossData? get profitLossData;
PaymentMethodAnalyticData? get paymentMethodAnalyticData;
ProductAnalyticData? get productAnalyticData;
/// Create a copy of ReportState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$LoadedImplCopyWith<_$LoadedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$ErrorImplCopyWith<$Res> {
factory _$$ErrorImplCopyWith(
_$ErrorImpl value, $Res Function(_$ErrorImpl) then) =
__$$ErrorImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$ErrorImplCopyWithImpl<$Res>
extends _$ReportStateCopyWithImpl<$Res, _$ErrorImpl>
implements _$$ErrorImplCopyWith<$Res> {
__$$ErrorImplCopyWithImpl(
_$ErrorImpl _value, $Res Function(_$ErrorImpl) _then)
: super(_value, _then);
/// Create a copy of ReportState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$ErrorImpl implements _Error {
const _$ErrorImpl();
@override
String toString() {
return 'ReportState.error()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$ErrorImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)
loaded,
required TResult Function() error,
}) {
return error();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult? Function()? error,
}) {
return error?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData)?
loaded,
TResult Function()? error,
required TResult orElse(),
}) {
if (error != null) {
return error();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return error(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return error?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(this);
}
return orElse();
}
}
abstract class _Error implements ReportState {
const factory _Error() = _$ErrorImpl;
}
@@ -1,9 +0,0 @@
part of 'report_bloc.dart';
@freezed
class ReportEvent with _$ReportEvent {
const factory ReportEvent.get({
required DateTime startDate,
required DateTime endDate,
}) = _Get;
}
@@ -1,15 +0,0 @@
part of 'report_bloc.dart';
@freezed
class ReportState with _$ReportState {
const factory ReportState.initial() = _Initial;
const factory ReportState.loading() = _Loading;
const factory ReportState.loaded(
Outlet outlet,
CategoryAnalyticData? categoryAnalyticData,
ProfitLossData? profitLossData,
PaymentMethodAnalyticData? paymentMethodAnalyticData,
ProductAnalyticData? productAnalyticData,
) = _Loaded;
const factory ReportState.error() = _Error;
}
+38 -256
View File
@@ -1,20 +1,12 @@
import 'dart:developer'; import 'dart:developer';
import 'package:enaklo_pos/core/components/date_range_picker.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
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/transaction_report.dart';
import 'package:enaklo_pos/presentation/report/blocs/category_report/category_report_bloc.dart';
import 'package:enaklo_pos/presentation/report/blocs/inventory_report/inventory_report_bloc.dart';
import 'package:enaklo_pos/presentation/report/blocs/profit_loss/profit_loss_bloc.dart'; import 'package:enaklo_pos/presentation/report/blocs/profit_loss/profit_loss_bloc.dart';
import 'package:enaklo_pos/presentation/report/blocs/report/report_bloc.dart';
import 'package:enaklo_pos/presentation/report/widgets/category_report_widget.dart';
import 'package:enaklo_pos/presentation/report/widgets/dashboard_analytic_widget.dart'; import 'package:enaklo_pos/presentation/report/widgets/dashboard_analytic_widget.dart';
import 'package:enaklo_pos/presentation/report/widgets/inventory_report_widget.dart';
import 'package:enaklo_pos/presentation/report/widgets/profit_loss_widget.dart'; import 'package:enaklo_pos/presentation/report/widgets/profit_loss_widget.dart';
import 'package:enaklo_pos/presentation/sales/pages/sales_page.dart'; import 'package:enaklo_pos/presentation/sales/pages/sales_page.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/core/components/custom_date_picker.dart';
import 'package:enaklo_pos/core/constants/colors.dart'; 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/utils/date_formatter.dart'; import 'package:enaklo_pos/core/utils/date_formatter.dart';
@@ -52,181 +44,59 @@ class _ReportPageState extends State<ReportPage> {
context.read<SummaryBloc>().add( context.read<SummaryBloc>().add(
SummaryEvent.getSummary(fromDate, toDate), SummaryEvent.getSummary(fromDate, toDate),
); );
context.read<ReportBloc>().add(
ReportEvent.get(startDate: fromDate, endDate: toDate),
);
}
onDateChanged(DateTime? startDate, DateTime? endDate) {
setState(() {
fromDate = startDate ?? fromDate;
toDate = endDate ?? toDate;
});
context.read<ReportBloc>().add(
ReportEvent.get(startDate: fromDate, endDate: toDate),
);
if (selectedMenu == 0) {
context.read<SummaryBloc>().add(
SummaryEvent.getSummary(fromDate, toDate),
);
}
if (selectedMenu == 2) {
context.read<ItemSalesReportBloc>().add(
ItemSalesReportEvent.getItemSales(
startDate: fromDate, endDate: toDate),
);
}
if (selectedMenu == 3) {
context.read<ProductSalesBloc>().add(
ProductSalesEvent.getProductSales(
fromDate,
toDate,
),
);
}
if (selectedMenu == 4) {
context.read<PaymentMethodReportBloc>().add(
PaymentMethodReportEvent.getPaymentMethodReport(
startDate: fromDate,
endDate: toDate,
),
);
}
if (selectedMenu == 5) {
context.read<ProfitLossBloc>().add(
ProfitLossEvent.getProfitLoss(
fromDate,
toDate,
),
);
}
if (selectedMenu == 6) {
context.read<InventoryReportBloc>().add(
InventoryReportEvent.get(
startDate: fromDate,
endDate: toDate,
),
);
}
if (selectedMenu == 7) {
context.read<CategoryReportBloc>().add(
CategoryReportEvent.get(
startDate: fromDate,
endDate: toDate,
),
);
}
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String searchDateFormatted = String searchDateFormatted =
'${fromDate.toFormattedDate2()} - ${toDate.toFormattedDate2()}'; '${fromDate.toFormattedDate2()} to ${toDate.toFormattedDate2()}';
return Scaffold( return Scaffold(
backgroundColor: AppColors.background, backgroundColor: AppColors.background,
body: Column( body: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ReportTitle( ReportTitle(
searchDateFormatted: searchDateFormatted,
actionWidget: [ actionWidget: [
InkWell( SizedBox(
onTap: () { width: 300,
DateRangePickerModal.show( child: CustomDatePicker(
context: context, prefix: const Text('From: '),
initialEndDate: toDate, initialDate: fromDate,
initialStartDate: fromDate, onDateSelected: (selectedDate) {
primaryColor: AppColors.primary, fromDate = selectedDate;
onChanged: onDateChanged,
); setState(() {});
}, },
child: Container(
padding:
EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
border: Border.all(
color: AppColors.stroke,
)),
child: Icon(
Icons.calendar_month_outlined,
color: AppColors.primary,
size: 28,
), ),
), ),
), const SpaceWidth(24.0),
const SpaceWidth(12.0), SizedBox(
BlocBuilder<ReportBloc, ReportState>( width: 300,
builder: (context, state) { child: CustomDatePicker(
return state.maybeWhen( prefix: const Text('To: '),
orElse: () => SizedBox.shrink(), initialDate: toDate,
loading: () => SizedBox( onDateSelected: (selectedDate) {
height: 24, toDate = selectedDate;
width: 24, setState(() {});
child: const CircularProgressIndicator(), // context.read<TransactionReportBloc>().add(
), // TransactionReportEvent.getReport(
loaded: (outlet, categoryAnalyticData, profitLossData, // startDate:
paymentMethodAnalyticData, productAnalyticData) => // DateFormatter.formatDateTime(
InkWell( // fromDate),
onTap: () async { // endDate: DateFormatter.formatDateTime(
try { // toDate)),
final status = // );
await PermessionHelper().checkPermission(); // context.read<ItemSalesReportBloc>().add(
if (status) { // ItemSalesReportEvent.getItemSales(
final pdfFile = await TransactionReport.previewPdf( // startDate:
outlet: outlet, // DateFormatter.formatDateTime(
searchDateFormatted: searchDateFormatted, // fromDate),
categoryAnalyticData: categoryAnalyticData, // endDate: DateFormatter.formatDateTime(
profitLossData: profitLossData, // toDate)),
paymentMethodAnalyticData: // );
paymentMethodAnalyticData,
productAnalyticData: productAnalyticData,
);
log("pdfFile: $pdfFile");
await HelperPdfService.openFile(pdfFile);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Storage permission is required to save PDF'),
backgroundColor: Colors.red,
),
);
}
} catch (e) {
log("Error generating PDF: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to generate PDF: $e'),
backgroundColor: Colors.red,
),
);
}
}, },
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 12.0, vertical: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
border: Border.all(
color: AppColors.stroke,
)),
child: Icon(
Icons.download,
color: AppColors.primary,
size: 28,
), ),
), ),
),
);
},
),
], ],
), ),
Expanded( Expanded(
@@ -324,12 +194,12 @@ class _ReportPageState extends State<ReportPage> {
isActive: selectedMenu == 4, isActive: selectedMenu == 4,
), ),
ReportMenu( ReportMenu(
label: 'Laporan Untung Rugi', label: 'Laporan untung rugi',
subtitle: 'Laporan untung rugi penjualan.', subtitle: 'Laporan untung rugi penjualan.',
icon: Icons.trending_down, icon: Icons.trending_down,
onPressed: () { onPressed: () {
selectedMenu = 5; selectedMenu = 5;
title = 'Laporan Untung Rugi'; title = 'Laporan untung rugi';
setState(() {}); setState(() {});
context.read<ProfitLossBloc>().add( context.read<ProfitLossBloc>().add(
ProfitLossEvent.getProfitLoss( ProfitLossEvent.getProfitLoss(
@@ -340,40 +210,6 @@ class _ReportPageState extends State<ReportPage> {
}, },
isActive: selectedMenu == 5, isActive: selectedMenu == 5,
), ),
ReportMenu(
label: 'Laporan Inventori',
subtitle: 'Laporan inventori produk',
icon: Icons.archive_outlined,
onPressed: () {
selectedMenu = 6;
title = 'Laporan Inventori';
setState(() {});
context.read<InventoryReportBloc>().add(
InventoryReportEvent.get(
startDate: fromDate,
endDate: toDate,
),
);
},
isActive: selectedMenu == 6,
),
ReportMenu(
label: 'Laporan Kategori',
subtitle: 'Laporan kategori produk',
icon: Icons.category_outlined,
onPressed: () {
selectedMenu = 7;
title = 'Laporan Kategori';
setState(() {});
context.read<CategoryReportBloc>().add(
CategoryReportEvent.get(
startDate: fromDate,
endDate: toDate,
),
);
},
isActive: selectedMenu == 7,
),
], ],
), ),
), ),
@@ -524,60 +360,6 @@ class _ReportPageState extends State<ReportPage> {
); );
}, },
) )
: selectedMenu == 6
? BlocBuilder<
InventoryReportBloc,
InventoryReportState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () =>
const Center(
child:
CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
loaded: (data) {
return InventoryReportWidget(
title: title,
searchDateFormatted:
searchDateFormatted,
inventory: data,
);
},
);
},
)
: selectedMenu == 7
? BlocBuilder<
CategoryReportBloc,
CategoryReportState>(
builder:
(context, state) {
return state
.maybeWhen(
orElse: () =>
const Center(
child:
CircularProgressIndicator(),
),
error: (message) {
return Text(
message);
},
loaded: (data) {
return CategoryReportWidget(
title: title,
searchDateFormatted:
searchDateFormatted,
categoryAnalyticData:
data,
);
},
);
},
)
: const SizedBox.shrink()), : const SizedBox.shrink()),
], ],
), ),
@@ -1,292 +0,0 @@
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/data/models/response/category_analytic_response_model.dart';
import 'package:flutter/material.dart';
class CategoryReportWidget extends StatelessWidget {
final String title;
final String searchDateFormatted;
final CategoryAnalyticData categoryAnalyticData;
const CategoryReportWidget({
super.key,
required this.title,
required this.searchDateFormatted,
required this.categoryAnalyticData,
});
String formatCurrency(int amount) {
return 'Rp ${amount.toString().replaceAllMapped(
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
(Match m) => '${m[1]}.',
)}';
}
int getTotalRevenue() {
return categoryAnalyticData.data
.fold(0, (sum, item) => sum + item.totalRevenue);
}
int getTotalQuantity() {
return categoryAnalyticData.data
.fold(0, (sum, item) => sum + item.totalQuantity);
}
int getTotalOrders() {
return categoryAnalyticData.data
.fold(0, (sum, item) => sum + item.orderCount);
}
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
color: AppColors.background,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header Section
Container(
width: double.infinity,
color: AppColors.primary,
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: AppColors.whiteText,
),
),
const SizedBox(height: 8),
Text(
'Periode: $searchDateFormatted',
style: TextStyle(
fontSize: 14,
color: AppColors.whiteText.withOpacity(0.8),
),
),
],
),
),
// Summary Cards Section
Container(
color: AppColors.white,
padding: const EdgeInsets.all(20),
child: Row(
children: [
Expanded(
child: _buildSummaryCard(
'Total Pendapatan',
formatCurrency(getTotalRevenue()),
AppColors.green,
),
),
const SizedBox(width: 16),
Expanded(
child: _buildSummaryCard(
'Total Item Terjual',
'${getTotalQuantity()} pcs',
AppColors.subtitle,
),
),
const SizedBox(width: 16),
Expanded(
child: _buildSummaryCard(
'Total Pesanan',
'${getTotalOrders()}',
AppColors.primary,
),
),
],
),
),
// Categories List Header
Container(
color: AppColors.stroke,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
child: Row(
children: [
Expanded(
flex: 3,
child: Text(
'Kategori',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.black,
),
),
),
Expanded(
flex: 2,
child: Text(
'Pendapatan',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.black,
),
),
),
Expanded(
flex: 1,
child: Text(
'Qty',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.black,
),
),
),
Expanded(
flex: 1,
child: Text(
'Order',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.black,
),
),
),
],
),
),
// Categories List
Expanded(
child: Container(
color: AppColors.white,
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: categoryAnalyticData.data.length,
itemBuilder: (context, index) {
return _buildCategoryItem(
categoryAnalyticData.data[index], index);
},
),
),
),
],
),
);
}
Widget _buildSummaryCard(String title, String value, Color color) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.light,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.stroke, width: 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 12,
color: AppColors.subtitle,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 8),
Text(
value,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
);
}
Widget _buildCategoryItem(CategoryAnalyticItem category, int index) {
final isEven = index % 2 == 0;
return Container(
color: isEven ? AppColors.white : AppColors.light.withOpacity(0.3),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
child: Row(
children: [
Expanded(
flex: 3,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
category.categoryName,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.black,
),
),
const SizedBox(height: 4),
Text(
'${category.productCount} produk',
style: TextStyle(
fontSize: 12,
color: AppColors.subtitle,
),
),
],
),
),
Expanded(
flex: 2,
child: Column(
children: [
Text(
formatCurrency(category.totalRevenue),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.green,
),
),
],
),
),
Expanded(
flex: 1,
child: Text(
'${category.totalQuantity}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.black,
),
),
),
Expanded(
flex: 1,
child: Text(
'${category.orderCount}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.black,
),
),
),
],
),
);
}
}
@@ -1,616 +0,0 @@
import 'dart:developer';
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/core/utils/helper_pdf_service.dart';
import 'package:enaklo_pos/core/utils/inventory_report.dart';
import 'package:enaklo_pos/core/utils/permession_handler.dart';
import 'package:enaklo_pos/data/models/response/inventory_analytic_response_model.dart';
import 'package:flutter/material.dart';
class InventoryReportWidget extends StatefulWidget {
final String title;
final String searchDateFormatted;
final InventoryAnalyticData inventory;
const InventoryReportWidget({
super.key,
required this.title,
required this.searchDateFormatted,
required this.inventory,
});
@override
State<InventoryReportWidget> createState() => _InventoryReportWidgetState();
}
class _InventoryReportWidgetState extends State<InventoryReportWidget> {
int _selectedTabIndex = 0;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
decoration: BoxDecoration(
color: AppColors.white,
border: Border.all(color: AppColors.stroke, width: 1),
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Report Header
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppColors.light,
border: Border(
bottom: BorderSide(color: AppColors.stroke, width: 1),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.title,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.primary,
),
),
const SizedBox(height: 4),
Text(
widget.searchDateFormatted,
style: TextStyle(
fontSize: 12,
color: AppColors.greyDark,
),
),
],
),
Row(
children: [
// Download Button
GestureDetector(
onTap: () async {
try {
final status =
await PermessionHelper().checkPermission();
if (status) {
final pdfFile = await InventoryReport.previewPdf(
searchDateFormatted: widget.searchDateFormatted,
inventory: widget.inventory,
);
log("pdfFile: $pdfFile");
await HelperPdfService.openFile(pdfFile);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Storage permission is required to save PDF'),
backgroundColor: Colors.red,
),
);
}
} catch (e) {
log("Error generating PDF: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to generate PDF: $e'),
backgroundColor: Colors.red,
),
);
}
},
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border:
Border.all(color: AppColors.primary, width: 1),
),
child: Icon(
Icons.download_outlined,
size: 18,
color: AppColors.primary,
),
),
),
const SizedBox(width: 12),
// Status Badge
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: AppColors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColors.green, width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.check_circle,
size: 14,
color: AppColors.green,
),
const SizedBox(width: 6),
Text(
'Aktif',
style: TextStyle(
fontSize: 12,
color: AppColors.green,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
],
),
),
// Summary Section
Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.analytics_outlined,
size: 20,
color: AppColors.primary,
),
const SizedBox(width: 8),
Text(
'Ringkasan Inventori',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColors.black,
),
),
],
),
const SizedBox(height: 16),
// Summary Grid
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
childAspectRatio: 2.2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
children: [
_buildSummaryCard(
'Total Produk',
(widget.inventory.summary.totalProducts).toString(),
AppColors.primary,
Icons.inventory_2_outlined,
),
_buildSummaryCard(
'Total Bahan',
widget.inventory.summary.totalIngredients.toString(),
AppColors.subtitle,
Icons.list_alt_outlined,
),
_buildSummaryCard(
'Total Nilai',
widget.inventory.summary.totalValue
.toString()
.currencyFormatRpV2,
AppColors.green,
Icons.monetization_on_outlined,
),
],
),
],
),
),
// Divider
Container(
height: 1,
margin: const EdgeInsets.symmetric(horizontal: 20),
color: AppColors.stroke,
),
// Tabs
Container(
padding: const EdgeInsets.all(20),
child: Row(
children: [
_buildTab('Produk', 0),
const SizedBox(width: 12),
_buildTab('Bahan Baku', 1),
],
),
),
// Content based on selected tab
_selectedTabIndex == 0
? _buildProductsContent()
: _buildIngredientsContent(),
],
),
),
);
}
Widget _buildSummaryCard(
String title, String value, Color color, IconData icon) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.08),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: color.withOpacity(0.2), width: 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(6),
),
child: Icon(
icon,
size: 16,
color: color,
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
title,
style: TextStyle(
fontSize: 10,
color: AppColors.greyDark,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 6),
Text(
value,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
);
}
Widget _buildTab(String title, int index) {
bool isActive = _selectedTabIndex == index;
return GestureDetector(
onTap: () {
setState(() {
_selectedTabIndex = index;
});
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: isActive ? AppColors.primary : AppColors.white,
borderRadius: BorderRadius.circular(25),
border: Border.all(
color: isActive ? AppColors.primary : AppColors.stroke,
width: 1,
),
),
child: Text(
title,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: isActive ? AppColors.whiteText : AppColors.greyDark,
),
),
),
);
}
Widget _buildProductsContent() {
return Container(
margin: const EdgeInsets.fromLTRB(20, 0, 20, 20),
child: Column(
children: [
Container(
decoration: BoxDecoration(
color: AppColors.primary, // Purple color
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8),
topRight: Radius.circular(8),
),
),
child: Table(
columnWidths: const {
0: FlexColumnWidth(2.5), // Produk
1: FlexColumnWidth(2), // Kategori
2: FlexColumnWidth(1), // Stock
3: FlexColumnWidth(2), // Masuk
4: FlexColumnWidth(2), // Keluar
},
children: [
TableRow(
children: [
_buildHeaderCell('Nama'),
_buildHeaderCell('Kategori'),
_buildHeaderCell('Stock'),
_buildHeaderCell('Masuk'),
_buildHeaderCell('Keluar'),
],
),
],
),
),
Container(
decoration: BoxDecoration(
color: AppColors.white,
),
child: Table(
columnWidths: {
0: FlexColumnWidth(2.5), // Produk
1: FlexColumnWidth(2), // Kategori
2: FlexColumnWidth(1), // Stock
3: FlexColumnWidth(2), // Masuk
4: FlexColumnWidth(2), // Keluar
},
children: widget.inventory.products
.map((item) => _buildProductDataRow(
item,
widget.inventory.products.indexOf(item) % 2 == 0,
))
.toList(),
),
),
Container(
decoration: BoxDecoration(
color: AppColors.primary, // Purple color
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(8),
bottomRight: Radius.circular(8),
),
),
child: Table(
columnWidths: const {
0: FlexColumnWidth(2.5), // Produk
1: FlexColumnWidth(2), // Kategori
2: FlexColumnWidth(1), // Stock
3: FlexColumnWidth(2), // Masuk
4: FlexColumnWidth(2), // Keluar
},
children: [
TableRow(
children: [
_buildTotalCell('TOTAL'),
_buildTotalCell(''),
_buildTotalCell(
(widget.inventory.products.fold<num>(
0, (sum, item) => sum + (item.quantity))).toString(),
),
_buildTotalCell(
(widget.inventory.products.fold<num>(
0, (sum, item) => sum + (item.totalIn))).toString(),
),
_buildTotalCell(
(widget.inventory.products.fold<num>(
0, (sum, item) => sum + (item.totalOut))).toString(),
),
],
),
],
),
),
],
),
);
}
Widget _buildIngredientsContent() {
return Container(
margin: const EdgeInsets.fromLTRB(20, 0, 20, 20),
child: Column(
children: [
Container(
decoration: BoxDecoration(
color: AppColors.primary, // Purple color
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8),
topRight: Radius.circular(8),
),
),
child: Table(
columnWidths: const {
0: FlexColumnWidth(2.5), // Name
1: FlexColumnWidth(1), // Stock
2: FlexColumnWidth(2), // Masuk
3: FlexColumnWidth(2), // Keluar
},
children: [
TableRow(
children: [
_buildHeaderCell('Nama'),
_buildHeaderCell('Stock'),
_buildHeaderCell('Masuk'),
_buildHeaderCell('Keluar'),
],
),
],
),
),
Container(
decoration: BoxDecoration(
color: AppColors.white,
),
child: Table(
columnWidths: {
0: FlexColumnWidth(2.5), // Name
1: FlexColumnWidth(1), // Stock
2: FlexColumnWidth(2), // Masuk
3: FlexColumnWidth(2), // Keluar
},
children: widget.inventory.ingredients
.map((item) => _buildIngredientsDataRow(
item,
widget.inventory.ingredients.indexOf(item) % 2 == 0,
))
.toList(),
),
),
Container(
decoration: BoxDecoration(
color: AppColors.primary, // Purple color
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(8),
bottomRight: Radius.circular(8),
),
),
child: Table(
columnWidths: const {
0: FlexColumnWidth(2.5), // Name
1: FlexColumnWidth(1), // Stock
2: FlexColumnWidth(2), // Masuk
3: FlexColumnWidth(2), // Keluar
},
children: [
TableRow(
children: [
_buildTotalCell('TOTAL'),
_buildTotalCell(
(widget.inventory.ingredients.fold<num>(
0, (sum, item) => sum + (item.quantity))).toString(),
),
_buildTotalCell(
(widget.inventory.ingredients.fold<num>(
0, (sum, item) => sum + (item.totalIn))).toString(),
),
_buildTotalCell(
(widget.inventory.ingredients.fold<num>(
0, (sum, item) => sum + (item.totalOut))).toString(),
),
],
),
],
),
),
],
),
);
}
TableRow _buildProductDataRow(InventoryProductItem product, bool isEven) {
return TableRow(
decoration: BoxDecoration(
color: product.isZeroStock
? Colors.red.shade100
: product.isLowStock
? Colors.yellow.shade100
: isEven
? Colors.grey.shade50
: AppColors.white,
),
children: [
_buildDataCell(product.productName, alignment: Alignment.centerLeft),
_buildDataCell(product.categoryName, alignment: Alignment.centerLeft),
_buildDataCell(product.quantity.toString()),
_buildDataCell(product.totalIn.toString()),
_buildDataCell(product.totalOut.toString()),
],
);
}
TableRow _buildIngredientsDataRow(InventoryIngredientItem item, bool isEven) {
return TableRow(
decoration: BoxDecoration(
color: item.isZeroStock
? Colors.red.shade100
: item.isLowStock
? Colors.yellow.shade100
: isEven
? Colors.grey.shade50
: AppColors.white,
),
children: [
_buildDataCell(item.ingredientName, alignment: Alignment.centerLeft),
_buildDataCell(item.quantity.toString()),
_buildDataCell(item.totalIn.toString()),
_buildDataCell(item.totalOut.toString()),
],
);
}
Widget _buildHeaderCell(String text) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 16),
child: Text(
text,
style: TextStyle(
color: AppColors.white,
fontWeight: FontWeight.bold,
fontSize: 12,
),
textAlign: TextAlign.center,
),
);
}
Widget _buildDataCell(String text,
{Alignment alignment = Alignment.center, Color? textColor}) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 16),
alignment: alignment,
child: Text(
text,
style: TextStyle(
fontSize: 12,
color: textColor ?? AppColors.black,
fontWeight: FontWeight.normal,
),
textAlign: alignment == Alignment.centerLeft
? TextAlign.left
: TextAlign.center,
),
);
}
Widget _buildTotalCell(String text) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 16),
child: Text(
text,
style: TextStyle(
color: AppColors.white,
fontWeight: FontWeight.bold,
fontSize: 12,
),
textAlign: TextAlign.center,
),
);
}
}
@@ -73,8 +73,7 @@ class ProfitLossWidget extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Column(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
@@ -95,7 +94,6 @@ class ProfitLossWidget extends StatelessWidget {
), ),
], ],
), ),
),
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -630,7 +628,7 @@ class ProfitLossWidget extends StatelessWidget {
); );
} }
Widget _buildProductMetric(String label, num value, Color color) { Widget _buildProductMetric(String label, int value, Color color) {
return Container( return Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -724,7 +722,7 @@ class ProfitLossWidget extends StatelessWidget {
sections: breakdownData.asMap().entries.map((entry) { sections: breakdownData.asMap().entries.map((entry) {
final item = entry.value; final item = entry.value;
final grossProfit = data.summary.grossProfit; final grossProfit = data.summary.grossProfit;
final value = item['value'] as num; final value = item['value'] as int;
// Handle division by zero // Handle division by zero
final percentage = final percentage =
@@ -774,7 +772,7 @@ class ProfitLossWidget extends StatelessWidget {
), ),
), ),
Text( Text(
_formatCurrency(item['value'] as num), _formatCurrency(item['value'] as int),
style: const TextStyle( style: const TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -875,7 +873,7 @@ class ProfitLossWidget extends StatelessWidget {
Expanded( Expanded(
child: _buildMetricCard( child: _buildMetricCard(
'Keuntungan Rata-rata', 'Keuntungan Rata-rata',
"${data.summary.averageProfit.round()}%", _formatCurrency(data.summary.averageProfit),
'Per pesanan', 'Per pesanan',
Icons.trending_up, Icons.trending_up,
AppColorProfitLoss.success, AppColorProfitLoss.success,
@@ -1156,7 +1154,7 @@ class ProfitLossWidget extends StatelessWidget {
return AppColorProfitLoss.danger; return AppColorProfitLoss.danger;
} }
String _formatCurrency(num amount) { String _formatCurrency(int amount) {
final formatter = NumberFormat.currency( final formatter = NumberFormat.currency(
locale: 'id_ID', locale: 'id_ID',
symbol: 'Rp ', symbol: 'Rp ',
@@ -1165,7 +1163,7 @@ class ProfitLossWidget extends StatelessWidget {
return formatter.format(amount); return formatter.format(amount);
} }
String _formatCurrencyShort(num amount) { String _formatCurrencyShort(int amount) {
if (amount >= 1000000) { if (amount >= 1000000) {
return 'Rp ${(amount / 1000000).round()}M'; return 'Rp ${(amount / 1000000).round()}M';
} else if (amount >= 1000) { } else if (amount >= 1000) {
@@ -1,13 +1,12 @@
import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; 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/extensions/date_time_ext.dart';
import '../../../core/constants/colors.dart'; import '../../../core/constants/colors.dart';
class ReportTitle extends StatelessWidget { class ReportTitle extends StatelessWidget {
final List<Widget>? actionWidget; final List<Widget>? actionWidget;
final String searchDateFormatted; const ReportTitle({super.key, this.actionWidget});
const ReportTitle(
{super.key, this.actionWidget, required this.searchDateFormatted});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -17,7 +16,7 @@ class ReportTitle extends StatelessWidget {
vertical: 10.0, vertical: 10.0,
), ),
width: double.infinity, width: double.infinity,
height: context.deviceHeight * 0.13, height: context.deviceHeight * 0.1,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: AppColors.white, color: AppColors.white,
border: Border( border: Border(
@@ -42,7 +41,7 @@ class ReportTitle extends StatelessWidget {
), ),
), ),
Text( Text(
searchDateFormatted, DateTime.now().toFormattedDate2(),
style: TextStyle( style: TextStyle(
color: AppColors.grey, color: AppColors.grey,
fontSize: 14, fontSize: 14,
@@ -52,8 +51,6 @@ class ReportTitle extends StatelessWidget {
), ),
if (actionWidget != null) if (actionWidget != null)
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: actionWidget!, children: actionWidget!,
), ),
], ],
+2 -35
View File
@@ -1,7 +1,6 @@
import 'package:enaklo_pos/core/components/buttons.dart'; import 'package:enaklo_pos/core/components/buttons.dart';
import 'package:enaklo_pos/core/components/spaces.dart'; import 'package:enaklo_pos/core/components/spaces.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/core/function/app_function.dart';
import 'package:enaklo_pos/data/models/response/order_response_model.dart'; import 'package:enaklo_pos/data/models/response/order_response_model.dart';
import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart';
import 'package:enaklo_pos/presentation/payment/pages/payment_page.dart'; import 'package:enaklo_pos/presentation/payment/pages/payment_page.dart';
@@ -99,8 +98,8 @@ class _SalesPageState extends State<SalesPage> {
}, },
onDateRangeChanged: (start, end) { onDateRangeChanged: (start, end) {
setState(() { setState(() {
startDate = start ?? startDate; startDate = start;
endDate = end ?? endDate; endDate = end;
}); });
context.read<OrderLoaderBloc>().add( context.read<OrderLoaderBloc>().add(
@@ -247,7 +246,6 @@ class _SalesPageState extends State<SalesPage> {
), ),
], ],
if (widget.status == 'completed') if (widget.status == 'completed')
if (orderDetail?.isRefund == false)
Button.outlined( Button.outlined(
onPressed: () { onPressed: () {
context.push(RefundPage( context.push(RefundPage(
@@ -257,37 +255,6 @@ class _SalesPageState extends State<SalesPage> {
label: 'Refund', label: 'Refund',
icon: Icon(Icons.autorenew), icon: Icon(Icons.autorenew),
), ),
SpaceWidth(8),
Button.outlined(
onPressed: () {
if (widget.status == 'completed') {
onPrintRecipt(
context,
order: orderDetail!,
paymentMethod: orderDetail!.payments
?.map((p) => p.paymentMethodName)
.join(', ') ??
"",
nominalBayar: orderDetail?.totalPaid ?? 0,
kembalian: 0,
productQuantity: orderDetail!.orderItems!
.toProductQuantities(),
);
} else {
onPrint(
context,
productQuantity: orderDetail!.orderItems!
.toProductQuantities(),
order: orderDetail!,
);
}
},
label: 'Print',
icon: Icon(
Icons.print,
),
),
SpaceWidth(8),
], ],
), ),
Expanded( Expanded(
@@ -1,9 +1,9 @@
import 'package:enaklo_pos/core/components/components.dart'; import 'package:enaklo_pos/core/components/components.dart';
import 'package:enaklo_pos/core/components/date_range_picker.dart';
import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.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/presentation/sales/blocs/order_loader/order_loader_bloc.dart'; import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart';
import 'package:enaklo_pos/presentation/sales/dialog/filter_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';
@@ -12,7 +12,7 @@ class SalesTitle extends StatelessWidget {
final DateTime startDate; final DateTime startDate;
final DateTime endDate; final DateTime endDate;
final Function(String) onChanged; final Function(String) onChanged;
final void Function(DateTime? start, DateTime? end) onDateRangeChanged; final void Function(DateTime start, DateTime end) onDateRangeChanged;
const SalesTitle( const SalesTitle(
{super.key, {super.key,
@@ -119,12 +119,13 @@ class SalesTitle extends StatelessWidget {
), ),
SpaceWidth(12), SpaceWidth(12),
GestureDetector( GestureDetector(
onTap: () => DateRangePickerModal.show( onTap: () => showDialog(
context: context, context: context,
initialStartDate: startDate, builder: (context) => SalesFilterDialog(
initialEndDate: endDate, startDate: startDate,
primaryColor: AppColors.primary, endDate: endDate,
onChanged: onDateRangeChanged, onDateRangeChanged: onDateRangeChanged,
),
), ),
child: Container( child: Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
@@ -41,7 +41,7 @@ class _SettingPageState extends State<SettingPage> {
vertical: 12.0, vertical: 12.0,
), ),
width: double.infinity, width: double.infinity,
height: context.deviceHeight * 0.12, height: context.deviceHeight * 0.1,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: AppColors.white, color: AppColors.white,
border: Border( border: Border(
@@ -63,7 +63,7 @@ class _SettingsPageState extends State<SettingsPage> {
vertical: 12.0, vertical: 12.0,
), ),
width: double.infinity, width: double.infinity,
height: context.deviceHeight * 0.12, height: context.deviceHeight * 0.1,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: AppColors.white, color: AppColors.white,
border: Border( border: Border(
@@ -22,22 +22,23 @@ class BarPrinterPage extends StatefulWidget {
class _BarPrinterPageState extends State<BarPrinterPage> { class _BarPrinterPageState extends State<BarPrinterPage> {
String selectedPrinter = 'Bluetooth'; String selectedPrinter = 'Bluetooth';
TextEditingController addressController = TextEditingController(); TextEditingController? addressController;
TextEditingController printNameController = TextEditingController(); TextEditingController? printNameController;
String paper = '58'; String paper = '58';
bool isInitialized = false; bool isInitialized = false;
@override @override
void initState() { void initState() {
// TODO: implement initState
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { addressController = TextEditingController();
printNameController = TextEditingController();
context.read<GetPrinterBarBloc>().add(GetPrinterBarEvent.get()); context.read<GetPrinterBarBloc>().add(GetPrinterBarEvent.get());
});
} }
@override @override
void dispose() { void dispose() {
addressController.dispose(); addressController!.dispose();
printNameController.dispose(); printNameController!.dispose();
super.dispose(); super.dispose();
} }
@@ -56,8 +57,8 @@ class _BarPrinterPageState extends State<BarPrinterPage> {
}, },
success: (data) { success: (data) {
if (data != null && !isInitialized) { if (data != null && !isInitialized) {
addressController.text = data.address; addressController!.text = data.address;
printNameController.text = data.name; printNameController!.text = data.name;
selectedPrinter = data.type; selectedPrinter = data.type;
paper = data.paper; paper = data.paper;
isInitialized = true; isInitialized = true;
@@ -111,7 +112,7 @@ class _BarPrinterPageState extends State<BarPrinterPage> {
context: context, context: context,
builder: (context) => DialogSearchPrinter( builder: (context) => DialogSearchPrinter(
onSelected: (value) { onSelected: (value) {
addressController.text = value; addressController!.text = value;
setState(() {}); setState(() {});
}, },
), ),
@@ -119,14 +120,14 @@ class _BarPrinterPageState extends State<BarPrinterPage> {
}, },
label: 'Search') label: 'Search')
: CustomTextField( : CustomTextField(
controller: addressController, controller: addressController!,
label: 'Address', label: 'Address',
showLabel: false, showLabel: false,
), ),
SpaceHeight(16), SpaceHeight(16),
// Textfield for name // Textfield for name
CustomTextField( CustomTextField(
controller: printNameController, controller: printNameController!,
label: 'Print Name', label: 'Print Name',
showLabel: false, showLabel: false,
), ),
@@ -160,26 +161,23 @@ class _BarPrinterPageState extends State<BarPrinterPage> {
// button test print // button test print
Button.outlined( Button.outlined(
onPressed: () async { onPressed: () async {
if (addressController.text.isNotEmpty && if (addressController!.text.isNotEmpty && printNameController!.text.isNotEmpty) {
printNameController.text.isNotEmpty) {
try { try {
// Create a test print model // Create a test print model
final testPrinter = PrintModel( final testPrinter = PrintModel(
code: 'bar', code: 'bar',
name: printNameController.text, name: printNameController!.text,
address: addressController.text, address: addressController!.text,
paper: paper, paper: paper,
type: selectedPrinter, type: selectedPrinter,
); );
// Generate test print data // Generate test print data
final testPrintData = final testPrintData = await PrintDataoutputs.instance.printBar(
await PrintDataoutputs.instance.printBar(
[], // Empty product list for test [], // Empty product list for test
'Test Table', 'Test Table',
'Test Order', 'Test Order',
'Test Cashier', 'Test Cashier',
'Test Customer',
int.parse(paper), int.parse(paper),
'DINE IN', 'DINE IN',
); );
@@ -193,19 +191,17 @@ class _BarPrinterPageState extends State<BarPrinterPage> {
} catch (e) { } catch (e) {
log("Error test printing: $e"); log("Error test printing: $e");
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(content: Text('Error test printing: $e')),
content: Text('Error test printing: $e')),
); );
} }
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(content: Text('Please fill in printer details first')),
content: Text(
'Please fill in printer details first')),
); );
} }
}, },
label: 'Test Print'), label: 'Test Print'
),
SpaceHeight(8), SpaceHeight(8),
// button save // button save
data == null data == null
@@ -233,8 +229,8 @@ class _BarPrinterPageState extends State<BarPrinterPage> {
onPressed: () { onPressed: () {
final printData = PrintModel( final printData = PrintModel(
code: 'bar', code: 'bar',
name: printNameController.text, name: printNameController!.text,
address: addressController.text, address: addressController!.text,
paper: paper, paper: paper,
type: selectedPrinter, type: selectedPrinter,
); );
@@ -278,8 +274,8 @@ class _BarPrinterPageState extends State<BarPrinterPage> {
final printData = PrintModel( final printData = PrintModel(
id: data.id, id: data.id,
code: 'bar', code: 'bar',
name: printNameController.text, name: printNameController!.text,
address: addressController.text, address: addressController!.text,
paper: paper, paper: paper,
type: selectedPrinter, type: selectedPrinter,
); );
@@ -22,22 +22,23 @@ class CheckerPrinterPage extends StatefulWidget {
class _CheckerPrinterPageState extends State<CheckerPrinterPage> { class _CheckerPrinterPageState extends State<CheckerPrinterPage> {
String selectedPrinter = 'Bluetooth'; String selectedPrinter = 'Bluetooth';
TextEditingController addressController = TextEditingController(); TextEditingController? addressController;
TextEditingController printNameController = TextEditingController(); TextEditingController? printNameController;
String paper = '58'; String paper = '58';
bool isInitialized = false; bool isInitialized = false;
@override @override
void initState() { void initState() {
// TODO: implement initState
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { addressController = TextEditingController();
printNameController = TextEditingController();
context.read<GetPrinterCheckerBloc>().add(GetPrinterCheckerEvent.get()); context.read<GetPrinterCheckerBloc>().add(GetPrinterCheckerEvent.get());
});
} }
@override @override
void dispose() { void dispose() {
addressController.dispose(); addressController!.dispose();
printNameController.dispose(); printNameController!.dispose();
isInitialized = false; isInitialized = false;
super.dispose(); super.dispose();
} }
@@ -58,8 +59,8 @@ class _CheckerPrinterPageState extends State<CheckerPrinterPage> {
success: (data) { success: (data) {
log("Checker Printer: ${data?.toMap()}"); log("Checker Printer: ${data?.toMap()}");
if (data != null && !isInitialized) { if (data != null && !isInitialized) {
addressController.text = data.address; addressController!.text = data.address;
printNameController.text = data.name; printNameController!.text = data.name;
selectedPrinter = data.type; selectedPrinter = data.type;
paper = data.paper; paper = data.paper;
isInitialized = true; isInitialized = true;
@@ -113,7 +114,7 @@ class _CheckerPrinterPageState extends State<CheckerPrinterPage> {
context: context, context: context,
builder: (context) => DialogSearchPrinter( builder: (context) => DialogSearchPrinter(
onSelected: (value) { onSelected: (value) {
addressController.text = value; addressController!.text = value;
setState(() {}); setState(() {});
}, },
), ),
@@ -121,14 +122,14 @@ class _CheckerPrinterPageState extends State<CheckerPrinterPage> {
}, },
label: 'Search') label: 'Search')
: CustomTextField( : CustomTextField(
controller: addressController, controller: addressController!,
label: 'Address', label: 'Address',
showLabel: false, showLabel: false,
), ),
SpaceHeight(16), SpaceHeight(16),
// Textfield for name // Textfield for name
CustomTextField( CustomTextField(
controller: printNameController, controller: printNameController!,
label: 'Print Name', label: 'Print Name',
showLabel: false, showLabel: false,
), ),
@@ -162,27 +163,23 @@ class _CheckerPrinterPageState extends State<CheckerPrinterPage> {
// button test print // button test print
Button.outlined( Button.outlined(
onPressed: () async { onPressed: () async {
if (addressController.text.isNotEmpty && if (addressController!.text.isNotEmpty && printNameController!.text.isNotEmpty) {
printNameController.text.isNotEmpty) {
try { try {
// Create a test print model // Create a test print model
final testPrinter = PrintModel( final testPrinter = PrintModel(
code: 'checker', code: 'checker',
name: printNameController.text, name: printNameController!.text,
address: addressController.text, address: addressController!.text,
paper: paper, paper: paper,
type: selectedPrinter, type: selectedPrinter,
); );
// Generate test print data // Generate test print data
final testPrintData = await PrintDataoutputs final testPrintData = await PrintDataoutputs.instance.printChecker(
.instance
.printChecker(
[], // Empty product list for test [], // Empty product list for test
'Test Table', 'Test Table',
'Test Order', 'Test Order',
'Test Cashier', 'Test Cashier',
'Test Customer',
int.parse(paper), int.parse(paper),
'DINE IN', 'DINE IN',
); );
@@ -196,19 +193,17 @@ class _CheckerPrinterPageState extends State<CheckerPrinterPage> {
} catch (e) { } catch (e) {
log("Error test printing: $e"); log("Error test printing: $e");
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(content: Text('Error test printing: $e')),
content: Text('Error test printing: $e')),
); );
} }
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(content: Text('Please fill in printer details first')),
content: Text(
'Please fill in printer details first')),
); );
} }
}, },
label: 'Test Print'), label: 'Test Print'
),
SpaceHeight(8), SpaceHeight(8),
// button save // button save
data == null data == null
@@ -236,8 +231,8 @@ class _CheckerPrinterPageState extends State<CheckerPrinterPage> {
onPressed: () { onPressed: () {
final printData = PrintModel( final printData = PrintModel(
code: 'checker', code: 'checker',
name: printNameController.text, name: printNameController!.text,
address: addressController.text, address: addressController!.text,
paper: paper, paper: paper,
type: selectedPrinter, type: selectedPrinter,
); );
@@ -281,8 +276,8 @@ class _CheckerPrinterPageState extends State<CheckerPrinterPage> {
final printData = PrintModel( final printData = PrintModel(
id: data.id, id: data.id,
code: 'checker', code: 'checker',
name: printNameController.text, name: printNameController!.text,
address: addressController.text, address: addressController!.text,
paper: paper, paper: paper,
type: selectedPrinter, type: selectedPrinter,
); );
@@ -22,22 +22,23 @@ class KitchenPrinterPage extends StatefulWidget {
class _KitchenPrinterPageState extends State<KitchenPrinterPage> { class _KitchenPrinterPageState extends State<KitchenPrinterPage> {
String selectedPrinter = 'Bluetooth'; String selectedPrinter = 'Bluetooth';
TextEditingController addressController = TextEditingController(); TextEditingController? addressController;
TextEditingController printNameController = TextEditingController(); TextEditingController? printNameController;
String paper = '58'; String paper = '58';
bool isInitialized = false; bool isInitialized = false;
@override @override
void initState() { void initState() {
// TODO: implement initState
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { addressController = TextEditingController();
printNameController = TextEditingController();
context.read<GetPrinterKitchenBloc>().add(GetPrinterKitchenEvent.get()); context.read<GetPrinterKitchenBloc>().add(GetPrinterKitchenEvent.get());
});
} }
@override @override
void dispose() { void dispose() {
addressController.dispose(); addressController!.dispose();
printNameController.dispose(); printNameController!.dispose();
super.dispose(); super.dispose();
} }
@@ -56,8 +57,8 @@ class _KitchenPrinterPageState extends State<KitchenPrinterPage> {
}, },
success: (data) { success: (data) {
if (data != null && !isInitialized) { if (data != null && !isInitialized) {
addressController.text = data.address; addressController!.text = data.address;
printNameController.text = data.name; printNameController!.text = data.name;
selectedPrinter = data.type; selectedPrinter = data.type;
paper = data.paper; paper = data.paper;
isInitialized = true; isInitialized = true;
@@ -111,7 +112,7 @@ class _KitchenPrinterPageState extends State<KitchenPrinterPage> {
context: context, context: context,
builder: (context) => DialogSearchPrinter( builder: (context) => DialogSearchPrinter(
onSelected: (value) { onSelected: (value) {
addressController.text = value; addressController!.text = value;
setState(() {}); setState(() {});
}, },
), ),
@@ -119,14 +120,14 @@ class _KitchenPrinterPageState extends State<KitchenPrinterPage> {
}, },
label: 'Search') label: 'Search')
: CustomTextField( : CustomTextField(
controller: addressController, controller: addressController!,
label: 'Address', label: 'Address',
showLabel: false, showLabel: false,
), ),
SpaceHeight(16), SpaceHeight(16),
// Textfield for name // Textfield for name
CustomTextField( CustomTextField(
controller: printNameController, controller: printNameController!,
label: 'Print Name', label: 'Print Name',
showLabel: false, showLabel: false,
), ),
@@ -160,27 +161,23 @@ class _KitchenPrinterPageState extends State<KitchenPrinterPage> {
// button test print // button test print
Button.outlined( Button.outlined(
onPressed: () async { onPressed: () async {
if (addressController.text.isNotEmpty && if (addressController!.text.isNotEmpty && printNameController!.text.isNotEmpty) {
printNameController.text.isNotEmpty) {
try { try {
// Create a test print model // Create a test print model
final testPrinter = PrintModel( final testPrinter = PrintModel(
code: 'kitchen', code: 'kitchen',
name: printNameController.text, name: printNameController!.text,
address: addressController.text, address: addressController!.text,
paper: paper, paper: paper,
type: selectedPrinter, type: selectedPrinter,
); );
// Generate test print data // Generate test print data
final testPrintData = await PrintDataoutputs final testPrintData = await PrintDataoutputs.instance.printKitchen(
.instance
.printKitchen(
[], // Empty product list for test [], // Empty product list for test
'Test Table', 'Test Table',
'Test Order', 'Test Order',
'Test Cashier', 'Test Cashier',
'Test Customer',
int.parse(paper), int.parse(paper),
'DINE IN', 'DINE IN',
); );
@@ -194,19 +191,17 @@ class _KitchenPrinterPageState extends State<KitchenPrinterPage> {
} catch (e) { } catch (e) {
log("Error test printing: $e"); log("Error test printing: $e");
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(content: Text('Error test printing: $e')),
content: Text('Error test printing: $e')),
); );
} }
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(content: Text('Please fill in printer details first')),
content: Text(
'Please fill in printer details first')),
); );
} }
}, },
label: 'Test Print'), label: 'Test Print'
),
SpaceHeight(8), SpaceHeight(8),
// button save // button save
data == null data == null
@@ -234,8 +229,8 @@ class _KitchenPrinterPageState extends State<KitchenPrinterPage> {
onPressed: () { onPressed: () {
final printData = PrintModel( final printData = PrintModel(
code: 'kitchen', code: 'kitchen',
name: printNameController.text, name: printNameController!.text,
address: addressController.text, address: addressController!.text,
paper: paper, paper: paper,
type: selectedPrinter, type: selectedPrinter,
); );
@@ -279,8 +274,8 @@ class _KitchenPrinterPageState extends State<KitchenPrinterPage> {
final printData = PrintModel( final printData = PrintModel(
id: data.id, id: data.id,
code: 'kitchen', code: 'kitchen',
name: printNameController.text, name: printNameController!.text,
address: addressController.text, address: addressController!.text,
paper: paper, paper: paper,
type: selectedPrinter, type: selectedPrinter,
); );
@@ -23,22 +23,23 @@ class ReceiptPrinterPage extends StatefulWidget {
class _ReceiptPrinterPageState extends State<ReceiptPrinterPage> { class _ReceiptPrinterPageState extends State<ReceiptPrinterPage> {
String selectedPrinter = 'Bluetooth'; String selectedPrinter = 'Bluetooth';
TextEditingController addressController = TextEditingController(); TextEditingController? addressController;
TextEditingController printNameController = TextEditingController(); TextEditingController? printNameController;
String paper = '58'; String paper = '58';
bool isInitialized = false; bool isInitialized = false;
@override @override
void initState() { void initState() {
// TODO: implement initState
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { addressController = TextEditingController();
printNameController = TextEditingController();
context.read<GetPrinterReceiptBloc>().add(GetPrinterReceiptEvent.get()); context.read<GetPrinterReceiptBloc>().add(GetPrinterReceiptEvent.get());
});
} }
@override @override
void dispose() { void dispose() {
addressController.dispose(); addressController!.dispose();
printNameController.dispose(); printNameController!.dispose();
super.dispose(); super.dispose();
} }
@@ -57,8 +58,8 @@ class _ReceiptPrinterPageState extends State<ReceiptPrinterPage> {
}, },
success: (data) { success: (data) {
if (data != null && !isInitialized) { if (data != null && !isInitialized) {
addressController.text = data.address; addressController!.text = data.address;
printNameController.text = data.name; printNameController!.text = data.name;
selectedPrinter = data.type; selectedPrinter = data.type;
paper = data.paper; paper = data.paper;
isInitialized = true; isInitialized = true;
@@ -112,7 +113,7 @@ class _ReceiptPrinterPageState extends State<ReceiptPrinterPage> {
context: context, context: context,
builder: (context) => DialogSearchPrinter( builder: (context) => DialogSearchPrinter(
onSelected: (value) { onSelected: (value) {
addressController.text = value; addressController!.text = value;
setState(() {}); setState(() {});
}, },
), ),
@@ -120,14 +121,14 @@ class _ReceiptPrinterPageState extends State<ReceiptPrinterPage> {
}, },
label: 'Search') label: 'Search')
: CustomTextField( : CustomTextField(
controller: addressController, controller: addressController!,
label: 'Address', label: 'Address',
showLabel: false, showLabel: false,
), ),
SpaceHeight(16), SpaceHeight(16),
// Textfield for name // Textfield for name
CustomTextField( CustomTextField(
controller: printNameController, controller: printNameController!,
label: 'Print Name', label: 'Print Name',
showLabel: false, showLabel: false,
), ),
@@ -161,8 +162,8 @@ class _ReceiptPrinterPageState extends State<ReceiptPrinterPage> {
// button test print // button test print
Button.outlined( Button.outlined(
onPressed: () async { onPressed: () async {
if (addressController.text.isNotEmpty && if (addressController!.text.isNotEmpty &&
printNameController.text.isNotEmpty) { printNameController!.text.isNotEmpty) {
try { try {
// Get actual tax and service charge settings // Get actual tax and service charge settings
final settingsLocalDatasource = final settingsLocalDatasource =
@@ -176,8 +177,8 @@ class _ReceiptPrinterPageState extends State<ReceiptPrinterPage> {
// Create a test print model // Create a test print model
final testPrinter = PrintModel( final testPrinter = PrintModel(
code: 'receipt', code: 'receipt',
name: printNameController.text, name: printNameController!.text,
address: addressController.text, address: addressController!.text,
paper: paper, paper: paper,
type: selectedPrinter, type: selectedPrinter,
); );
@@ -252,8 +253,8 @@ class _ReceiptPrinterPageState extends State<ReceiptPrinterPage> {
onPressed: () { onPressed: () {
final printData = PrintModel( final printData = PrintModel(
code: 'receipt', code: 'receipt',
name: printNameController.text, name: printNameController!.text,
address: addressController.text, address: addressController!.text,
paper: paper, paper: paper,
type: selectedPrinter, type: selectedPrinter,
); );
@@ -297,8 +298,8 @@ class _ReceiptPrinterPageState extends State<ReceiptPrinterPage> {
final printData = PrintModel( final printData = PrintModel(
id: data.id, id: data.id,
code: 'receipt', code: 'receipt',
name: printNameController.text, name: printNameController!.text,
address: addressController.text, address: addressController!.text,
paper: paper, paper: paper,
type: selectedPrinter, type: selectedPrinter,
); );
@@ -23,7 +23,7 @@ class SettingsTitle extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
height: context.deviceHeight * 0.12, height: context.deviceHeight * 0.1,
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,
@@ -23,23 +23,22 @@ class TicketPrinterPage extends StatefulWidget {
class _TicketPrinterPageState extends State<TicketPrinterPage> { class _TicketPrinterPageState extends State<TicketPrinterPage> {
String selectedPrinter = 'Bluetooth'; String selectedPrinter = 'Bluetooth';
TextEditingController addressController = TextEditingController(); TextEditingController? addressController;
TextEditingController printNameController = TextEditingController(); TextEditingController? printNameController;
String paper = '58'; String paper = '58';
bool isInitialized = false; bool isInitialized = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { addressController = TextEditingController();
printNameController = TextEditingController();
context.read<GetPrinterTicketBloc>().add(GetPrinterTicketEvent.get()); context.read<GetPrinterTicketBloc>().add(GetPrinterTicketEvent.get());
});
} }
@override @override
void dispose() { void dispose() {
addressController.dispose(); addressController!.dispose();
printNameController.dispose(); printNameController!.dispose();
super.dispose(); super.dispose();
} }
@@ -58,8 +57,8 @@ class _TicketPrinterPageState extends State<TicketPrinterPage> {
}, },
success: (data) { success: (data) {
if (data != null && !isInitialized) { if (data != null && !isInitialized) {
addressController.text = data.address; addressController!.text = data.address;
printNameController.text = data.name; printNameController!.text = data.name;
selectedPrinter = data.type; selectedPrinter = data.type;
paper = data.paper; paper = data.paper;
isInitialized = true; isInitialized = true;
@@ -113,7 +112,7 @@ class _TicketPrinterPageState extends State<TicketPrinterPage> {
context: context, context: context,
builder: (context) => DialogSearchPrinter( builder: (context) => DialogSearchPrinter(
onSelected: (value) { onSelected: (value) {
addressController.text = value; addressController!.text = value;
setState(() {}); setState(() {});
}, },
), ),
@@ -121,14 +120,14 @@ class _TicketPrinterPageState extends State<TicketPrinterPage> {
}, },
label: 'Search') label: 'Search')
: CustomTextField( : CustomTextField(
controller: addressController, controller: addressController!,
label: 'Address', label: 'Address',
showLabel: false, showLabel: false,
), ),
SpaceHeight(16), SpaceHeight(16),
// Textfield for name // Textfield for name
CustomTextField( CustomTextField(
controller: printNameController, controller: printNameController!,
label: 'Print Name', label: 'Print Name',
showLabel: false, showLabel: false,
), ),
@@ -162,14 +161,14 @@ class _TicketPrinterPageState extends State<TicketPrinterPage> {
// button test print // button test print
Button.outlined( Button.outlined(
onPressed: () async { onPressed: () async {
if (addressController.text.isNotEmpty && if (addressController!.text.isNotEmpty &&
printNameController.text.isNotEmpty) { printNameController!.text.isNotEmpty) {
try { try {
// Create a test print model // Create a test print model
final testPrinter = PrintModel( final testPrinter = PrintModel(
code: 'ticket', code: 'ticket',
name: printNameController.text, name: printNameController!.text,
address: addressController.text, address: addressController!.text,
paper: paper, paper: paper,
type: selectedPrinter, type: selectedPrinter,
); );
@@ -232,8 +231,8 @@ class _TicketPrinterPageState extends State<TicketPrinterPage> {
onPressed: () { onPressed: () {
final printData = PrintModel( final printData = PrintModel(
code: 'ticket', code: 'ticket',
name: printNameController.text, name: printNameController!.text,
address: addressController.text, address: addressController!.text,
paper: paper, paper: paper,
type: selectedPrinter, type: selectedPrinter,
); );
@@ -277,8 +276,8 @@ class _TicketPrinterPageState extends State<TicketPrinterPage> {
final printData = PrintModel( final printData = PrintModel(
id: data.id, id: data.id,
code: 'ticket', code: 'ticket',
name: printNameController.text, name: printNameController!.text,
address: addressController.text, address: addressController!.text,
paper: paper, paper: paper,
type: selectedPrinter, type: selectedPrinter,
); );
@@ -932,7 +932,7 @@ class _SplitBillPageState extends State<SplitBillPage> {
Order splitOrder = Order( Order splitOrder = Order(
id: widget.order.id, id: widget.order.id,
orderNumber: widget.order.orderNumber, orderNumber: widget.order.orderNumber,
orderItems: [], // Keep all items for reference orderItems: getOrderItemPending(), // Keep all items for reference
subtotal: splitAmount, subtotal: splitAmount,
totalAmount: splitAmount, totalAmount: splitAmount,
taxAmount: 0, // You might want to calculate proportional values taxAmount: 0, // You might want to calculate proportional values
@@ -28,7 +28,116 @@ class SuccessOrderPage extends StatefulWidget {
State<SuccessOrderPage> createState() => _SuccessOrderPageState(); State<SuccessOrderPage> createState() => _SuccessOrderPageState();
} }
class _SuccessOrderPageState extends State<SuccessOrderPage> { class _SuccessOrderPageState extends State<SuccessOrderPage>
with TickerProviderStateMixin {
late AnimationController _mainController;
late AnimationController _successController;
late AnimationController _cardController;
late AnimationController _floatingController;
late Animation<double> _fadeInAnimation;
late Animation<double> _scaleAnimation;
late Animation<double> _successIconAnimation;
late Animation<Offset> _slideUpAnimation;
late Animation<double> _floatingAnimation;
late Animation<double> _shimmerAnimation;
@override
void initState() {
super.initState();
_mainController = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
);
_successController = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
);
_cardController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_floatingController = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
)..repeat(reverse: true);
_fadeInAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _mainController,
curve: const Interval(0.0, 0.6, curve: Curves.easeOut),
));
_scaleAnimation = Tween<double>(
begin: 0.8,
end: 1.0,
).animate(CurvedAnimation(
parent: _mainController,
curve: const Interval(0.2, 0.8, curve: Curves.elasticOut),
));
_successIconAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _successController,
curve: Curves.elasticOut,
));
_slideUpAnimation = Tween<Offset>(
begin: const Offset(0.0, 0.3),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _cardController,
curve: Curves.easeOutCubic,
));
_floatingAnimation = Tween<double>(
begin: -5.0,
end: 5.0,
).animate(CurvedAnimation(
parent: _floatingController,
curve: Curves.easeInOut,
));
_shimmerAnimation = Tween<double>(
begin: -1.0,
end: 2.0,
).animate(CurvedAnimation(
parent: _mainController,
curve: Curves.easeInOut,
));
// Start animations with staggered delays
_startAnimations();
}
void _startAnimations() async {
await Future.delayed(const Duration(milliseconds: 300));
_mainController.forward();
await Future.delayed(const Duration(milliseconds: 500));
_successController.forward();
await Future.delayed(const Duration(milliseconds: 700));
_cardController.forward();
}
@override
void dispose() {
_mainController.dispose();
_successController.dispose();
_cardController.dispose();
_floatingController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@@ -46,6 +155,10 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
], ],
), ),
), ),
child: FadeTransition(
opacity: _fadeInAnimation,
child: ScaleTransition(
scale: _scaleAnimation,
child: Padding( child: Padding(
padding: const EdgeInsets.all(24.0), padding: const EdgeInsets.all(24.0),
child: Row( child: Row(
@@ -68,11 +181,15 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
), ),
), ),
), ),
),
),
); );
} }
Widget _buildLeftPanel() { Widget _buildLeftPanel() {
return Container( return SlideTransition(
position: _slideUpAnimation,
child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
@@ -87,10 +204,6 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
child: Column( child: Column(
children: [ children: [
// Success Header with Glassmorphism Effect // Success Header with Glassmorphism Effect
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(32.0), padding: const EdgeInsets.all(32.0),
@@ -109,8 +222,15 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
), ),
child: Column( child: Column(
children: [ children: [
// Success Icon // Animated Success Icon with Floating Effect
Container( AnimatedBuilder(
animation: _floatingAnimation,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, _floatingAnimation.value),
child: ScaleTransition(
scale: _successIconAnimation,
child: Container(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
@@ -136,22 +256,49 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
color: Colors.white, color: Colors.white,
), ),
), ),
),
);
},
),
const SizedBox(height: 24), const SizedBox(height: 24),
// Success Title // Success Title with Shimmer Effect
const Text( FadeTransition(
opacity: _fadeInAnimation,
child: ShaderMask(
shaderCallback: (bounds) {
return LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: const [
AppColors.primary,
Colors.amber,
AppColors.primary,
],
stops: [
_shimmerAnimation.value - 1,
_shimmerAnimation.value,
_shimmerAnimation.value + 1,
],
).createShader(bounds);
},
child: const Text(
'Pesanan Berhasil!', 'Pesanan Berhasil!',
style: TextStyle( style: TextStyle(
fontSize: 26, fontSize: 26,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: AppColors.primary, color: Colors.white,
),
),
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Container( FadeTransition(
opacity: _fadeInAnimation,
child: Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
vertical: 8, vertical: 8,
@@ -170,11 +317,14 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
),
], ],
), ),
), ),
// Order Information Section // Order Information Section
Padding( Expanded(
child: Padding(
padding: const EdgeInsets.all(24.0), padding: const EdgeInsets.all(24.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -196,35 +346,38 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
const SizedBox(height: 16), const SizedBox(height: 16),
// Order Details Grid // Order Details Grid
Column( Expanded(
child: Column(
children: [ children: [
_buildInfoRow( _buildInfoRow(
icon: Icons.receipt_long_outlined, icon: Icons.receipt_long_outlined,
label: 'No. Pesanan', label: 'No. Pesanan',
value: widget.order.orderNumber ?? "-", value: widget.order.orderNumber ?? "-",
delay: 0.3,
), ),
const SizedBox(height: 12),
_buildInfoRow(
icon: Icons.wallet_outlined,
label: 'Metode Pembayaran',
value: widget.paymentMethod,
),
if (widget.order.tableNumber != "") ...[
const SizedBox(height: 12), const SizedBox(height: 12),
_buildInfoRow( _buildInfoRow(
icon: Icons.table_restaurant_outlined, icon: Icons.table_restaurant_outlined,
label: 'No. Meja', label: 'No. Meja',
value: widget.order.tableNumber ?? "-", value: widget.order.tableNumber ?? "-",
delay: 0.4,
), ),
],
const SizedBox(height: 12), const SizedBox(height: 12),
_buildInfoRow( _buildInfoRow(
icon: Icons.access_time_rounded, icon: Icons.access_time_rounded,
label: 'Waktu', label: 'Waktu',
value: (widget.order.createdAt ?? DateTime.now()) value: (widget.order.createdAt ?? DateTime.now())
.toFormattedDate3(), .toFormattedDate3(),
delay: 0.5,
), ),
], const SizedBox(height: 12),
_buildInfoRow(
icon: Icons.check_circle_outline,
label: 'Status Pembayaran',
value: 'Lunas',
delay: 0.6,
valueColor: Colors.green,
showBadge: true,
), ),
], ],
), ),
@@ -238,11 +391,14 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
_buildBottomSection(), _buildBottomSection(),
], ],
), ),
),
); );
} }
Widget _buildRightPanel() { Widget _buildRightPanel() {
return Container( return SlideTransition(
position: _slideUpAnimation,
child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
@@ -275,7 +431,9 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
), ),
child: Row( child: Row(
children: [ children: [
Container( ScaleTransition(
scale: _successIconAnimation,
child: Container(
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
@@ -292,32 +450,41 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
size: 28, size: 28,
), ),
), ),
),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( FadeTransition(
opacity: _fadeInAnimation,
child: const Text(
'Detail Pesanan', 'Detail Pesanan',
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( FadeTransition(
opacity: _fadeInAnimation,
child: Text(
'Ringkasan item yang dipesan', 'Ringkasan item yang dipesan',
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
color: Colors.grey.shade600, color: Colors.grey.shade600,
), ),
), ),
),
], ],
), ),
), ),
Container( ScaleTransition(
padding: scale: _scaleAnimation,
const EdgeInsets.symmetric(horizontal: 16, vertical: 10), child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
@@ -343,6 +510,7 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
), ),
), ),
), ),
),
], ],
), ),
), ),
@@ -352,7 +520,8 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
child: ListView.separated( child: ListView.separated(
padding: const EdgeInsets.all(24.0), padding: const EdgeInsets.all(24.0),
itemCount: widget.productQuantity.length, itemCount: widget.productQuantity.length,
separatorBuilder: (context, index) => const SizedBox(height: 12), separatorBuilder: (context, index) =>
const SizedBox(height: 12),
itemBuilder: (context, index) { itemBuilder: (context, index) {
return _buildProductCard(index); return _buildProductCard(index);
}, },
@@ -363,6 +532,7 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
_buildSummaryFooter(), _buildSummaryFooter(),
], ],
), ),
),
); );
} }
@@ -371,7 +541,16 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
final totalPrice = (item.product.price ?? 0) * item.quantity + final totalPrice = (item.product.price ?? 0) * item.quantity +
(item.variant?.priceModifier ?? 0); (item.variant?.priceModifier ?? 0);
return Container( return TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
duration: Duration(milliseconds: 600 + (index * 100)),
curve: Curves.easeOutCubic,
builder: (context, animation, child) {
return Transform.translate(
offset: Offset(0, 20 * (1 - animation)),
child: Opacity(
opacity: animation,
child: Container(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
@@ -398,7 +577,9 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
child: Row( child: Row(
children: [ children: [
// Enhanced Product Image // Enhanced Product Image
Container( Hero(
tag: 'product_${index}',
child: Container(
width: 70, width: 70,
height: 70, height: 70,
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -425,6 +606,7 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
size: 28, size: 28,
), ),
), ),
),
const SizedBox(width: 16), const SizedBox(width: 16),
@@ -475,8 +657,8 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Container( Container(
padding: padding: const EdgeInsets.symmetric(
const EdgeInsets.symmetric(horizontal: 12, vertical: 8), horizontal: 12, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
@@ -515,16 +697,23 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
), ),
], ],
), ),
),
),
);
},
); );
} }
Widget _buildSectionTitle(String title) { Widget _buildSectionTitle(String title) {
return Text( return FadeTransition(
opacity: _fadeInAnimation,
child: Text(
title, title,
style: const TextStyle( style: const TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
),
); );
} }
@@ -534,7 +723,9 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
required String value, required String value,
required List<Color> gradient, required List<Color> gradient,
}) { }) {
return Container( return ScaleTransition(
scale: _scaleAnimation,
child: Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -581,6 +772,7 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
), ),
], ],
), ),
),
); );
} }
@@ -588,10 +780,20 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
required IconData icon, required IconData icon,
required String label, required String label,
required String value, required String value,
required double delay,
Color? valueColor, Color? valueColor,
bool showBadge = false, bool showBadge = false,
}) { }) {
return Row( return TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
duration: Duration(milliseconds: (800 + delay * 1000).round()),
curve: Curves.easeOutCubic,
builder: (context, animation, child) {
return Transform.translate(
offset: Offset(0, 10 * (1 - animation)),
child: Opacity(
opacity: animation,
child: Row(
children: [ children: [
Icon( Icon(
icon, icon,
@@ -648,6 +850,10 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
), ),
), ),
], ],
),
),
);
},
); );
} }
@@ -671,7 +877,9 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
child: Column( child: Column(
children: [ children: [
// Total Amount with Enhanced Styling // Total Amount with Enhanced Styling
Container( ScaleTransition(
scale: _scaleAnimation,
child: Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
@@ -699,7 +907,9 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
), ),
), ),
Text( Text(
(widget.order.totalAmount ?? 0).toString().currencyFormatRpV2, (widget.order.totalAmount ?? 0)
.toString()
.currencyFormatRpV2,
style: const TextStyle( style: const TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -709,6 +919,7 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
], ],
), ),
), ),
),
const SizedBox(height: 24), const SizedBox(height: 24),
@@ -716,6 +927,13 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
Row( Row(
children: [ children: [
Expanded( Expanded(
child: TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 800),
curve: Curves.easeOutCubic,
builder: (context, animation, child) {
return Transform.scale(
scale: animation,
child: Container( child: Container(
height: 50, height: 50,
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -746,9 +964,19 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
), ),
), ),
), ),
);
},
),
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 1000),
curve: Curves.easeOutCubic,
builder: (context, animation, child) {
return Transform.scale(
scale: animation,
child: Container( child: Container(
height: 50, height: 50,
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -784,7 +1012,6 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
: widget.order.totalAmount ?? 0, : widget.order.totalAmount ?? 0,
kembalian: widget.nominalBayar - kembalian: widget.nominalBayar -
(widget.order.totalAmount ?? 0), (widget.order.totalAmount ?? 0),
productQuantity: widget.productQuantity,
); );
onPrint( onPrint(
context, context,
@@ -814,6 +1041,9 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
), ),
), ),
), ),
);
},
),
), ),
], ],
), ),
@@ -823,7 +1053,9 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
} }
Widget _buildSummaryFooter() { Widget _buildSummaryFooter() {
return Container( return SlideTransition(
position: _slideUpAnimation,
child: Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(24.0), padding: const EdgeInsets.all(24.0),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -857,7 +1089,9 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
), ),
// Subtotal Row // Subtotal Row
Row( FadeTransition(
opacity: _fadeInAnimation,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row( Row(
@@ -879,7 +1113,9 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
], ],
), ),
Text( Text(
(widget.order.totalAmount ?? 0).toString().currencyFormatRpV2, (widget.order.totalAmount ?? 0)
.toString()
.currencyFormatRpV2,
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -887,11 +1123,14 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
), ),
], ],
), ),
),
const SizedBox(height: 16), const SizedBox(height: 16),
// Total Payment Row with Enhanced Styling // Total Payment Row with Enhanced Styling
Container( ScaleTransition(
scale: _scaleAnimation,
child: Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
@@ -970,8 +1209,10 @@ class _SuccessOrderPageState extends State<SuccessOrderPage> {
], ],
), ),
), ),
),
], ],
), ),
),
); );
} }
} }
@@ -108,10 +108,6 @@ class _SuccessPaymentPageState extends State<SuccessPaymentPage> {
child: Column( child: Column(
children: [ children: [
// Success Header // Success Header
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(32.0), padding: const EdgeInsets.all(32.0),
@@ -194,8 +190,10 @@ class _SuccessPaymentPageState extends State<SuccessPaymentPage> {
], ],
), ),
), ),
// Order Information Section // Order Information Section
Padding( Expanded(
child: Padding(
padding: const EdgeInsets.all(24.0), padding: const EdgeInsets.all(24.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -217,7 +215,8 @@ class _SuccessPaymentPageState extends State<SuccessPaymentPage> {
const SizedBox(height: 16), const SizedBox(height: 16),
// Order Details // Order Details
Column( Expanded(
child: Column(
children: [ children: [
_buildInfoRow( _buildInfoRow(
icon: Icons.receipt_long_outlined, icon: Icons.receipt_long_outlined,
@@ -226,9 +225,9 @@ class _SuccessPaymentPageState extends State<SuccessPaymentPage> {
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildInfoRow( _buildInfoRow(
icon: Icons.receipt_long_outlined, icon: Icons.table_restaurant_outlined,
label: 'Metode Pembayaran', label: 'No. Meja',
value: widget.paymentMethod, value: order.tableNumber ?? "-",
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildInfoRow( _buildInfoRow(
@@ -247,8 +246,6 @@ class _SuccessPaymentPageState extends State<SuccessPaymentPage> {
), ),
], ],
), ),
],
),
), ),
], ],
), ),
@@ -801,7 +798,6 @@ class _SuccessPaymentPageState extends State<SuccessPaymentPage> {
: order.totalAmount ?? 0, : order.totalAmount ?? 0,
kembalian: kembalian:
widget.nominalBayar - (order.totalAmount ?? 0), widget.nominalBayar - (order.totalAmount ?? 0),
productQuantity: widget.productQuantity,
); );
}, },
child: const Row( child: const Row(
+2 -14
View File
@@ -1,10 +1,7 @@
import 'dart:developer';
import 'package:enaklo_pos/core/components/flushbar.dart'; import 'package:enaklo_pos/core/components/flushbar.dart';
import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/data/models/response/table_model.dart'; import 'package:enaklo_pos/data/models/response/table_model.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart'; import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart';
import 'package:enaklo_pos/presentation/table/blocs/change_position_table/change_position_table_bloc.dart'; import 'package:enaklo_pos/presentation/table/blocs/change_position_table/change_position_table_bloc.dart';
import 'package:enaklo_pos/presentation/table/blocs/create_table/create_table_bloc.dart'; import 'package:enaklo_pos/presentation/table/blocs/create_table/create_table_bloc.dart';
@@ -35,8 +32,7 @@ TableStatus parseStatus(String? status) {
} }
class TablePage extends StatefulWidget { class TablePage extends StatefulWidget {
final List<ProductQuantity>? items; const TablePage({super.key});
const TablePage({super.key, this.items});
@override @override
State<TablePage> createState() => _TablePageState(); State<TablePage> createState() => _TablePageState();
@@ -65,7 +61,6 @@ class _TablePageState extends State<TablePage> {
@override @override
void initState() { void initState() {
log("ITEM COUNT: ${widget.items?.length}");
context.read<GetTableBloc>().add(const GetTableEvent.getTables()); context.read<GetTableBloc>().add(const GetTableEvent.getTables());
super.initState(); super.initState();
} }
@@ -79,12 +74,6 @@ class _TablePageState extends State<TablePage> {
appBar: AppBar( appBar: AppBar(
title: const Text("Layout Meja"), title: const Text("Layout Meja"),
actions: [ actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
context.read<GetTableBloc>().add(const GetTableEvent.getTables());
},
),
BlocListener<CreateTableBloc, CreateTableState>( BlocListener<CreateTableBloc, CreateTableState>(
listener: (context, state) { listener: (context, state) {
state.maybeWhen( state.maybeWhen(
@@ -402,9 +391,8 @@ class _TablePageState extends State<TablePage> {
), ),
onPressed: () { onPressed: () {
if (selectedTable?.status == 'available') { if (selectedTable?.status == 'available') {
context.pushReplacement(DashboardPage( context.push(DashboardPage(
table: selectedTable!, table: selectedTable!,
items: widget.items,
)); ));
} else {} } else {}
}, },
@@ -1,6 +1,5 @@
import 'dart:developer'; import 'dart:developer';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/components/components.dart'; import 'package:enaklo_pos/core/components/components.dart';
@@ -16,15 +15,13 @@ import 'package:enaklo_pos/presentation/home/bloc/status_table/status_table_bloc
import 'package:enaklo_pos/presentation/home/pages/home_page.dart'; import 'package:enaklo_pos/presentation/home/pages/home_page.dart';
import 'package:enaklo_pos/presentation/table/blocs/get_table/get_table_bloc.dart'; import 'package:enaklo_pos/presentation/table/blocs/get_table/get_table_bloc.dart';
import 'package:enaklo_pos/presentation/table/models/draft_order_model.dart'; import 'package:enaklo_pos/presentation/table/models/draft_order_model.dart';
import 'package:enaklo_pos/presentation/table/pages/payment_table_page.dart.old'; import 'package:enaklo_pos/presentation/table/pages/payment_table_page.dart';
class CardTableWidget extends StatefulWidget { class CardTableWidget extends StatefulWidget {
final TableModel table; final TableModel table;
final List<ProductQuantity> items;
const CardTableWidget({ const CardTableWidget({
super.key, super.key,
required this.table, required this.table,
required this.items,
}); });
@override @override
@@ -92,7 +89,6 @@ class _CardTableWidgetState extends State<CardTableWidget> {
context.push(HomePage( context.push(HomePage(
isTable: true, isTable: true,
table: widget.table, table: widget.table,
items: widget.items,
)); ));
} else { } else {
context.read<CheckoutBloc>().add( context.read<CheckoutBloc>().add(
@@ -3,7 +3,6 @@ import 'package:enaklo_pos/core/components/spaces.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.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/function/app_function.dart';
import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart'; import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart';
import 'package:enaklo_pos/presentation/void/painter/pattern_painter.dart'; import 'package:enaklo_pos/presentation/void/painter/pattern_painter.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -633,10 +632,5 @@ class _SuccessVoidPageState extends State<SuccessVoidPage>
); );
} }
void _printVoidReceipt() { void _printVoidReceipt() {}
onPrinVoidRecipt(context,
order: widget.voidedOrder,
productItemVoid: widget.voidedItems ?? [],
totalVoid: widget.voidAmount);
}
} }
+31 -108
View File
@@ -5,31 +5,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "76.0.0" version: "67.0.0"
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: "948f7d74f41dd6f2d563ea9f4c21d7ea764f8e047d2b24138974c19c24d37eb6"
url: "https://pub.dev"
source: hosted
version: "1.3.61"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.3"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.11.0" version: "6.4.1"
another_flushbar: another_flushbar:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -114,10 +101,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build name: build
sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.4.1"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
@@ -138,26 +125,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build_resolvers name: build_resolvers
sha256: "99d3980049739a985cf9b21f30881f46db3ebc62c5b8d5e60e27440876b1ba1e" sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.3" version: "2.4.2"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573" sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.14" version: "2.4.13"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.0.0" version: "7.3.2"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@@ -322,10 +309,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.7" version: "2.3.6"
dartx: dartx:
dependency: transitive dependency: transitive
description: description:
@@ -462,46 +449,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.3+3" version: "0.9.3+3"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "967dae9a65f69377beb9f4ab292ea63ce5befa1ce24682cab1b69ca4b7a46927"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: "5dbc900677dcbe5873d22ad7fbd64b047750124f1f9b7ebe2a33b9ddccc838eb"
url: "https://pub.dev"
source: hosted
version: "6.0.0"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: f7ee08febc1c4451588ce58ffcf28edaee857e9a196fee88b85deb889990094a
url: "https://pub.dev"
source: hosted
version: "3.1.0"
firebase_crashlytics:
dependency: "direct main"
description:
name: firebase_crashlytics
sha256: f2e175a967712ee1f616ab8843390891a315428ba497ce3d256d4c46f32db6f8
url: "https://pub.dev"
source: hosted
version: "5.0.1"
firebase_crashlytics_platform_interface:
dependency: transitive
description:
name: firebase_crashlytics_platform_interface
sha256: b49b90af4a1fd8f30b58abd90af88371969bea51b62838a4f4e737c2098b725e
url: "https://pub.dev"
source: hosted
version: "3.8.12"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@@ -633,10 +580,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: freezed name: freezed
sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.7" version: "2.5.2"
freezed_annotation: freezed_annotation:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -857,26 +804,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.9" version: "11.0.2"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_flutter_testing name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.9" version: "3.0.10"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_testing name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "3.0.2"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@@ -893,14 +840,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
macros:
dependency: transitive
description:
name: macros
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
url: "https://pub.dev"
source: hosted
version: "0.1.3-main.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@@ -921,10 +860,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.16.0" version: "1.17.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@@ -1030,7 +969,7 @@ packages:
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
path: path:
dependency: "direct main" dependency: transitive
description: description:
name: path name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
@@ -1434,22 +1373,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.1" version: "1.4.1"
syncfusion_flutter_core:
dependency: transitive
description:
name: syncfusion_flutter_core
sha256: ce02ce65f51db8e29edc9d2225872d927e001bd2b13c2490d176563bbb046fc7
url: "https://pub.dev"
source: hosted
version: "30.2.5"
syncfusion_flutter_datepicker:
dependency: "direct main"
description:
name: syncfusion_flutter_datepicker
sha256: e8df9f4777df15db11929f20cbe98e4249fe08208e7107bcb4ad889aa1ba2bbf
url: "https://pub.dev"
source: hosted
version: "30.2.5"
synchronized: synchronized:
dependency: transitive dependency: transitive
description: description:
@@ -1470,10 +1393,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.4" version: "0.7.7"
time: time:
dependency: transitive dependency: transitive
description: description:
@@ -1534,10 +1457,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.2.0"
vm_service: vm_service:
dependency: transitive dependency: transitive
description: description:
@@ -1635,5 +1558,5 @@ packages:
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
sdks: sdks:
dart: ">=3.7.0 <4.0.0" dart: ">=3.8.0-0 <4.0.0"
flutter: ">=3.29.0" flutter: ">=3.27.4"
-4
View File
@@ -66,10 +66,6 @@ dependencies:
fl_chart: ^1.0.0 fl_chart: ^1.0.0
barcode: ^2.2.9 barcode: ^2.2.9
barcode_image: ^2.0.3 barcode_image: ^2.0.3
syncfusion_flutter_datepicker: ^30.2.5
firebase_core: ^4.1.0
firebase_crashlytics: ^5.0.1
path: ^1.9.1
# imin_printer: ^0.6.10 # imin_printer: ^0.6.10
dev_dependencies: dev_dependencies: