diff --git a/README.md b/README.md index 16efea5..37a4403 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# EnakloPOS +# ApskelPOS A new Flutter project. diff --git a/android/app/build.gradle b/android/app/build.gradle index 7ecc902..3eb4ebd 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -23,7 +23,7 @@ if (flutterVersionName == null) { } android { - namespace "com.example.enaklo_pos" + namespace "com.appscale.pos" compileSdkVersion 35 ndkVersion flutter.ndkVersion @@ -42,7 +42,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.enaklo_pos" + applicationId "com.appscale.pos" // 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. minSdkVersion 21 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0b2dde7..159867a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -11,7 +11,7 @@ - + + + diff --git a/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png index 255f6e7..8a735f2 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/launcher_icon.png and b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png index bf1325e..f52a995 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/launcher_icon.png and b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png index b76448e..6fc97e6 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png and b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png index 6e71dd1..9fc4359 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png and b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png index bbe6bc1..43a4915 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png and b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index c5d5899..ab98328 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -1,4 +1,4 @@ - #FFFFFF + #ffffff \ No newline at end of file diff --git a/assets/fonts/quicksand/Quicksand-Bold.ttf b/assets/fonts/quicksand/Quicksand-Bold.ttf new file mode 100644 index 0000000..0106805 Binary files /dev/null and b/assets/fonts/quicksand/Quicksand-Bold.ttf differ diff --git a/assets/fonts/quicksand/Quicksand-Light.ttf b/assets/fonts/quicksand/Quicksand-Light.ttf new file mode 100644 index 0000000..fcf86af Binary files /dev/null and b/assets/fonts/quicksand/Quicksand-Light.ttf differ diff --git a/assets/fonts/quicksand/Quicksand-Medium.ttf b/assets/fonts/quicksand/Quicksand-Medium.ttf new file mode 100644 index 0000000..afca2d9 Binary files /dev/null and b/assets/fonts/quicksand/Quicksand-Medium.ttf differ diff --git a/assets/fonts/quicksand/Quicksand-Regular.ttf b/assets/fonts/quicksand/Quicksand-Regular.ttf new file mode 100644 index 0000000..c03548a Binary files /dev/null and b/assets/fonts/quicksand/Quicksand-Regular.ttf differ diff --git a/assets/fonts/quicksand/Quicksand-SemiBold.ttf b/assets/fonts/quicksand/Quicksand-SemiBold.ttf new file mode 100644 index 0000000..83475ea Binary files /dev/null and b/assets/fonts/quicksand/Quicksand-SemiBold.ttf differ diff --git a/assets/icons/people.svg b/assets/icons/people.svg new file mode 100644 index 0000000..f956bf1 --- /dev/null +++ b/assets/icons/people.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/gojek.png b/assets/images/gojek.png new file mode 100644 index 0000000..75548ee Binary files /dev/null and b/assets/images/gojek.png differ diff --git a/assets/images/grab.png b/assets/images/grab.png new file mode 100644 index 0000000..09ffcbd Binary files /dev/null and b/assets/images/grab.png differ diff --git a/assets/logo/logo.png b/assets/logo/logo.png index 733fa8c..93f2c08 100644 Binary files a/assets/logo/logo.png and b/assets/logo/logo.png differ diff --git a/generate_app_icon.dart b/generate_app_icon.dart index da080e4..4fd8bd9 100644 --- a/generate_app_icon.dart +++ b/generate_app_icon.dart @@ -4,31 +4,30 @@ import 'lib/core/utils/app_icon_generator.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - - print('Generating EnakloPOS app icon...'); - + + print('Generating ApskelPOS app icon...'); + try { final iconData = await AppIconGenerator.generateAppIcon(); - + // Ensure the assets/logo directory exists final logoDir = Directory('assets/logo'); if (!await logoDir.exists()) { await logoDir.create(recursive: true); } - + // Write the generated icon to file - final iconFile = File('assets/logo/logo_app_icon.png'); + final iconFile = File('assets/logo/ic_launcher.png'); await iconFile.writeAsBytes(iconData); - - print('✅ App icon generated successfully at: assets/logo/logo_app_icon.png'); + + print('✅ App icon generated successfully at: assets/logo/ic_launcher.png'); print('📱 The icon features:'); print(' - White background for visibility'); print(' - Blue circular background'); print(' - Gift box with "e" inside'); print(' - "ENAKLO" and "POS" text'); print(' - 1024x1024 resolution for high quality'); - } catch (e) { print('❌ Error generating app icon: $e'); } -} \ No newline at end of file +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..d0d98aa 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index fe17dc9..1c10fac 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 62e6e0a..f1bc452 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 4fd1115..1a6a719 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index bb37dbb..ea51316 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 66b7659..030b15e 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index 697aa91..95717a7 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index a3d490b..a131b69 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 4fd1115..1a6a719 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 881007f..b056c30 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 8fd7e63..2914128 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png index f7d3438..306f89c 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png index dea8518..9e23f21 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png index 5d60416..7b14acd 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png index 4c51fa9..639dee4 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index 8fd7e63..2914128 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index aec4644..b339a99 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png index 2105988..a3020db 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png index baf1a6a..39567bf 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 3c68433..4bdd792 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index adc2635..f179387 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index f2bfd8b..c5d5e2b 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 0a7712c..4353496 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - EnakloPOS + ApskelPOS CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier diff --git a/launcher_icon.yaml b/launcher_icon.yaml new file mode 100644 index 0000000..002d378 --- /dev/null +++ b/launcher_icon.yaml @@ -0,0 +1,17 @@ +# Generate: dart run flutter_launcher_icons -f launcher_icon.yaml + +flutter_launcher_icons: + android: "launcher_icon" + ios: true + image_path: "assets/logo/logo.png" + remove_alpha_ios: true + min_sdk_android: 21 # android min sdk min:16, default 21 + adaptive_icon_background: "#ffffff" + adaptive_icon_foreground: "assets/logo/logo.png" + web: + generate: true + image_path: "assets/logo/logo.png" + windows: + generate: true + image_path: "assets/logo/logo.png" + icon_size: 48 diff --git a/lib/core/assets/assets.gen.dart b/lib/core/assets/assets.gen.dart index 85b1d40..ae39032 100644 --- a/lib/core/assets/assets.gen.dart +++ b/lib/core/assets/assets.gen.dart @@ -101,6 +101,9 @@ class $AssetsIconsGen { /// File path: assets/icons/payments.svg SvgGenImage get payments => const SvgGenImage('assets/icons/payments.svg'); + /// File path: assets/icons/people.svg + SvgGenImage get people => const SvgGenImage('assets/icons/people.svg'); + /// File path: assets/icons/print.svg SvgGenImage get print => const SvgGenImage('assets/icons/print.svg'); @@ -152,6 +155,7 @@ class $AssetsIconsGen { orders, pajak, payments, + people, print, qrCode, report, @@ -186,6 +190,12 @@ class $AssetsImagesGen { /// File path: assets/images/drink7.png AssetGenImage get drink7 => const AssetGenImage('assets/images/drink7.png'); + /// File path: assets/images/gojek.png + AssetGenImage get gojek => const AssetGenImage('assets/images/gojek.png'); + + /// File path: assets/images/grab.png + AssetGenImage get grab => const AssetGenImage('assets/images/grab.png'); + /// File path: assets/images/logo.png AssetGenImage get logo => const AssetGenImage('assets/images/logo.png'); @@ -265,6 +275,8 @@ class $AssetsImagesGen { drink5, drink6, drink7, + gojek, + grab, logo, managePrinter, manageProduct, diff --git a/lib/core/assets/fonts.gen.dart b/lib/core/assets/fonts.gen.dart new file mode 100644 index 0000000..e6d6e31 --- /dev/null +++ b/lib/core/assets/fonts.gen.dart @@ -0,0 +1,15 @@ +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use + +class FontFamily { + FontFamily._(); + + /// Font family: Quicksand + static const String quicksand = 'Quicksand'; +} diff --git a/lib/core/components/buttons.dart b/lib/core/components/buttons.dart index 2301468..bcdc058 100644 --- a/lib/core/components/buttons.dart +++ b/lib/core/components/buttons.dart @@ -18,6 +18,10 @@ class Button extends StatelessWidget { this.icon, this.disabled = false, this.fontSize = 16.0, + this.elevation, + this.labelStyle, + this.mainAxisAlignment = MainAxisAlignment.center, + this.crossAxisAlignment = CrossAxisAlignment.center, }); const Button.outlined({ @@ -33,9 +37,13 @@ class Button extends StatelessWidget { this.icon, this.disabled = false, this.fontSize = 16.0, + this.elevation, + this.labelStyle, + this.mainAxisAlignment = MainAxisAlignment.center, + this.crossAxisAlignment = CrossAxisAlignment.center, }); - final Function() onPressed; + final Function()? onPressed; final String label; final ButtonStyle style; final Color color; @@ -43,9 +51,13 @@ class Button extends StatelessWidget { final double? width; final double height; final double borderRadius; + final double? elevation; final Widget? icon; final bool disabled; final double fontSize; + final TextStyle? labelStyle; + final MainAxisAlignment mainAxisAlignment; + final CrossAxisAlignment crossAxisAlignment; @override Widget build(BuildContext context) { @@ -60,11 +72,12 @@ class Button extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(borderRadius), ), + elevation: elevation, padding: const EdgeInsets.symmetric(horizontal: 16.0), ), child: Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, + mainAxisAlignment: mainAxisAlignment, + crossAxisAlignment: crossAxisAlignment, children: [ icon ?? const SizedBox.shrink(), if (icon != null) const SizedBox(width: 10.0), @@ -73,11 +86,12 @@ class Button extends StatelessWidget { fit: BoxFit.scaleDown, child: Text( label, - style: TextStyle( - color: disabled ? Colors.grey : textColor, - fontSize: fontSize, - fontWeight: FontWeight.w600, - ), + style: labelStyle ?? + TextStyle( + color: disabled ? Colors.grey : textColor, + fontSize: fontSize, + fontWeight: FontWeight.bold, + ), textAlign: TextAlign.center, ), ), @@ -89,14 +103,15 @@ class Button extends StatelessWidget { onPressed: disabled ? null : onPressed, style: OutlinedButton.styleFrom( backgroundColor: color, - side: const BorderSide(color: Colors.grey), + side: const BorderSide(color: AppColors.primary), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(borderRadius), ), padding: const EdgeInsets.symmetric(horizontal: 16.0), ), child: Row( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: mainAxisAlignment, + crossAxisAlignment: crossAxisAlignment, mainAxisSize: MainAxisSize.min, children: [ icon ?? const SizedBox.shrink(), @@ -106,11 +121,12 @@ class Button extends StatelessWidget { fit: BoxFit.scaleDown, child: Text( label, - style: TextStyle( - color: disabled ? Colors.grey : textColor, - fontSize: fontSize, - fontWeight: FontWeight.w600, - ), + style: labelStyle ?? + TextStyle( + color: disabled ? Colors.grey : textColor, + fontSize: fontSize, + fontWeight: FontWeight.w600, + ), textAlign: TextAlign.center, ), ), diff --git a/lib/core/components/custom_modal_dialog.dart b/lib/core/components/custom_modal_dialog.dart new file mode 100644 index 0000000..8d943be --- /dev/null +++ b/lib/core/components/custom_modal_dialog.dart @@ -0,0 +1,117 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:flutter/material.dart'; + +class CustomModalDialog extends StatelessWidget { + final String title; + final String? subtitle; + final Widget child; + final VoidCallback? onClose; + final double? minWidth; + final double? maxWidth; + final double? minHeight; + final double? maxHeight; + final EdgeInsets? contentPadding; + + const CustomModalDialog({ + super.key, + required this.title, + this.subtitle, + required this.child, + this.onClose, + this.minWidth, + this.maxWidth, + this.minHeight, + this.maxHeight, + this.contentPadding, + }); + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: AppColors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: minWidth ?? context.deviceWidth * 0.3, + maxWidth: maxWidth ?? context.deviceWidth * 0.8, + minHeight: minHeight ?? context.deviceHeight * 0.3, + maxHeight: maxHeight ?? context.deviceHeight * 0.8, + ), + child: IntrinsicWidth( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.all(16), + width: double.infinity, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + const Color.fromARGB(255, 81, 40, 134), + AppColors.primary, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.vertical( + top: Radius.circular(16), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + color: AppColors.white, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + if (subtitle != null) + Text( + subtitle ?? '', + style: TextStyle( + color: AppColors.grey, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + SpaceWidth(12), + IconButton( + icon: Icon(Icons.close, color: AppColors.white), + onPressed: () { + if (onClose != null) { + onClose!(); + } else { + Navigator.of(context).pop(); + } + }, + ), + ], + ), + ), + Flexible( + child: SingleChildScrollView( + padding: contentPadding ?? EdgeInsets.zero, + child: child, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/core/components/custom_text_field.dart b/lib/core/components/custom_text_field.dart index cbc6828..3e677fc 100644 --- a/lib/core/components/custom_text_field.dart +++ b/lib/core/components/custom_text_field.dart @@ -14,6 +14,8 @@ class CustomTextField extends StatelessWidget { final Widget? prefixIcon; final Widget? suffixIcon; final bool readOnly; + final int maxLines; + final String? Function(String?)? validator; const CustomTextField({ super.key, @@ -28,6 +30,8 @@ class CustomTextField extends StatelessWidget { this.prefixIcon, this.suffixIcon, this.readOnly = false, + this.maxLines = 1, + this.validator, }); @override @@ -53,17 +57,11 @@ class CustomTextField extends StatelessWidget { textInputAction: textInputAction, textCapitalization: textCapitalization ?? TextCapitalization.none, readOnly: readOnly, + maxLines: maxLines, + validator: validator, decoration: InputDecoration( prefixIcon: prefixIcon, suffixIcon: suffixIcon, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(16.0), - borderSide: const BorderSide(color: Colors.grey), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(16.0), - borderSide: const BorderSide(color: Colors.grey), - ), hintText: label, ), ), diff --git a/lib/core/components/dashed_divider.dart b/lib/core/components/dashed_divider.dart new file mode 100644 index 0000000..5cb3fad --- /dev/null +++ b/lib/core/components/dashed_divider.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +class DashedDivider extends StatelessWidget { + final double height; + final double dashWidth; + final double dashSpacing; + final Color color; + + const DashedDivider({ + super.key, + this.height = 1, + this.dashWidth = 5, + this.dashSpacing = 3, + this.color = Colors.grey, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: height, + child: LayoutBuilder( + builder: (context, constraints) { + final boxWidth = constraints.constrainWidth(); + final dashCount = (boxWidth / (dashWidth + dashSpacing)).floor(); + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(dashCount, (_) { + return SizedBox( + width: dashWidth, + height: height, + child: DecoratedBox( + decoration: BoxDecoration(color: color), + ), + ); + }), + ); + }, + ), + ); + } +} diff --git a/lib/core/components/flushbar.dart b/lib/core/components/flushbar.dart new file mode 100644 index 0000000..3563f07 --- /dev/null +++ b/lib/core/components/flushbar.dart @@ -0,0 +1,48 @@ +import 'package:another_flushbar/flushbar.dart'; +import 'package:flutter/material.dart'; + +class AppFlushbar { + static void showSuccess(BuildContext context, String message) { + Flushbar( + messageText: Text( + message, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + icon: const Icon( + Icons.check_circle, + color: Colors.white, + ), + duration: const Duration(seconds: 2), + flushbarPosition: FlushbarPosition.BOTTOM, + backgroundColor: Colors.green, + borderRadius: BorderRadius.circular(12), + margin: const EdgeInsets.all(12), + ).show(context); + } + + static void showError(BuildContext context, String message) { + Flushbar( + messageText: Text( + message, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + icon: const Icon( + Icons.error, + color: Colors.white, + ), + duration: const Duration(seconds: 3), + flushbarPosition: FlushbarPosition.BOTTOM, + backgroundColor: Colors.red, + borderRadius: BorderRadius.circular(12), + margin: const EdgeInsets.all(12), + ).show(context); + } +} diff --git a/lib/core/components/image_picker_widget.dart b/lib/core/components/image_picker_widget.dart index 010fa6e..cc29a03 100644 --- a/lib/core/components/image_picker_widget.dart +++ b/lib/core/components/image_picker_widget.dart @@ -1,91 +1,189 @@ import 'dart:io'; +import 'package:enaklo_pos/presentation/setting/bloc/upload_file/upload_file_bloc.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:image_picker/image_picker.dart'; import 'package:cached_network_image/cached_network_image.dart'; -import '../assets/assets.gen.dart'; import '../constants/colors.dart'; import '../constants/variables.dart'; -import 'buttons.dart'; import 'spaces.dart'; class ImagePickerWidget extends StatefulWidget { final String label; final void Function(XFile? file) onChanged; + final void Function(String? uploadedUrl)? onUploaded; final bool showLabel; final String? initialImageUrl; + final bool autoUpload; const ImagePickerWidget({ super.key, required this.label, required this.onChanged, + this.onUploaded, this.showLabel = true, this.initialImageUrl, + this.autoUpload = false, }); @override State createState() => _ImagePickerWidgetState(); } -class _ImagePickerWidgetState extends State { +class _ImagePickerWidgetState extends State + with TickerProviderStateMixin { String? imagePath; + String? uploadedImageUrl; bool hasInitialImage = false; + bool isHovering = false; + bool isUploading = false; + late AnimationController _scaleController; + late AnimationController _fadeController; + late AnimationController _uploadController; + late Animation _scaleAnimation; + late Animation _fadeAnimation; + late Animation _uploadAnimation; @override void initState() { super.initState(); hasInitialImage = widget.initialImageUrl != null; + + _scaleController = AnimationController( + duration: const Duration(milliseconds: 200), + vsync: this, + ); + _fadeController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + ); + _uploadController = AnimationController( + duration: const Duration(milliseconds: 1000), + vsync: this, + ); + + _scaleAnimation = Tween( + begin: 1.0, + end: 0.95, + ).animate(CurvedAnimation( + parent: _scaleController, + curve: Curves.easeInOut, + )); + + _fadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _fadeController, + curve: Curves.easeInOut, + )); + + _uploadAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _uploadController, + curve: Curves.easeInOut, + )); + + _fadeController.forward(); + } + + @override + void dispose() { + _scaleController.dispose(); + _fadeController.dispose(); + _uploadController.dispose(); + super.dispose(); } Future _pickImage() async { + _scaleController.forward().then((_) { + _scaleController.reverse(); + }); + final pickedFile = await ImagePicker().pickImage( source: ImageSource.gallery, ); - setState(() { - if (pickedFile != null) { + if (pickedFile != null) { + setState(() { imagePath = pickedFile.path; - hasInitialImage = false; // Clear initial image when new image is picked - widget.onChanged(pickedFile); - } else { - debugPrint('No image selected.'); - widget.onChanged(null); + hasInitialImage = false; + uploadedImageUrl = null; + }); + + widget.onChanged(pickedFile); + + // Auto upload if enabled + if (widget.autoUpload) { + _uploadImage(pickedFile.path); } - }); + } else { + debugPrint('No image selected.'); + widget.onChanged(null); + } } - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (widget.showLabel) ...[ - Text( - widget.label, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w700, - ), + void _uploadImage(String filePath) { + setState(() { + isUploading = true; + }); + _uploadController.forward(); + + context.read().add( + UploadFileEvent.upload(filePath), + ); + } + + Widget _buildImageContainer() { + return Container( + width: 100.0, + height: 100.0, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.0), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 4), ), - const SpaceHeight(12.0), ], - Container( - padding: const EdgeInsets.all(6.0), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16.0), - border: Border.all(color: AppColors.primary), - ), - child: Row( - children: [ - SizedBox( - width: 80.0, - height: 80.0, - child: ClipRRect( - borderRadius: BorderRadius.circular(10.0), - child: imagePath != null - ? Image.file( - File(imagePath!), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(20.0), + child: Stack( + children: [ + Positioned.fill( + child: imagePath != null + ? Image.file( + File(imagePath!), + fit: BoxFit.cover, + ) + : uploadedImageUrl != null + ? CachedNetworkImage( + imageUrl: uploadedImageUrl!.contains('http') + ? uploadedImageUrl! + : '${Variables.baseUrl}/$uploadedImageUrl', + placeholder: (context, url) => Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.05), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: const Center( + child: CircularProgressIndicator(strokeWidth: 2), + ), + ), + errorWidget: (context, url, error) => + _buildPlaceholder(), fit: BoxFit.cover, ) : hasInitialImage && widget.initialImageUrl != null @@ -93,38 +191,493 @@ class _ImagePickerWidgetState extends State { imageUrl: widget.initialImageUrl!.contains('http') ? widget.initialImageUrl! : '${Variables.baseUrl}/${widget.initialImageUrl}', - placeholder: (context, url) => - const Center(child: CircularProgressIndicator()), - errorWidget: (context, url, error) => Container( - padding: const EdgeInsets.all(16.0), - color: AppColors.black.withOpacity(0.05), - child: Assets.icons.image.svg(), + placeholder: (context, url) => Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.05), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: const Center( + child: + CircularProgressIndicator(strokeWidth: 2), + ), ), + errorWidget: (context, url, error) => + _buildPlaceholder(), fit: BoxFit.cover, ) - : Container( - padding: const EdgeInsets.all(16.0), - color: AppColors.black.withOpacity(0.05), - child: Assets.icons.image.svg(), - ), + : _buildPlaceholder(), + ), + // Upload progress overlay + if (isUploading) + Positioned.fill( + child: Container( + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.6), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + value: _uploadAnimation.value == 1.0 + ? null + : _uploadAnimation.value, + ), + ), + const SizedBox(height: 8), + const Text( + 'Uploading...', + style: TextStyle( + color: Colors.white, + fontSize: 10, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), ), ), - const Spacer(), - Padding( - padding: const EdgeInsets.only(right: 10.0), - child: Button.filled( - height: 30.0, - width: 140.0, - onPressed: _pickImage, - label: 'Choose Photo', - fontSize: 12.0, - borderRadius: 5.0, + // Overlay gradient for better button visibility + if ((imagePath != null || + uploadedImageUrl != null || + (hasInitialImage && widget.initialImageUrl != null)) && + !isUploading) + Positioned.fill( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.transparent, + Colors.black.withOpacity(0.3), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), ), ), + ], + ), + ), + ); + } + + Widget _buildPlaceholder() { + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.05), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.add_photo_alternate_outlined, + size: 32, + color: AppColors.primary.withOpacity(0.6), + ), + const SizedBox(height: 4), + Text( + 'Photo', + style: TextStyle( + fontSize: 10, + color: AppColors.primary.withOpacity(0.6), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ); + } + + Widget _buildActionButton() { + bool hasImage = imagePath != null || + uploadedImageUrl != null || + (hasInitialImage && widget.initialImageUrl != null); + + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: isUploading ? null : _pickImage, + onHover: (hover) { + setState(() { + isHovering = hover; + }); + }, + borderRadius: BorderRadius.circular(12), + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: isHovering + ? Colors.white.withOpacity(0.1) + : Colors.transparent, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + isUploading + ? Icons.cloud_upload_outlined + : hasImage + ? Icons.edit_outlined + : Icons.add_photo_alternate_outlined, + color: Colors.white, + size: 18, + ), + const SizedBox(width: 8), + Text( + isUploading + ? 'Uploading...' + : hasImage + ? 'Change Photo' + : 'Choose Photo', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildUploadButton() { + if (!widget.autoUpload && + imagePath != null && + uploadedImageUrl == null && + !isUploading) { + return Padding( + padding: const EdgeInsets.only(top: 12), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + gradient: LinearGradient( + colors: [ + Colors.green.shade600, + Colors.green.shade500, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + boxShadow: [ + BoxShadow( + color: Colors.green.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), ], ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => _uploadImage(imagePath!), + borderRadius: BorderRadius.circular(12), + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.cloud_upload_outlined, + color: Colors.white, + size: 16, + ), + const SizedBox(width: 8), + Text( + 'Upload to Server', + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ), ), - ], + ); + } + return const SizedBox.shrink(); + } + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + state.when( + initial: () {}, + loading: () { + if (!isUploading) { + setState(() { + isUploading = true; + }); + _uploadController.repeat(); + } + }, + success: (fileData) { + setState(() { + isUploading = false; + uploadedImageUrl = fileData.fileUrl; + }); + _uploadController.reset(); + + if (widget.onUploaded != null) { + widget.onUploaded!(fileData.fileUrl); + } + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Row( + children: [ + Icon(Icons.check_circle, color: Colors.white), + SizedBox(width: 8), + Text('Image uploaded successfully!'), + ], + ), + backgroundColor: Colors.green, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + duration: Duration(seconds: 2), + ), + ); + }, + error: (message) { + setState(() { + isUploading = false; + }); + _uploadController.reset(); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Row( + children: [ + Icon(Icons.error_outline, color: Colors.white), + SizedBox(width: 8), + Expanded(child: Text('Upload failed: $message')), + ], + ), + backgroundColor: Colors.red, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + duration: Duration(seconds: 3), + ), + ); + }, + ); + }, + child: FadeTransition( + opacity: _fadeAnimation, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.showLabel) ...[ + Text( + widget.label, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: Colors.black87, + ), + ), + const SpaceHeight(16.0), + ], + ScaleTransition( + scale: _scaleAnimation, + child: Container( + padding: const EdgeInsets.all(20.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24.0), + color: Colors.white, + border: Border.all( + color: isUploading + ? Colors.orange.withOpacity(0.5) + : uploadedImageUrl != null + ? Colors.green.withOpacity(0.5) + : AppColors.primary.withOpacity(0.2), + width: 1.5, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 20, + offset: const Offset(0, 10), + ), + ], + ), + child: Column( + children: [ + _buildImageContainer(), + const SizedBox(height: 20), + _buildActionButton(), + _buildUploadButton(), + if ((imagePath != null || + uploadedImageUrl != null || + (hasInitialImage && + widget.initialImageUrl != null)) && + !isUploading) ...[ + const SizedBox(height: 12), + TextButton.icon( + onPressed: () { + setState(() { + imagePath = null; + hasInitialImage = false; + uploadedImageUrl = null; + }); + widget.onChanged(null); + if (widget.onUploaded != null) { + widget.onUploaded!(null); + } + }, + icon: Icon( + Icons.delete_outline, + size: 16, + color: Colors.red.shade400, + ), + label: Text( + 'Remove Photo', + style: TextStyle( + color: Colors.red.shade400, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + // Upload status indicator + if (uploadedImageUrl != null && !isUploading) ...[ + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.green.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + border: + Border.all(color: Colors.green.withOpacity(0.3)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.cloud_done_outlined, + size: 14, + color: Colors.green.shade600, + ), + const SizedBox(width: 6), + Text( + 'Uploaded', + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w600, + color: Colors.green.shade600, + ), + ), + ], + ), + ), + ], + ], + ), + ), + ), + ], + ), + ), ); } } + +/// Cara menggunakan widget ini: +/// +/// ```dart +/// // 1. Basic usage tanpa auto upload +/// ImagePickerWidget( +/// label: 'Product Image', +/// onChanged: (file) { +/// // Handle selected file +/// print('Selected file: ${file?.path}'); +/// }, +/// onUploaded: (url) { +/// // Handle uploaded URL +/// print('Uploaded URL: $url'); +/// }, +/// ) +/// +/// // 2. Auto upload setelah memilih gambar +/// ImagePickerWidget( +/// label: 'Profile Picture', +/// autoUpload: true, +/// onChanged: (file) => setState(() => selectedFile = file), +/// onUploaded: (url) => setState(() => profileImageUrl = url), +/// ) +/// +/// // 3. Dengan initial image +/// ImagePickerWidget( +/// label: 'Banner Image', +/// initialImageUrl: existingImageUrl, +/// onChanged: (file) => handleFileChange(file), +/// onUploaded: (url) => handleUploadSuccess(url), +/// ) +/// ``` +/// +/// Pastikan untuk wrap widget ini dengan BlocProvider: +/// ```dart +/// BlocProvider( +/// create: (context) => UploadFileBloc( +/// context.read(), +/// ), +/// child: ImagePickerWidget(...), +/// ) +/// ``` \ No newline at end of file diff --git a/lib/core/components/search_input.dart b/lib/core/components/search_input.dart index 0be828b..d07a4c0 100644 --- a/lib/core/components/search_input.dart +++ b/lib/core/components/search_input.dart @@ -2,8 +2,6 @@ import 'package:flutter/material.dart'; import '../constants/colors.dart'; - - class SearchInput extends StatelessWidget { final TextEditingController controller; final Function(String value)? onChanged; diff --git a/lib/core/constants/colors.dart b/lib/core/constants/colors.dart index 3bfc2c3..8c0311b 100644 --- a/lib/core/constants/colors.dart +++ b/lib/core/constants/colors.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; class AppColors { /// primary = #3949AB - static const Color primary = Color(0xff6466f1); + static const Color primary = Color(0xff36175e); /// grey = #B7B7B7 static const Color grey = Color(0xffB7B7B7); @@ -18,6 +18,7 @@ class AppColors { /// white = #FFFFFF static const Color white = Color(0xffFFFFFF); + static const Color whiteText = Color(0xfff1eaf9); /// green = #50C474 static const Color green = Color(0xff50C474); @@ -36,4 +37,10 @@ class AppColors { /// stroke = #EFF0F6 static const Color stroke = Color(0xffEFF0F6); + + static const Color background = Color.fromARGB(255, 241, 241, 241); + + static const Color primaryLight = Color(0xFF5A3E8A); + static const Color greyLight = Color(0xFFE0E0E0); + static const Color greyDark = Color(0xFF707070); } diff --git a/lib/core/constants/theme.dart b/lib/core/constants/theme.dart new file mode 100644 index 0000000..c749181 --- /dev/null +++ b/lib/core/constants/theme.dart @@ -0,0 +1,41 @@ +import 'package:enaklo_pos/core/assets/fonts.gen.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:flutter/material.dart'; + +ThemeData getApplicationTheme = ThemeData( + primaryColor: AppColors.primary, + scaffoldBackgroundColor: AppColors.white, + appBarTheme: AppBarTheme( + color: AppColors.white, + elevation: 0, + titleTextStyle: TextStyle( + color: AppColors.primary, + fontSize: 16.0, + fontWeight: FontWeight.w500, + ), + iconTheme: const IconThemeData( + color: AppColors.primary, + ), + ), + fontFamily: FontFamily.quicksand, + colorScheme: ColorScheme.fromSeed(seedColor: AppColors.primary), + useMaterial3: true, + inputDecorationTheme: InputDecorationTheme( + contentPadding: const EdgeInsets.symmetric(horizontal: 16.0), + hintStyle: const TextStyle( + color: AppColors.grey, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + borderSide: BorderSide(color: AppColors.primary), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + borderSide: BorderSide(color: AppColors.primary), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + borderSide: BorderSide(color: AppColors.primary), + ), + ), +); diff --git a/lib/core/constants/variables.dart b/lib/core/constants/variables.dart index d0717c9..b5b9b83 100644 --- a/lib/core/constants/variables.dart +++ b/lib/core/constants/variables.dart @@ -2,5 +2,6 @@ class Variables { static const String appName = 'POS Kasir Resto App'; static const String apiVersion = 'v1'; // static const String baseUrl = 'http://192.168.1.202:8000'; - static const String baseUrl = 'https://pos-app-tablet.enaklo.co.id'; + static const String baseUrl = 'https://enaklo-pos-be.altru.id'; + static const int defaultLimit = 10; } diff --git a/lib/core/extensions/string_ext.dart b/lib/core/extensions/string_ext.dart index 4ed973a..cdb7ae7 100644 --- a/lib/core/extensions/string_ext.dart +++ b/lib/core/extensions/string_ext.dart @@ -15,4 +15,12 @@ extension StringExt on String { decimalDigits: 0, ).format(parsedValue); } + + String toTitleCase() { + if (isEmpty) return ''; + return split(' ').map((word) { + if (word.isEmpty) return ''; + return word[0].toUpperCase() + word.substring(1).toLowerCase(); + }).join(' '); + } } diff --git a/lib/core/function/app_function.dart b/lib/core/function/app_function.dart new file mode 100644 index 0000000..908d4ef --- /dev/null +++ b/lib/core/function/app_function.dart @@ -0,0 +1,183 @@ +import 'dart:developer'; +import 'dart:typed_data'; + +import 'package:barcode_image/barcode_image.dart'; +import 'package:enaklo_pos/core/extensions/string_ext.dart'; +import 'package:enaklo_pos/core/utils/printer_service.dart'; +import 'package:enaklo_pos/data/dataoutputs/print_dataoutputs.dart'; +import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; +import 'package:enaklo_pos/data/datasources/outlet_local_datasource.dart'; +import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; +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/type/bussines_type.dart'; +import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; +import 'package:flutter/material.dart'; +import 'package:barcode/barcode.dart'; +import 'package:image/image.dart' as img; + +Future onPrint( + BuildContext context, { + required List productQuantity, + required Order order, +}) async { + final outlet = await OutletLocalDatasource().get(); + if (outlet.businessType == BusinessType.restaurant) { + final checkerPrinter = + await ProductLocalDatasource.instance.getPrinterByCode('checker'); + final kitchenPrinter = + await ProductLocalDatasource.instance.getPrinterByCode('kitchen'); + final barPrinter = + await ProductLocalDatasource.instance.getPrinterByCode('bar'); + + final authData = await AuthLocalDataSource().getAuthData(); + + // Checker printer + if (checkerPrinter != null) { + try { + final printValue = await PrintDataoutputs.instance.printChecker( + productQuantity, + order.tableNumber ?? "", + order.orderNumber ?? "", + authData.user?.name ?? "", + checkerPrinter.paper.toIntegerFromText, + order.orderType ?? "", + ); + + await PrinterService() + // ignore: use_build_context_synchronously + .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( + productQuantity, + order.tableNumber!, + order.orderNumber ?? "", + authData.user?.name ?? "", + kitchenPrinter.paper.toIntegerFromText, + order.orderType ?? "", + ); + + 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( + productQuantity, + order.tableNumber ?? "", + order.orderNumber ?? "", + authData.user?.name ?? "", + barPrinter.paper.toIntegerFromText, + order.orderType ?? "", + ); + + 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')), + ); + } + } + } + + if (outlet.businessType == BusinessType.ticketing) { + final ticketPrinter = + await ProductLocalDatasource.instance.getPrinterByCode('ticket'); + + final barcode = await generateBarcodeAsUint8List(order.orderNumber ?? ""); + + if (ticketPrinter != null) { + try { + final printValue = await PrintDataoutputs.instance.printTicket( + order.totalAmount ?? 0, + barcode, + ticketPrinter.paper.toIntegerFromText, + ); + + await PrinterService() + // ignore: use_build_context_synchronously + .printWithPrinter(ticketPrinter, printValue, context); + } catch (e) { + log("Error printing ticket: $e"); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error printing ticket: $e')), + ); + } + } + } +} + +Future onPrintRecipt( + context, { + required Order order, + required String paymentMethod, + required int nominalBayar, + required int kembalian, +}) 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.printOrderV4( + order, + authData.user?.name ?? "", + paymentMethod, + nominalBayar, + kembalian, + settings.value, + receiptPrinter.paper.toIntegerFromText, + order.orderType ?? "", + outlet, + ); + await PrinterService() + .printWithPrinter(receiptPrinter, printValue, context); + } catch (e) { + log("Error printing receipt order: $e"); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error printing receipt order: $e')), + ); + } + } +} + +Future generateBarcodeAsUint8List(String data) async { + // 1. Buat barcode instance (code128, qrCode, dll) + final barcode = Barcode.code128(); + + // 2. Siapkan canvas image dari package `image` + final image = img.Image(width: 600, height: 200); + + // 3. Gambar barcode ke canvas menggunakan barcode_image + drawBarcode( + image, + barcode, + data, + ); + + // 4. Encode image ke Uint8List PNG + return Uint8List.fromList(img.encodePng(image)); +} diff --git a/lib/core/network/dio_client.dart b/lib/core/network/dio_client.dart new file mode 100644 index 0000000..309d3b5 --- /dev/null +++ b/lib/core/network/dio_client.dart @@ -0,0 +1,181 @@ +import 'package:awesome_dio_interceptor/awesome_dio_interceptor.dart'; +import 'package:dio/dio.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/presentation/auth/login_page.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class DioClient { + static final Dio _dio = Dio(BaseOptions( + connectTimeout: const Duration(seconds: 10), + receiveTimeout: const Duration(seconds: 10), + headers: { + 'Accept': 'application/json', + }, + )) + ..interceptors.add(AuthInterceptor()) + ..interceptors.add( + AwesomeDioInterceptor( + logRequestTimeout: true, + logRequestHeaders: true, + logResponseHeaders: true, + ), + ); + + static Dio get instance => _dio; +} + +class AuthInterceptor extends Interceptor { + static final GlobalKey navigatorKey = + GlobalKey(); + + @override + void onRequest( + RequestOptions options, RequestInterceptorHandler handler) async { + // Add token to request headers + final prefs = await SharedPreferences.getInstance(); + final token = prefs.getString('auth_token'); + + if (token != null) { + options.headers['Authorization'] = 'Bearer $token'; + } + + handler.next(options); + } + + @override + void onResponse(Response response, ResponseInterceptorHandler handler) { + handler.next(response); + } + + @override + void onError(DioException err, ErrorInterceptorHandler handler) async { + // Check if error is 401 (Unauthorized) - token expired + if (err.response?.statusCode == 401) { + await _handleTokenExpired(); + } + + handler.next(err); + } + + Future _handleTokenExpired() async { + // Clear stored token + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('auth_token'); + await prefs.remove('refresh_token'); + await prefs.clear(); // Optional: clear all user data + + // Navigate to login page + final context = navigatorKey.currentContext; + if (context != null) { + // Option 1: Navigate and remove all previous routes + context.pushReplacement(LoginPage()); + + // Option 2: If using GoRouter, uncomment below: + // GoRouter.of(context).go('/login'); + + // Option 3: If using custom routing, uncomment below: + // Navigator.of(context).pushAndRemoveUntil( + // MaterialPageRoute(builder: (context) => LoginPage()), + // (route) => false, + // ); + } + } +} + +// Alternative: Dengan Refresh Token Logic +class AuthInterceptorWithRefresh extends Interceptor { + static final GlobalKey navigatorKey = + GlobalKey(); + + @override + void onRequest( + RequestOptions options, RequestInterceptorHandler handler) async { + final prefs = await SharedPreferences.getInstance(); + final token = prefs.getString('auth_token'); + + if (token != null) { + options.headers['Authorization'] = 'Bearer $token'; + } + + handler.next(options); + } + + @override + void onError(DioException err, ErrorInterceptorHandler handler) async { + if (err.response?.statusCode == 401) { + // Try refresh token first + final success = await _tryRefreshToken(); + + if (success) { + // Retry the original request + final response = await _retryRequest(err.requestOptions); + handler.resolve(response); + return; + } else { + // Refresh failed, redirect to login + await _handleTokenExpired(); + } + } + + handler.next(err); + } + + Future _tryRefreshToken() async { + try { + final prefs = await SharedPreferences.getInstance(); + final refreshToken = prefs.getString('refresh_token'); + + if (refreshToken == null) return false; + + final response = await Dio().post( + 'YOUR_REFRESH_TOKEN_ENDPOINT', + data: {'refresh_token': refreshToken}, + ); + + if (response.statusCode == 200) { + final newToken = response.data['access_token']; + final newRefreshToken = response.data['refresh_token']; + + await prefs.setString('auth_token', newToken); + await prefs.setString('refresh_token', newRefreshToken); + + return true; + } + } catch (e) { + print('Refresh token failed: $e'); + } + + return false; + } + + Future _retryRequest(RequestOptions options) async { + final prefs = await SharedPreferences.getInstance(); + final token = prefs.getString('auth_token'); + + options.headers['Authorization'] = 'Bearer $token'; + + return await DioClient.instance.request( + options.path, + options: Options( + method: options.method, + headers: options.headers, + ), + data: options.data, + queryParameters: options.queryParameters, + ); + } + + Future _handleTokenExpired() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.clear(); + + final context = navigatorKey.currentContext; + if (context != null) { + Navigator.of(context).pushNamedAndRemoveUntil( + '/login', + (route) => false, + ); + } + } +} diff --git a/lib/core/utils/item_sales_invoice.dart b/lib/core/utils/item_sales_invoice.dart index 4b46c87..751c321 100644 --- a/lib/core/utils/item_sales_invoice.dart +++ b/lib/core/utils/item_sales_invoice.dart @@ -38,7 +38,7 @@ class ItemSalesInvoice { return HelperPdfService.saveDocument( name: - 'Enaklo POS | Item Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf', + 'Apskel POS | Item Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf', pdf: pdf); } @@ -48,7 +48,7 @@ class ItemSalesInvoice { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 1 * PdfPageFormat.cm), - Text('Enaklo POS | Item Sales Report', + Text('Apskel POS | Item Sales Report', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, diff --git a/lib/core/utils/revenue_invoice.dart b/lib/core/utils/revenue_invoice.dart index 4471e9a..42ca4e6 100644 --- a/lib/core/utils/revenue_invoice.dart +++ b/lib/core/utils/revenue_invoice.dart @@ -22,13 +22,14 @@ class RevenueInvoice { log("Starting PDF generation for summary report"); log("Summary model: ${summaryModel.toMap()}"); log("Search date formatted: $searchDateFormatted"); - + final pdf = Document(); log("PDF document created"); - + // Load logo image log("Loading logo image..."); - final ByteData dataImage = await rootBundle.load('assets/images/logo.png'); + final ByteData dataImage = + await rootBundle.load('assets/images/logo.png'); final Uint8List bytes = dataImage.buffer.asUint8List(); final image = pw.MemoryImage(bytes); log("Logo image loaded successfully, size: ${bytes.length} bytes"); @@ -49,7 +50,7 @@ class RevenueInvoice { log("Saving PDF document..."); return HelperPdfService.saveDocument( name: - 'Enaklo POS | Summary Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf', + 'Apskel POS | Summary Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf', pdf: pdf, ); } catch (e) { @@ -69,7 +70,7 @@ class RevenueInvoice { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 1 * PdfPageFormat.cm), - Text('Enaklo POS | Summary Sales Report', + Text('Apskel POS | Summary Sales Report', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -93,7 +94,7 @@ class RevenueInvoice { static Widget buildTotal(SummaryModel summaryModel) { log("Building total section with summary model: ${summaryModel.toMap()}"); - + // Helper function to safely parse string to int int safeParseInt(String? value) { if (value == null || value.isEmpty) return 0; @@ -104,7 +105,7 @@ class RevenueInvoice { return 0; } } - + return Container( width: double.infinity, child: Column( @@ -125,7 +126,8 @@ class RevenueInvoice { buildText( title: 'Discount', titleStyle: TextStyle(fontWeight: FontWeight.normal), - value: "- ${safeParseInt(summaryModel.totalDiscount).currencyFormatRp}", + value: + "- ${safeParseInt(summaryModel.totalDiscount).currencyFormatRp}", unite: true, textStyle: TextStyle( color: PdfColor.fromHex('#FF0000'), @@ -147,7 +149,8 @@ class RevenueInvoice { titleStyle: TextStyle( fontWeight: FontWeight.normal, ), - value: safeParseInt(summaryModel.totalServiceCharge).currencyFormatRp, + value: + safeParseInt(summaryModel.totalServiceCharge).currencyFormatRp, unite: true, ), Divider(), diff --git a/lib/core/utils/transaction_sales_invoice.dart b/lib/core/utils/transaction_sales_invoice.dart index f7a7b70..3e31f6e 100644 --- a/lib/core/utils/transaction_sales_invoice.dart +++ b/lib/core/utils/transaction_sales_invoice.dart @@ -5,7 +5,7 @@ import 'package:enaklo_pos/core/extensions/int_ext.dart'; import 'package:flutter/services.dart'; import 'package:enaklo_pos/core/utils/helper_pdf_service.dart'; -import 'package:enaklo_pos/data/models/response/order_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; import 'package:pdf/widgets.dart'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; @@ -13,7 +13,7 @@ import 'package:pdf/widgets.dart' as pw; class TransactionSalesInvoice { static late Font ttf; static Future generate( - List itemOrders, String searchDateFormatted) async { + List itemOrders, String searchDateFormatted) async { final pdf = Document(); // var data = await rootBundle.load("assets/fonts/noto-sans.ttf"); // ttf = Font.ttf(data); @@ -38,7 +38,7 @@ class TransactionSalesInvoice { return HelperPdfService.saveDocument( name: - 'Enaklo POS | Transaction Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf', + 'Apskel POS | Transaction Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf', pdf: pdf); } @@ -48,7 +48,7 @@ class TransactionSalesInvoice { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 1 * PdfPageFormat.cm), - Text('Enaklo POS | Transaction Sales Report', + Text('Apskel POS | Transaction Sales Report', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -70,7 +70,7 @@ class TransactionSalesInvoice { ), ]); - static Widget buildInvoice(List itemOrders) { + static Widget buildInvoice(List itemOrders) { final headers = [ 'Total', 'Sub Total', @@ -81,12 +81,13 @@ class TransactionSalesInvoice { ]; final data = itemOrders.map((item) { return [ - item.total!.currencyFormatRp, - item.subTotal!.currencyFormatRp, - item.tax!.currencyFormatRp, - int.parse(item.discountAmount!.replaceAll('.00', '')).currencyFormatRp, - item.serviceCharge!.currencyFormatRp, - item.transactionTime!.toFormattedDate2(), + item.totalAmount!.currencyFormatRp, + item.subtotal!.currencyFormatRp, + item.taxAmount!.currencyFormatRp, + int.parse(item.discountAmount!.toString().replaceAll('.00', '')) + .currencyFormatRp, + 0, + item.createdAt!.toFormattedDate2(), ]; }).toList(); diff --git a/lib/data/dataoutputs/print_dataoutputs.dart b/lib/data/dataoutputs/print_dataoutputs.dart index c071a1f..eba8df6 100644 --- a/lib/data/dataoutputs/print_dataoutputs.dart +++ b/lib/data/dataoutputs/print_dataoutputs.dart @@ -1,5 +1,7 @@ import 'dart:math'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:enaklo_pos/presentation/home/models/outlet_model.dart'; import 'package:esc_pos_utils_plus/esc_pos_utils_plus.dart'; import 'package:flutter/services.dart'; import 'package:enaklo_pos/core/extensions/int_ext.dart'; @@ -35,7 +37,7 @@ class PrintDataoutputs { final total = totalPrice + pajak; bytes += generator.reset(); - bytes += generator.text('Enaklo POS', + bytes += generator.text('Apskel POS', styles: const PosStyles( bold: true, align: PosAlign.center, @@ -60,12 +62,12 @@ class PrintDataoutputs { bytes += generator.row([ PosColumn( text: - '${product.product.price!.toIntegerFromText.currencyFormatRp} x ${product.quantity}', + '${product.product.price!.currencyFormatRp} x ${product.quantity}', width: 8, styles: const PosStyles(align: PosAlign.left), ), PosColumn( - text: '${product.product.price!.toIntegerFromText * product.quantity}' + text: '${product.product.price! * product.quantity}' .toIntegerFromText .currencyFormatRp, width: 4, @@ -202,7 +204,7 @@ class PrintDataoutputs { // bytes += generator.feed(3); // } - bytes += generator.text('Enaklo POS', + bytes += generator.text('Apskel POS', styles: const PosStyles( bold: true, align: PosAlign.center, @@ -328,8 +330,7 @@ class PrintDataoutputs { styles: const PosStyles(align: PosAlign.left), ), PosColumn( - text: (product.product.price!.toIntegerFromText * product.quantity) - .currencyFormatRp, + text: (product.product.price! * product.quantity).currencyFormatRp, width: 4, styles: const PosStyles(align: PosAlign.right), ), @@ -398,8 +399,7 @@ class PrintDataoutputs { styles: const PosStyles(align: PosAlign.left), ), PosColumn( - text: (products[0].product.price!.toIntegerFromText * - products[0].quantity) + text: (products[0].product.price! * products[0].quantity) .currencyFormatRp, width: 4, styles: const PosStyles(align: PosAlign.right), @@ -430,8 +430,7 @@ class PrintDataoutputs { styles: const PosStyles(align: PosAlign.left), ), PosColumn( - text: (products[0].product.price!.toIntegerFromText * - products[0].quantity) + text: (products[0].product.price! * products[0].quantity) .currencyFormatRp, width: 4, styles: const PosStyles(align: PosAlign.right), @@ -474,21 +473,21 @@ class PrintDataoutputs { } Future> printOrderV3( - List products, - int totalQuantity, - int totalPrice, - String paymentMethod, - int nominalBayar, - int kembalian, - int subTotal, - int discount, - int pajak, - int serviceCharge, - String namaKasir, - String customerName, - int paper, - {int taxPercentage = 11, int serviceChargePercentage = 5} - ) async { + List products, + int totalQuantity, + int totalPrice, + String paymentMethod, + int nominalBayar, + int kembalian, + int subTotal, + int discount, + int pajak, + int serviceCharge, + String namaKasir, + String customerName, + int paper, + {int taxPercentage = 11, + int serviceChargePercentage = 5}) async { List bytes = []; final profile = await CapabilityProfile.load(); @@ -610,8 +609,8 @@ class PrintDataoutputs { styles: const PosStyles(bold: true, align: PosAlign.left), ), PosColumn( - text: '${product.product.price!.toIntegerFromText * product.quantity}' - .currencyFormatRpV2, + text: + '${product.product.price! * product.quantity}'.currencyFormatRpV2, width: 4, styles: const PosStyles(bold: true, align: PosAlign.right), ), @@ -626,8 +625,7 @@ class PrintDataoutputs { final subTotalPrice = products.fold( 0, (previousValue, element) => - previousValue + - (element.product.price!.toIntegerFromText * element.quantity)); + previousValue + (element.product.price! * element.quantity)); bytes += generator.row([ PosColumn( text: 'Subtotal $totalQuantity Product', @@ -743,6 +741,265 @@ class PrintDataoutputs { return bytes; } + Future> printOrderV4( + Order order, + String chashierName, + String paymentMethod, + int nominalBayar, + int kembalian, + int taxPercentage, + int paper, + String orderType, + Outlet outlet, + ) async { + List 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: 'JF-${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: 'Pembayaran', + width: 8, + styles: const PosStyles(align: PosAlign.left), + ), + PosColumn( + text: paymentMethod, + width: 4, + styles: const PosStyles(align: PosAlign.right), + ), + ]); + + bytes += generator.text( + paper == 80 + ? '------------------------------------------------' + : '--------------------------------', + styles: const PosStyles(bold: false, align: PosAlign.center)); + bytes += generator.text(orderType, + styles: const PosStyles(bold: true, align: PosAlign.center)); + bytes += generator.text( + paper == 80 + ? '------------------------------------------------' + : '--------------------------------', + styles: const PosStyles(bold: false, align: PosAlign.center)); + for (final product in (order.orderItems ?? [])) { + 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 ${order.orderItems?.length ?? "0"} 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: 'Dibayar', + width: 6, + styles: const PosStyles(align: PosAlign.left), + ), + PosColumn( + text: nominalBayar.currencyFormatRpV2, + width: 6, + styles: const PosStyles(align: PosAlign.right), + ), + ]); + bytes += generator.row([ + PosColumn( + text: 'Kembali', + width: 6, + styles: const PosStyles(align: PosAlign.left), + ), + PosColumn( + text: kembalian.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> printQRIS( int totalPrice, Uint8List imageQris, int paper) async { List bytes = []; @@ -778,8 +1035,13 @@ class PrintDataoutputs { return bytes; } - Future> printChecker(List products, - String tableName, String draftName, String cashierName, int paper, String orderType) async { + Future> printChecker( + List products, + String tableName, + String draftName, + String cashierName, + int paper, + String orderType) async { List bytes = []; final profile = await CapabilityProfile.load(); @@ -908,8 +1170,13 @@ class PrintDataoutputs { return bytes; } - Future> printKitchen(List products, - String tableNumber, String draftName, String cashierName, int paper, String orderType) async { + Future> printKitchen( + List products, + String tableNumber, + String draftName, + String cashierName, + int paper, + String orderType) async { List bytes = []; final profile = await CapabilityProfile.load(); @@ -1134,4 +1401,39 @@ class PrintDataoutputs { return bytes; } + + Future> printTicket( + int totalPrice, Uint8List imageQris, int paper) async { + List bytes = []; + + final profile = await CapabilityProfile.load(); + final generator = + Generator(paper == 58 ? PaperSize.mm58 : PaperSize.mm80, profile); + + final img.Image? orginalImage = img.decodeImage(imageQris); + bytes += generator.reset(); + + // final Uint8List bytesData = data.buffer.asUint8List(); + // final img.Image? orginalImage = img.decodeImage(bytesData); + // bytes += generator.reset(); + + bytes += generator.text('Scan Ticket', + styles: const PosStyles(bold: false, align: PosAlign.center)); + bytes += generator.feed(2); + if (orginalImage != null) { + final img.Image grayscalledImage = img.grayscale(orginalImage); + final img.Image resizedImage = + img.copyResize(grayscalledImage, width: 240); + bytes += generator.imageRaster(resizedImage, align: PosAlign.center); + bytes += generator.feed(1); + } + + bytes += generator.text('Price : ${totalPrice.currencyFormatRp}', + styles: const PosStyles(bold: false, align: PosAlign.center)); + + bytes += generator.feed(4); + bytes += generator.cut(); + + return bytes; + } } diff --git a/lib/data/datasources/analytic_remote_datasource.dart b/lib/data/datasources/analytic_remote_datasource.dart new file mode 100644 index 0000000..b538034 --- /dev/null +++ b/lib/data/datasources/analytic_remote_datasource.dart @@ -0,0 +1,187 @@ +import 'dart:developer'; + +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:enaklo_pos/core/constants/variables.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/models/response/dashboard_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/data/models/response/sales_analytic_response_model.dart'; +import 'package:intl/intl.dart'; + +class AnalyticRemoteDatasource { + final Dio dio = DioClient.instance; + + Future> getPaymentMethod({ + 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/payment-methods', + 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(PaymentMethodAnalyticResponseModel.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> getSales({ + 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/sales', + 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(SalesAnalyticResponseModel.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> getProduct({ + 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/products', + 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(ProductAnalyticResponseModel.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> getDashboard({ + 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/dashboard', + 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(DashboardAnalyticResponseModel.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> getProfitLoss({ + 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/profit-loss', + 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(ProfitLossResponseModel.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'); + } + } +} diff --git a/lib/data/datasources/auth_local_datasource.dart b/lib/data/datasources/auth_local_datasource.dart index bf123d4..7488d36 100644 --- a/lib/data/datasources/auth_local_datasource.dart +++ b/lib/data/datasources/auth_local_datasource.dart @@ -1,10 +1,16 @@ +import 'dart:developer'; + import 'package:enaklo_pos/data/models/response/auth_response_model.dart'; import 'package:shared_preferences/shared_preferences.dart'; class AuthLocalDataSource { Future saveAuthData(AuthResponseModel authResponseModel) async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setString('auth_data', authResponseModel.toJson()); + try { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('auth_data', authResponseModel.toJson()); + } catch (e) { + log('Error saving auth data: $e'); + } } Future removeAuthData() async { @@ -16,6 +22,8 @@ class AuthLocalDataSource { final prefs = await SharedPreferences.getInstance(); final authData = prefs.getString('auth_data'); + log('Auth data: $authData'); + return AuthResponseModel.fromJson(authData!); } diff --git a/lib/data/datasources/auth_remote_datasource.dart b/lib/data/datasources/auth_remote_datasource.dart index 5f34461..c589193 100644 --- a/lib/data/datasources/auth_remote_datasource.dart +++ b/lib/data/datasources/auth_remote_datasource.dart @@ -1,44 +1,67 @@ +import 'dart:developer'; + import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; import 'package:enaklo_pos/core/constants/variables.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/models/response/auth_response_model.dart'; -import 'package:http/http.dart' as http; class AuthRemoteDatasource { + final Dio dio = DioClient.instance; Future> login( String email, String password) async { - final url = Uri.parse('${Variables.baseUrl}/api/login'); - final response = await http.post( - url, - body: { - 'email': email, - 'password': password, - }, - ); + final url = '${Variables.baseUrl}/api/v1/auth/login'; + log(url); - if (response.statusCode == 200) { - return Right(AuthResponseModel.fromJson(response.body)); - } else { - return const Left('Failed to login'); + try { + final response = await dio.post( + url, + data: { + 'email': email, + 'password': password, + }, + ); + + if (response.statusCode == 200) { + return Right(AuthResponseModel.fromMap(response.data['data'])); + } else { + return const Left('Failed to login'); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Login gagal'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); } } //logout Future> logout() async { - final authData = await AuthLocalDataSource().getAuthData(); - final url = Uri.parse('${Variables.baseUrl}/api/logout'); - final response = await http.post( - url, - headers: { - 'Authorization': 'Bearer ${authData.token}', - 'Accept': 'application/json', - }, - ); + try { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/auth/logout'; - if (response.statusCode == 200) { - return const Right(true); - } else { - return const Left('Failed to logout'); + final response = await dio.post( + url, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + return const Right(true); + } else { + return const Left('Failed to logout'); + } + } on DioException catch (e) { + return Left(e.response?.data['message'] ?? 'Logout gagal'); + } catch (e) { + return const Left('Unexpected error occurred'); } } } diff --git a/lib/data/datasources/category_remote_datasource.dart b/lib/data/datasources/category_remote_datasource.dart index d81071a..1623496 100644 --- a/lib/data/datasources/category_remote_datasource.dart +++ b/lib/data/datasources/category_remote_datasource.dart @@ -1,27 +1,48 @@ import 'dart:developer'; import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; import 'package:enaklo_pos/core/constants/variables.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/models/response/category_response_model.dart'; -import 'package:http/http.dart' as http; class CategoryRemoteDatasource { - Future> getCategories() async { + final Dio dio = DioClient.instance; + + Future> getCategories({ + int page = 1, + int limit = 10, + bool isActive = true, + }) async { final authData = await AuthLocalDataSource().getAuthData(); - final Map headers = { + final headers = { 'Authorization': 'Bearer ${authData.token}', 'Accept': 'application/json', }; - final response = await http.get( - Uri.parse('${Variables.baseUrl}/api/api-categories'), - headers: headers); - log(response.statusCode.toString()); - log(response.body); - if (response.statusCode == 200) { - return right(CategroyResponseModel.fromJson(response.body)); - } else { - return left(response.body); + + try { + final response = await dio.get( + '${Variables.baseUrl}/api/v1/categories', + queryParameters: { + 'page': page, + 'limit': limit, + 'is_active': isActive, + }, + options: Options(headers: headers), + ); + + if (response.statusCode == 200) { + return right(CategoryResponseModel.fromMap(response.data)); + } else { + return left(response.data.toString()); + } + } 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'); } } } diff --git a/lib/data/datasources/customer_remote_datasource.dart b/lib/data/datasources/customer_remote_datasource.dart new file mode 100644 index 0000000..ec6fd05 --- /dev/null +++ b/lib/data/datasources/customer_remote_datasource.dart @@ -0,0 +1,101 @@ +import 'dart:developer'; +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:enaklo_pos/core/network/dio_client.dart'; +import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import '../../core/constants/variables.dart'; +import 'auth_local_datasource.dart'; + +class CustomerRemoteDataSource { + final Dio dio = DioClient.instance; + + Future> getCustomers({ + int page = 1, + int limit = Variables.defaultLimit, + }) async { + try { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/customers'; + + final response = await dio.get( + url, + queryParameters: { + 'page': page, + 'limit': limit, + 'organization_id': authData.user?.organizationId, + }, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + return Right(CustomerResponseModel.fromMap(response.data)); + } else { + return const Left('Failed to get customers'); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Gagal mengambil customer'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); + } + } + + Future> createCustomer({ + required String name, + required String phone, + required String address, + required String email, + required bool isActive, + }) async { + try { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/customers'; + + Map data = { + 'name': name, + 'is_active': isActive, + }; + + if (phone.isNotEmpty) { + data['phone'] = phone; + } + + if (address.isNotEmpty) { + data['address'] = address; + } + + if (email.isNotEmpty) { + data['email'] = email; + } + + final response = await dio.post( + url, + data: data, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + }, + ), + ); + + if (response.statusCode == 200 || response.statusCode == 201) { + return const Right(true); + } else { + return const Left('Failed to create customer '); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Gagal membuat pelanggan'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); + } + } +} diff --git a/lib/data/datasources/delivery_local_datasource.dart b/lib/data/datasources/delivery_local_datasource.dart new file mode 100644 index 0000000..5119edc --- /dev/null +++ b/lib/data/datasources/delivery_local_datasource.dart @@ -0,0 +1,15 @@ +import 'package:enaklo_pos/core/assets/assets.gen.dart'; +import 'package:enaklo_pos/data/models/response/delivery_response_model.dart'; + +List deliveries = [ + DeliveryModel( + id: 'gojek', + name: 'Gojek', + imageUrl: Assets.images.gojek.path, + ), + DeliveryModel( + id: 'grab', + name: 'Grab', + imageUrl: Assets.images.grab.path, + ), +]; diff --git a/lib/data/datasources/file_remote_datasource.dart b/lib/data/datasources/file_remote_datasource.dart new file mode 100644 index 0000000..e46e70b --- /dev/null +++ b/lib/data/datasources/file_remote_datasource.dart @@ -0,0 +1,53 @@ +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:enaklo_pos/core/constants/variables.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/models/response/file_response_model.dart'; + +class FileRemoteDataSource { + final Dio dio = DioClient.instance; + + Future> uploadFile({ + required String filePath, + required String fileType, + required String description, + }) async { + final url = '${Variables.baseUrl}/api/v1/files/upload'; + + try { + final authData = await AuthLocalDataSource().getAuthData(); + + // Membuat FormData + final formData = FormData.fromMap({ + 'file': await MultipartFile.fromFile(filePath, + filename: filePath.split('/').last), + 'file_type': fileType, + 'description': description, + }); + + final response = await dio.post( + url, + data: formData, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + // Content-Type otomatis diatur oleh Dio untuk FormData + }, + ), + ); + + if (response.statusCode == 201 || response.statusCode == 200) { + // Misal response.data['url'] adalah URL file yang diupload + return Right(FileResponseModel.fromJson(response.data)); + } else { + return Left('Upload gagal: ${response.statusMessage}'); + } + } on DioException catch (e) { + return Left(e.response?.data['message'] ?? 'Upload gagal'); + } catch (e) { + return Left('Unexpected error: $e'); + } + } +} diff --git a/lib/data/datasources/order_remote_datasource.dart b/lib/data/datasources/order_remote_datasource.dart index 22a85d7..fdb3675 100644 --- a/lib/data/datasources/order_remote_datasource.dart +++ b/lib/data/datasources/order_remote_datasource.dart @@ -1,17 +1,25 @@ +import 'dart:convert'; import 'dart:developer'; -import 'dart:convert'; - import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; import 'package:enaklo_pos/core/constants/variables.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/models/response/order_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/request/payment_request.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; import 'package:enaklo_pos/data/models/response/payment_method_response_model.dart'; +import 'package:enaklo_pos/data/models/response/payment_methods_response_model.dart'; +import 'package:enaklo_pos/data/models/response/payment_response_model.dart'; import 'package:enaklo_pos/data/models/response/summary_response_model.dart'; import 'package:enaklo_pos/presentation/home/models/order_model.dart'; +import 'package:enaklo_pos/presentation/home/models/order_request.dart'; import 'package:http/http.dart' as http; +import 'package:intl/intl.dart'; class OrderRemoteDatasource { + final Dio dio = DioClient.instance; + //save order to remote server Future saveOrder(OrderModel orderModel) async { final authData = await AuthLocalDataSource().getAuthData(); @@ -28,11 +36,11 @@ class OrderRemoteDatasource { 'Content-Type': 'application/json', }, ); - + print("📥 HTTP Status Code: ${response.statusCode}"); print("📥 Response Body: ${response.body}"); print("📥 Response Headers: ${response.headers}"); - + if (response.statusCode == 200) { print("✅ API call successful - Order saved to server"); return true; @@ -69,7 +77,8 @@ class OrderRemoteDatasource { print("✅ getOrderByRangeDate API call successful"); return Right(OrderResponseModel.fromJson(response.body)); } else { - print("❌ getOrderByRangeDate API call failed - Status: ${response.statusCode}"); + print( + "❌ getOrderByRangeDate API call failed - Status: ${response.statusCode}"); print("❌ Error Response: ${response.body}"); return const Left("Failed Load Data"); } @@ -102,7 +111,8 @@ class OrderRemoteDatasource { print("✅ getSummaryByRangeDate API call successful"); return Right(SummaryResponseModel.fromJson(response.body)); } else { - print("❌ getSummaryByRangeDate API call failed - Status: ${response.statusCode}"); + print( + "❌ getSummaryByRangeDate API call failed - Status: ${response.statusCode}"); print("❌ Error Response: ${response.body}"); return const Left("Failed Load Data"); } @@ -112,7 +122,8 @@ class OrderRemoteDatasource { } } - Future> getPaymentMethodByRangeDate( + Future> + getPaymentMethodByRangeDate( String startDate, String endDate, ) async { @@ -134,7 +145,8 @@ class OrderRemoteDatasource { print("✅ getPaymentMethodByRangeDate API call successful"); return Right(PaymentMethodResponseModel.fromJson(response.body)); } else { - print("❌ getPaymentMethodByRangeDate API call failed - Status: ${response.statusCode}"); + print( + "❌ getPaymentMethodByRangeDate API call failed - Status: ${response.statusCode}"); print("❌ Error Response: ${response.body}"); return const Left("Failed Load Payment Method Data"); } @@ -162,16 +174,17 @@ class OrderRemoteDatasource { 'order_items': orderItems, }), ); - + print("📥 Add Order Items HTTP Status Code: ${response.statusCode}"); print("📥 Add Order Items Response Body: ${response.body}"); print("📥 Add Order Items Response Headers: ${response.headers}"); - + if (response.statusCode == 200) { print("✅ addOrderItems API call successful"); return const Right(true); } else { - print("❌ addOrderItems API call failed - Status: ${response.statusCode}"); + print( + "❌ addOrderItems API call failed - Status: ${response.statusCode}"); print("❌ Error Response: ${response.body}"); return Left("Failed to add order items: ${response.body}"); } @@ -180,4 +193,432 @@ class OrderRemoteDatasource { return Left("Failed: $e"); } } + + // New Api + Future> createOrder( + OrderRequestModel orderModel, + ) async { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/orders'; + + try { + final response = await dio.post( + url, + data: orderModel.toMap(), + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + return Right(OrderDetailResponseModel.fromMap(response.data)); + } else { + return const Left('Gagal membuat pesanan'); + } + } on DioException catch (e) { + final errorMessage = + e.response?.data['message'] ?? 'Terjadi kesalahan, coba lagi nanti.'; + log("💥 Dio error: ${e.message}"); + log("💥 Dio response: ${e.response?.data}"); + return Left(errorMessage); + } catch (e) { + log("💥 Unexpected error: $e"); + return const Left('Terjadi kesalahan tak terduga'); + } + } + + Future> createPayment( + PaymentRequestModel orderModel) async { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/payments'; + + try { + final response = await dio.post( + url, + data: orderModel.toMap(), + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + return Right(PaymentSuccessResponseModel.fromMap(response.data)); + } else { + return const Left('Gagal membuat pembayaran'); + } + } on DioException catch (e) { + final errorMessage = + e.response?.data['message'] ?? 'Terjadi kesalahan, coba lagi nanti.'; + log("💥 Dio error: ${e.message}"); + log("💥 Dio response: ${e.response?.data}"); + return Left(errorMessage); + } catch (e) { + log("💥 Unexpected error: $e"); + return const Left('Terjadi kesalahan tak terduga'); + } + } + + Future> getOrder({ + int page = 1, + int limit = Variables.defaultLimit, + String status = 'completed', + required DateTime dateFrom, + required DateTime dateTo, + String? search, + }) async { + try { + final authData = await AuthLocalDataSource().getAuthData(); + + Map params = { + 'page': page, + 'limit': limit, + 'status': status, + 'date_from': DateFormat('dd-MM-yyyy').format(dateFrom), + 'date_to': DateFormat('dd-MM-yyyy').format(dateTo), + }; + + if (search != null && search.isNotEmpty) { + params['search'] = search; + } + + final response = await dio.get( + '${Variables.baseUrl}/api/v1/orders', + queryParameters: params, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + return Right(OrderResponseModel.fromMap(response.data)); + } else { + log("❌ getOrderByRangeDate API call failed - Status: ${response.statusCode}"); + return const Left("Failed Load Data"); + } + } on DioException catch (e) { + final errorMessage = 'Something went wrong'; + log("💥 Dio error: ${e.message}"); + log("💥 Dio response: ${e.response?.data}"); + return Left(errorMessage); + } catch (e) { + log("💥 Unexpected error: $e"); + return Left("Unexpected Error: $e"); + } + } + + Future> getOrderById( + {required String orderId}) async { + try { + final authData = await AuthLocalDataSource().getAuthData(); + final response = await dio.get( + '${Variables.baseUrl}/api/v1/orders/$orderId', + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + return Right(OrderDetailResponseModel.fromMap(response.data)); + } else { + log("❌ OrderDetailResponseModel API call failed - Status: ${response.statusCode}"); + return const Left("Failed Load Data"); + } + } on DioException catch (e) { + final errorMessage = 'Something went wrong'; + log("💥 Dio error: ${e.message}"); + log("💥 Dio response: ${e.response?.data}"); + return Left(errorMessage); + } catch (e) { + log("💥 Unexpected error: $e"); + return Left("Unexpected Error: $e"); + } + } + + Future> createOrderWithPayment( + OrderRequestModel orderModel, + PaymentMethod payment, + ) async { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/orders'; + + try { + final response = await dio.post( + url, + data: orderModel.toMap(), + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + final data = OrderDetailResponseModel.fromMap(response.data); + final orderData = data.data; + final paymentRequest = PaymentRequestModel( + orderId: orderData?.id, + amount: orderData?.totalAmount, + paymentMethodId: payment.id, + splitDescription: '', + splitNumber: 1, + splitTotal: 1, + paymentOrderItems: orderData?.orderItems + ?.map((item) => PaymentOrderItemModel( + amount: item.totalPrice, + orderItemId: item.id, + )) + .toList(), + ); + + createPayment(paymentRequest); + return Right(data); + } else { + return const Left('Gagal membuat pesanan'); + } + } on DioException catch (e) { + final errorMessage = + e.response?.data['message'] ?? 'Terjadi kesalahan, coba lagi nanti.'; + log("💥 Dio error: ${e.message}"); + log("💥 Dio response: ${e.response?.data}"); + return Left(errorMessage); + } catch (e) { + log("💥 Unexpected error: $e"); + return const Left('Terjadi kesalahan tak terduga'); + } + } + + Future> addToOrder({ + required String orderId, + required List orderItems, + }) async { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/orders/$orderId/add-items'; + + try { + final response = await dio.post( + url, + data: { + "order_items": orderItems.map((item) => item.toMap()).toList(), + 'notes': '', + }, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + return Right(true); + } else { + return const Left('Gagal menambahkan pesanan pesanan'); + } + } on DioException catch (e) { + final errorMessage = + e.response?.data['message'] ?? 'Terjadi kesalahan, coba lagi nanti.'; + log("💥 Dio error: ${e.message}"); + log("💥 Dio response: ${e.response?.data}"); + return Left(errorMessage); + } catch (e) { + log("💥 Unexpected error: $e"); + return const Left('Terjadi kesalahan, coba lagi nanti.'); + } + } + + Future> refund({ + required String orderId, + required String reason, + required List orderItems, + }) async { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/orders/$orderId/refund'; + + final int refundAmount = orderItems.fold( + 0, + (sum, item) => sum + ((item.unitPrice ?? 0) * (item.quantity ?? 0)), + ); + try { + final response = await dio.post( + url, + data: { + 'refund_amount': refundAmount, + "order_items": orderItems + .map((item) => { + 'order_item_id': item.id, + "refund_quantity": item.quantity, + "refund_amount": item.totalPrice, + "reason": "" + }) + .toList(), + 'reason': reason, + }, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + return Right(true); + } else { + return const Left('Gagal refund'); + } + } on DioException catch (e) { + final errorMessage = + e.response?.data['message'] ?? 'Terjadi kesalahan, coba lagi nanti.'; + log("💥 Dio error: ${e.message}"); + log("💥 Dio response: ${e.response?.data}"); + return Left(errorMessage); + } catch (e) { + log("💥 Unexpected error: $e"); + return const Left('Terjadi kesalahan tak terduga'); + } + } + + Future> voidOrder({ + required String orderId, + required String reason, + String type = "ITEM", // TYPE: ALL, ITEM + required List orderItems, + }) async { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/orders/void'; + + try { + final response = await dio.post( + url, + data: { + 'order_id': orderId, + 'type': orderItems.isEmpty ? "ALL" : type, + 'reason': reason, + "items": orderItems + .map((item) => { + 'order_item_id': item.id, + "quantity": item.quantity, + }) + .toList(), + }, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + return Right(true); + } else { + return const Left('Gagal refund'); + } + } on DioException catch (e) { + final errorMessage = + e.response?.data['message'] ?? 'Terjadi kesalahan, coba lagi nanti.'; + log("💥 Dio error: ${e.message}"); + log("💥 Dio response: ${e.response?.data}"); + return Left(errorMessage); + } catch (e) { + log("💥 Unexpected error: $e"); + return const Left('Terjadi kesalahan tak terduga'); + } + } + + Future> refundPayment({ + required String orderId, + required String reason, + required int refundAmount, + }) async { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/orders/$orderId/refund'; + + try { + final response = await dio.post( + url, + data: { + 'refund_amount': refundAmount, + 'reason': reason, + }, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + return Right(true); + } else { + return const Left('Gagal refund'); + } + } on DioException catch (e) { + final errorMessage = 'Terjadi kesalahan coba lagi nanti'; + log("💥 Dio error: ${e.message}"); + log("💥 Dio response: ${e.response?.data}"); + return Left(errorMessage); + } catch (e) { + log("💥 Unexpected error: $e"); + return const Left('Terjadi kesalahan tak terduga'); + } + } + + Future> createPaymentSplitBill( + PaymentSplitBillRequest request) async { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/orders/split-bill'; + + try { + final response = await dio.post( + url, + data: request.toMap(), + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + return Right(PaymentSuccessResponseModel.fromMap(response.data)); + } else { + return const Left('Gagal membuat pembayaran'); + } + } on DioException catch (e) { + final errorMessage = + e.response?.data['message'] ?? 'Terjadi kesalahan, coba lagi nanti.'; + log("💥 Dio error: ${e.message}"); + log("💥 Dio response: ${e.response?.data}"); + return Left(errorMessage); + } catch (e) { + log("💥 Unexpected error: $e"); + return const Left('Terjadi kesalahan tak terduga'); + } + } } diff --git a/lib/data/datasources/outlet_local_datasource.dart b/lib/data/datasources/outlet_local_datasource.dart new file mode 100644 index 0000000..fa1b6f1 --- /dev/null +++ b/lib/data/datasources/outlet_local_datasource.dart @@ -0,0 +1,30 @@ +import 'dart:developer'; + +import 'package:enaklo_pos/presentation/home/models/outlet_model.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class OutletLocalDatasource { + Future save(Outlet outlet) async { + try { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('outlet', outlet.toJson()); + log('Outlet Local Data: ${outlet.toJson()}'); + } catch (e) { + log('Error saving outlet: $e'); + } + } + + Future remove() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('outlet'); + } + + Future get() async { + final prefs = await SharedPreferences.getInstance(); + final outlet = prefs.getString('outlet'); + + log('Outlet Local Data: $outlet'); + + return Outlet.fromJson(outlet!); + } +} diff --git a/lib/data/datasources/outlet_remote_data_source.dart b/lib/data/datasources/outlet_remote_data_source.dart new file mode 100644 index 0000000..c1a66b0 --- /dev/null +++ b/lib/data/datasources/outlet_remote_data_source.dart @@ -0,0 +1,96 @@ +import 'dart:developer'; +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:enaklo_pos/core/network/dio_client.dart'; +import 'package:enaklo_pos/data/datasources/outlet_local_datasource.dart'; +import 'package:enaklo_pos/data/datasources/settings_local_datasource.dart'; +import 'package:enaklo_pos/presentation/home/models/outlet_model.dart'; +import 'package:enaklo_pos/presentation/setting/models/tax_model.dart'; +import '../../core/constants/variables.dart'; +import 'auth_local_datasource.dart'; + +class OutletRemoteDataSource { + final Dio dio = DioClient.instance; + + Future> getOutlets() async { + try { + final authData = await AuthLocalDataSource().getAuthData(); + + final url = '${Variables.baseUrl}/api/v1/outlets/list'; + + final response = await dio.get( + url, + queryParameters: { + 'organization_id': authData.user?.organizationId, + }, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + final data = OutletResponse.fromMap(response.data); + return Right(data); + } else { + return const Left('Failed to get outlets'); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Gagal mengambil outlet'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); + } + } + + Future> currentOutlet() async { + try { + final authData = await AuthLocalDataSource().getAuthData(); + + if (authData.user?.outletId == null) { + return const Left('Kamu belum memiliki bergabung dengan outlet'); + } + + final url = + '${Variables.baseUrl}/api/v1/outlets/detail/${authData.user?.outletId}'; + + final response = await dio.get( + url, + queryParameters: { + 'organization_id': authData.user?.organizationId, + }, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + final data = OutletDetailResponse.fromMap(response.data); + await SettingsLocalDatasource().saveTax( + TaxModel( + name: 'PB1', + type: TaxType.pajak, + value: data.data?.taxRate ?? 0, + ), + ); + + await OutletLocalDatasource().save(data.data!); + return Right(data); + } else { + return const Left('Failed to get outlets'); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Gagal mengambil outlet'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); + } + } +} diff --git a/lib/data/datasources/payment_methods_remote_datasource.dart b/lib/data/datasources/payment_methods_remote_datasource.dart index 4ca105e..e602681 100644 --- a/lib/data/datasources/payment_methods_remote_datasource.dart +++ b/lib/data/datasources/payment_methods_remote_datasource.dart @@ -1,34 +1,40 @@ import 'dart:developer'; import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; import 'package:enaklo_pos/core/constants/variables.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/models/response/payment_methods_response_model.dart'; -import 'package:http/http.dart' as http; class PaymentMethodsRemoteDatasource { - Future> getPaymentMethods() async { + final Dio dio = DioClient.instance; + Future> + getPaymentMethods() async { try { final authData = await AuthLocalDataSource().getAuthData(); - final response = await http.get( - Uri.parse('${Variables.baseUrl}/api/payment-methods'), - headers: { - 'Authorization': 'Bearer ${authData.token}', - 'Accept': 'application/json', - }, + final response = await dio.get( + '${Variables.baseUrl}/api/v1/payment-methods', + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + }, + ), ); - - log("Payment Methods Response Status: ${response.statusCode}"); - log("Payment Methods Response Body: ${response.body}"); - + if (response.statusCode == 200) { - return Right(PaymentMethodsResponseModel.fromJson(response.body)); + return Right(PaymentMethodsResponseModel.fromMap(response.data)); } else { return const Left('Failed to get payment methods'); } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left( + e.response?.data['message'] ?? 'Failed to get payment methods'); } catch (e) { log("Error getting payment methods: $e"); - return Left('Error: $e'); + return Left('Unexpected error'); } } -} \ No newline at end of file +} diff --git a/lib/data/datasources/product_local_datasource.dart b/lib/data/datasources/product_local_datasource.dart index a7b63e9..6610f6e 100644 --- a/lib/data/datasources/product_local_datasource.dart +++ b/lib/data/datasources/product_local_datasource.dart @@ -7,7 +7,6 @@ import 'package:enaklo_pos/data/models/response/table_model.dart'; import 'package:enaklo_pos/presentation/home/models/order_model.dart'; import 'package:enaklo_pos/presentation/table/models/draft_order_item.dart'; import 'package:enaklo_pos/presentation/table/models/draft_order_model.dart'; -import 'package:intl/intl.dart'; import 'package:sqflite/sqflite.dart'; import '../../presentation/home/models/product_quantity.dart'; @@ -145,31 +144,32 @@ class ProductLocalDatasource { Future _initDB(String filePath) async { final dbPath = await getDatabasesPath(); final path = dbPath + filePath; - + // Force delete existing database to ensure new schema try { final dbExists = await databaseExists(path); if (dbExists) { log("Deleting existing database to ensure new schema with order_type column"); - await deleteDatabase(path); + // await deleteDatabase(path); } } catch (e) { log("Error deleting database: $e"); } - + return await openDatabase( - path, - version: 2, + path, + version: 2, onCreate: _createDb, onUpgrade: _onUpgrade, ); } - + Future _onUpgrade(Database db, int oldVersion, int newVersion) async { if (oldVersion < 2) { // Add order_type column to orders table if it doesn't exist try { - await db.execute('ALTER TABLE $tableOrder ADD COLUMN order_type TEXT DEFAULT "DINE IN"'); + await db.execute( + 'ALTER TABLE $tableOrder ADD COLUMN order_type TEXT DEFAULT "DINE IN"'); log("Added order_type column to orders table"); } catch (e) { log("order_type column might already exist: $e"); @@ -186,11 +186,11 @@ class ProductLocalDatasource { //save order Future saveOrder(OrderModel order) async { final db = await instance.database; - + // Since we're forcing database recreation, order_type column should exist final orderMap = order.toMap(includeOrderType: true); log("Final orderMap for insertion: $orderMap"); - + int id = await db.insert(tableOrder, orderMap, conflictAlgorithm: ConflictAlgorithm.replace); @@ -238,6 +238,31 @@ class ProductLocalDatasource { }); } + Future> getAllOrderByRange( + DateTime start, DateTime end) async { + final db = await instance.database; + + // Format ke ISO 8601 untuk range, hasil: yyyy-MM-ddTHH:mm:ss + final startIso = start.toIso8601String(); + final endIso = end.toIso8601String(); + + final startDateYYYYMMDD = startIso.substring(0, 10); + final endDateYYYYMMDD = endIso.substring(0, 10); + + final List> maps = await db.query( + tableOrder, + where: 'substr(transaction_time, 1, 10) BETWEEN ? AND ?', + whereArgs: [startDateYYYYMMDD, endDateYYYYMMDD], + orderBy: 'transaction_time DESC', + ); + log("Get All Order By Range: $startDateYYYYMMDD $endDateYYYYMMDD"); + + return List.generate(maps.length, (i) { + log("Save save OrderModel: ${OrderModel.fromMap(maps[i])}"); + return OrderModel.fromMap(maps[i]); + }); + } + //get order item by order id Future> getOrderItemByOrderId(int orderId) async { final db = await instance.database; @@ -283,7 +308,7 @@ class ProductLocalDatasource { tableProduct, product.toLocalMap(), where: 'product_id = ?', - whereArgs: [product.productId], + whereArgs: [product.id], ); } @@ -294,7 +319,7 @@ class ProductLocalDatasource { for (var product in products) { await db.insert(tableProduct, product.toLocalMap(), conflictAlgorithm: ConflictAlgorithm.replace); - log('inserted success id: ${product.productId} | name: ${product.name} | price: ${product.price} | Printer Type ${product.printerType}'); + log('inserted success id: ${product.id} | name: ${product.name} | price: ${product.price} '); } } @@ -333,19 +358,19 @@ class ProductLocalDatasource { // generate table managent with count Future createTableManagement(String tableName, Offset position) async { - final db = await instance.database; - TableModel newTable = TableModel( - tableName: tableName, - status: 'available', - orderId: 0, - paymentAmount: 0, - startTime: DateTime.now().toIso8601String(), - position: position, - ); - await db.insert( - tableManagement, - newTable.toMap(), - ); + // final db = await instance.database; + // TableModel newTable = TableModel( + // tableName: tableName, + // status: 'available', + // orderId: 0, + // paymentAmount: 0, + // startTime: DateTime.now().toIso8601String(), + // position: position, + // ); + // await db.insert( + // tableManagement, + // newTable.toMap(), + // ); } // change position table @@ -402,12 +427,12 @@ class ProductLocalDatasource { Future> getTableByStatus(String status) async { final db = await instance.database; List> maps; - + if (status == 'all') { // Get all tables maps = await db.query(tableManagement); log("Getting all tables, found: ${maps.length}"); - + // If no tables exist, create some default tables if (maps.isEmpty) { log("No tables found, creating default tables..."); @@ -428,19 +453,19 @@ class ProductLocalDatasource { final tables = List.generate(maps.length, (i) { return TableModel.fromMap(maps[i]); }); - + log("Returning ${tables.length} tables"); tables.forEach((table) { log("Table: ${table.tableName} (ID: ${table.id}, Status: ${table.status})"); }); - + return tables; } // Create default tables if none exist Future _createDefaultTables() async { final db = await instance.database; - + // Create 5 default tables for (int i = 1; i <= 5; i++) { await db.insert(tableManagement, { @@ -463,7 +488,7 @@ class ProductLocalDatasource { await db.update(tableManagement, table.toMap(), where: 'id = ?', whereArgs: [table.id]); log("Success Update Status Table: ${table.toMap()}"); - + // Verify the update final updatedTable = await db.query( tableManagement, @@ -474,7 +499,7 @@ class ProductLocalDatasource { log("Verified table update: ${updatedTable.first}"); } } - + // Debug method to reset all tables to available status Future resetAllTablesToAvailable() async { log("Resetting all tables to available status..."); @@ -564,7 +589,7 @@ class ProductLocalDatasource { //update draft order Future updateDraftOrder(DraftOrderModel draftOrder) async { final db = await instance.database; - + // Update the draft order await db.update( 'draft_orders', @@ -572,13 +597,14 @@ class ProductLocalDatasource { where: 'id = ?', whereArgs: [draftOrder.id], ); - + // Remove existing items and add new ones await db.delete('draft_order_items', where: 'id_draft_order = ?', whereArgs: [draftOrder.id]); - + for (var orderItem in draftOrder.orders) { - await db.insert('draft_order_items', orderItem.toMapForLocal(draftOrder.id!)); + await db.insert( + 'draft_order_items', orderItem.toMapForLocal(draftOrder.id!)); } } diff --git a/lib/data/datasources/product_remote_datasource.dart b/lib/data/datasources/product_remote_datasource.dart index dfc543d..de45da8 100644 --- a/lib/data/datasources/product_remote_datasource.dart +++ b/lib/data/datasources/product_remote_datasource.dart @@ -1,94 +1,126 @@ import 'dart:developer'; import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:enaklo_pos/core/network/dio_client.dart'; import 'package:enaklo_pos/data/models/request/product_request_model.dart'; import 'package:enaklo_pos/data/models/response/add_product_response_model.dart'; import 'package:enaklo_pos/data/models/response/product_response_model.dart'; -import 'package:http/http.dart' as http; import '../../core/constants/variables.dart'; import 'auth_local_datasource.dart'; class ProductRemoteDatasource { - Future> getProducts() async { - final url = Uri.parse('${Variables.baseUrl}/api/products'); - final authData = await AuthLocalDataSource().getAuthData(); - final response = await http.get(url, headers: { - 'Authorization': 'Bearer ${authData.token}', - 'Accept': 'application/json', - }); - log("Status Code: ${response.statusCode}"); - log("Body: ${response.body}"); - if (response.statusCode == 200) { - return Right(ProductResponseModel.fromJson(response.body)); - } else { - return const Left('Failed to get products'); + final Dio dio = DioClient.instance; + + Future> getProducts({ + int page = 1, + int limit = Variables.defaultLimit, + String? categoryId, + String? search, + }) async { + try { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/products'; + + Map queryParameters = { + 'page': page, + 'limit': limit, + }; + + if (categoryId != null) { + queryParameters['category_id'] = categoryId; + } + + if (search != null && search.isNotEmpty) { + queryParameters['search'] = search; + } + + final response = await dio.get( + url, + queryParameters: queryParameters, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + return Right(ProductResponseModel.fromMap(response.data)); + } else { + return const Left('Failed to get products'); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Gagal mengambil produk'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); } } Future> addProduct( ProductRequestModel productRequestModel) async { - final authData = await AuthLocalDataSource().getAuthData(); - final Map headers = { - 'Authorization': 'Bearer ${authData.token}', - }; - var request = http.MultipartRequest( - 'POST', Uri.parse('${Variables.baseUrl}/api/products')); - request.fields.addAll(productRequestModel.toMap()); - request.files.add(await http.MultipartFile.fromPath( - 'image', productRequestModel.image!.path)); - request.headers.addAll(headers); + try { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/products'; - http.StreamedResponse response = await request.send(); + final response = await dio.post( + url, + data: productRequestModel.toMap(), + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + }, + ), + ); - final String body = await response.stream.bytesToString(); - log(response.stream.toString()); - log(response.statusCode.toString()); - if (response.statusCode == 201) { - return right(AddProductResponseModel.fromJson(body)); - } else { - return left(body); + if (response.statusCode == 200) { + return Right(AddProductResponseModel.fromMap(response.data)); + } else { + return const Left('Failed to create products'); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Gagal menambah produk'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); } } Future> updateProduct( ProductRequestModel productRequestModel) async { - final authData = await AuthLocalDataSource().getAuthData(); - final Map headers = { - 'Authorization': 'Bearer ${authData.token}', - }; - - log("Update Product Request Data: ${productRequestModel.toMap()}"); - log("Update Product ID: ${productRequestModel.id}"); - log("Update Product Name: ${productRequestModel.name}"); - log("Update Product Price: ${productRequestModel.price}"); - log("Update Product Stock: ${productRequestModel.stock}"); - log("Update Product Category ID: ${productRequestModel.categoryId}"); - log("Update Product Is Best Seller: ${productRequestModel.isBestSeller}"); - log("Update Product Printer Type: ${productRequestModel.printerType}"); - log("Update Product Has Image: ${productRequestModel.image != null}"); - - var request = http.MultipartRequest( - 'POST', Uri.parse('${Variables.baseUrl}/api/products/edit')); - request.fields.addAll(productRequestModel.toMap()); - if (productRequestModel.image != null) { - request.files.add(await http.MultipartFile.fromPath( - 'image', productRequestModel.image!.path)); - } - request.headers.addAll(headers); + try { + final authData = await AuthLocalDataSource().getAuthData(); + final url = + '${Variables.baseUrl}/api/v1/products/${productRequestModel.id}'; - log("Update Product Request Fields: ${request.fields}"); - log("Update Product Request Files: ${request.files.length}"); + final response = await dio.put( + url, + data: productRequestModel.toMap(), + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + }, + ), + ); - http.StreamedResponse response = await request.send(); - - final String body = await response.stream.bytesToString(); - log("Update Product Status Code: ${response.statusCode}"); - log("Update Product Body: $body"); - if (response.statusCode == 200) { - return right(AddProductResponseModel.fromJson(body)); - } else { - return left(body); + if (response.statusCode == 200) { + return Right(AddProductResponseModel.fromMap(response.data)); + } else { + return const Left('Failed to update products'); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Gagal update produk'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); } } } diff --git a/lib/data/datasources/table_remote_datasource.dart b/lib/data/datasources/table_remote_datasource.dart new file mode 100644 index 0000000..67d5c98 --- /dev/null +++ b/lib/data/datasources/table_remote_datasource.dart @@ -0,0 +1,170 @@ +import 'dart:developer'; +import 'dart:ui'; +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:enaklo_pos/core/network/dio_client.dart'; +import 'package:enaklo_pos/data/models/response/table_model.dart'; +import '../../core/constants/variables.dart'; +import 'auth_local_datasource.dart'; + +class TableRemoteDataSource { + final Dio dio = DioClient.instance; + + Future> createTable({ + required String tableName, + required int capacity, + required String location, + }) async { + try { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/tables'; + + final response = await dio.post( + url, + data: { + "outlet_id": authData.user?.outletId, + "table_name": tableName, + "capacity": capacity, + "location": location, + "status": "available", + "is_active": true, + "position_x": 200, + "position_y": 200, + }, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + }, + ), + ); + + if (response.statusCode == 200 || response.statusCode == 201) { + return Right(true); + } else { + return const Left('Failed to create table'); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Gagal membuat table'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); + } + } + + Future> getTable({ + int page = 1, + int limit = 50, + String? status, + }) async { + try { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/tables'; + + Map queryParameters = { + 'page': page, + 'limit': limit, + }; + + if (status != null) { + queryParameters['status'] = status; + } + + final response = await dio.get( + url, + queryParameters: queryParameters, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + return Right(TableResponseModel.fromMap(response.data)); + } else { + return const Left('Failed to get tables'); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Gagal mengambil data meja'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); + } + } + + Future> updatePosition({ + required String tableId, + required Offset position, + }) async { + try { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/tables/$tableId'; + + final response = await dio.put( + url, + data: { + "position_x": position.dx.round(), + "position_y": position.dy.round(), + }, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + }, + ), + ); + + if (response.statusCode == 200 || response.statusCode == 201) { + return Right(true); + } else { + return const Left('Failed to create table'); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Gagal membuat table'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); + } + } + + Future> transferTable({ + required String fromTableId, + required String toTableId, + }) async { + try { + final authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/tables/transfer'; + + final response = await dio.put( + url, + data: { + "from_table": fromTableId, + "to_table": toTableId, + }, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + }, + ), + ); + + if (response.statusCode == 200 || response.statusCode == 201) { + return Right(true); + } else { + return const Left('Failed to create table'); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Gagal membuat table'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); + } + } +} diff --git a/lib/data/datasources/user_remote_datasource.dart b/lib/data/datasources/user_remote_datasource.dart new file mode 100644 index 0000000..c86c820 --- /dev/null +++ b/lib/data/datasources/user_remote_datasource.dart @@ -0,0 +1,47 @@ +import 'dart:developer'; + +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:enaklo_pos/core/constants/variables.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/models/response/auth_response_model.dart'; + +class UserRemoteDatasource { + final Dio dio = DioClient.instance; + + Future> updateOutlet(String outletId) async { + AuthResponseModel authData = await AuthLocalDataSource().getAuthData(); + final url = '${Variables.baseUrl}/api/v1/users/${authData.user?.id}'; + + try { + final response = await dio.put( + url, + data: { + 'outlet_id': outletId, + }, + options: Options( + headers: { + 'Authorization': 'Bearer ${authData.token}', + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + ), + ); + + if (response.statusCode == 200) { + authData.user?.outletId = response.data['outlet_id']; + await AuthLocalDataSource().saveAuthData(authData); + return Right(true); + } else { + return const Left('Failed to login'); + } + } on DioException catch (e) { + log("Dio error: ${e.message}"); + return Left(e.response?.data['message'] ?? 'Login gagal'); + } catch (e) { + log("Unexpected error: $e"); + return const Left('Unexpected error occurred'); + } + } +} diff --git a/lib/data/models/request/payment_request.dart b/lib/data/models/request/payment_request.dart new file mode 100644 index 0000000..37f6e55 --- /dev/null +++ b/lib/data/models/request/payment_request.dart @@ -0,0 +1,137 @@ +import 'dart:convert'; + +class PaymentRequestModel { + final String? orderId; + final String? paymentMethodId; + final int? amount; + final String? transactionId; + final int? splitNumber; + final int? splitTotal; + final String? splitDescription; + final List? paymentOrderItems; + + PaymentRequestModel({ + this.orderId, + this.paymentMethodId, + this.amount, + this.transactionId, + this.splitNumber, + this.splitTotal, + this.splitDescription, + this.paymentOrderItems, + }); + + factory PaymentRequestModel.fromJson(String str) => + PaymentRequestModel.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); + + factory PaymentRequestModel.fromMap(Map json) => + PaymentRequestModel( + orderId: json["order_id"], + paymentMethodId: json["payment_method_id"], + amount: json["amount"]?.toDouble(), + transactionId: json["transaction_id"], + splitNumber: json["split_number"], + splitTotal: json["split_total"], + splitDescription: json["split_description"], + paymentOrderItems: json["payment_order_items"] == null + ? [] + : List.from(json["payment_order_items"] + .map((x) => PaymentOrderItemModel.fromMap(x))), + ); + + Map toMap() => { + "order_id": orderId, + "payment_method_id": paymentMethodId, + "amount": amount, + "transaction_id": transactionId, + "split_number": splitNumber, + "split_total": splitTotal, + "split_description": splitDescription, + "payment_order_items": paymentOrderItems == null + ? [] + : List.from(paymentOrderItems!.map((x) => x.toMap())), + }; +} + +class PaymentOrderItemModel { + final String? orderItemId; + final int? amount; + + PaymentOrderItemModel({ + this.orderItemId, + this.amount, + }); + + factory PaymentOrderItemModel.fromJson(String str) => + PaymentOrderItemModel.fromMap(json.decode(str)); + + factory PaymentOrderItemModel.fromMap(Map json) => + PaymentOrderItemModel( + orderItemId: json["order_item_id"], + amount: json["amount"]?.toDouble(), + ); + + Map toMap() => { + "order_item_id": orderItemId, + "amount": amount, + }; +} + +class PaymentSplitBillRequest { + final String orderId; + final String paymentMethodId; + final String customerId; + final String type; // e.g., "AMOUNT" or "ITEM" + final int amount; + final List items; + + PaymentSplitBillRequest({ + required this.orderId, + required this.paymentMethodId, + required this.customerId, + required this.type, + required this.amount, + required this.items, + }); + + Map toMap() { + Map data = { + 'order_id': orderId, + 'payment_method_id': paymentMethodId, + 'type': type, + }; + + if (customerId.isNotEmpty) { + data['customer_id'] = customerId; + } + + if (type == "AMOUNT") { + data['amount'] = amount; + } + + if (type == "ITEM") { + data['items'] = items.map((e) => e.toJson()).toList(); + } + + return data; + } +} + +class SplitItem { + final String orderItemId; + final int quantity; + + SplitItem({ + required this.orderItemId, + required this.quantity, + }); + + Map toJson() { + return { + 'order_item_id': orderItemId, + 'quantity': quantity, + }; + } +} diff --git a/lib/data/models/request/product_request_model.dart b/lib/data/models/request/product_request_model.dart index 1ea57dd..3e55e1a 100644 --- a/lib/data/models/request/product_request_model.dart +++ b/lib/data/models/request/product_request_model.dart @@ -1,42 +1,51 @@ -import 'dart:developer'; - -import 'package:image_picker/image_picker.dart'; - class ProductRequestModel { - final int? id; + final String? id; final String name; + final String? description; + final String categoryId; + final String? sku; + final String? barcode; final int price; - final int stock; - final int categoryId; - final int isBestSeller; - final XFile? image; + final int cost; + final bool isActive; + final bool hasVariants; + final String imageUrl; final String? printerType; + ProductRequestModel({ this.id, required this.name, - required this.price, - required this.stock, + this.description, required this.categoryId, - required this.isBestSeller, - this.image, + this.sku, + this.barcode, + required this.price, + required this.cost, + this.isActive = true, + this.hasVariants = false, + required this.imageUrl, this.printerType, }); - Map toMap() { - log("toMap: $isBestSeller"); - final map = { + Map toMap() { + final map = { 'name': name, - 'price': price.toString(), - 'stock': stock.toString(), - 'category_id': categoryId.toString(), - 'is_best_seller': isBestSeller.toString(), + 'description': description ?? '', + 'category_id': categoryId, + 'sku': sku ?? '', + 'barcode': barcode ?? '', + 'price': price, + 'cost': cost, + 'is_active': isActive, + 'has_variants': hasVariants, + 'image_url': imageUrl, 'printer_type': printerType ?? '', }; - + if (id != null) { - map['id'] = id.toString(); + map['id'] = id; } - + return map; } } diff --git a/lib/data/models/response/add_product_response_model.dart b/lib/data/models/response/add_product_response_model.dart index 99e8af9..261ac28 100644 --- a/lib/data/models/response/add_product_response_model.dart +++ b/lib/data/models/response/add_product_response_model.dart @@ -4,12 +4,10 @@ import 'package:enaklo_pos/data/models/response/product_response_model.dart'; class AddProductResponseModel { final bool success; - final String message; final Product data; AddProductResponseModel({ required this.success, - required this.message, required this.data, }); @@ -21,13 +19,11 @@ class AddProductResponseModel { factory AddProductResponseModel.fromMap(Map json) => AddProductResponseModel( success: json["success"], - message: json["message"], data: Product.fromMap(json["data"]), ); Map toMap() => { "success": success, - "message": message, "data": data.toMap(), }; } diff --git a/lib/data/models/response/auth_response_model.dart b/lib/data/models/response/auth_response_model.dart index 56eb5f6..ff787f2 100644 --- a/lib/data/models/response/auth_response_model.dart +++ b/lib/data/models/response/auth_response_model.dart @@ -1,85 +1,83 @@ import 'dart:convert'; class AuthResponseModel { - final String? status; - final String? token; - final User? user; + final String? token; + final User? user; - AuthResponseModel({ - this.status, - this.token, - this.user, - }); + AuthResponseModel({ + this.token, + this.user, + }); - factory AuthResponseModel.fromJson(String str) => AuthResponseModel.fromMap(json.decode(str)); + factory AuthResponseModel.fromJson(String str) => + AuthResponseModel.fromMap(json.decode(str)); - String toJson() => json.encode(toMap()); + String toJson() => json.encode(toMap()); - factory AuthResponseModel.fromMap(Map json) => AuthResponseModel( - status: json["status"], + factory AuthResponseModel.fromMap(Map json) => + AuthResponseModel( token: json["token"], user: json["user"] == null ? null : User.fromMap(json["user"]), - ); + ); - Map toMap() => { - "status": status, + Map toMap() => { "token": token, "user": user?.toMap(), - }; + }; } class User { - final int? id; - final String? name; - final String? email; - final DateTime? emailVerifiedAt; - final dynamic twoFactorSecret; - final dynamic twoFactorRecoveryCodes; - final dynamic twoFactorConfirmedAt; - final DateTime? createdAt; - final DateTime? updatedAt; - final String? role; + final String? id; + final String? organizationId; + String? outletId; + final String? name; + final String? email; + final String? role; + final bool? isActive; + final DateTime? createdAt; + final DateTime? updatedAt; - User({ - this.id, - this.name, - this.email, - this.emailVerifiedAt, - this.twoFactorSecret, - this.twoFactorRecoveryCodes, - this.twoFactorConfirmedAt, - this.createdAt, - this.updatedAt, - this.role, - }); + User({ + this.id, + this.organizationId, + this.outletId, + this.name, + this.role, + this.isActive, + this.email, + this.createdAt, + this.updatedAt, + }); - factory User.fromJson(String str) => User.fromMap(json.decode(str)); + factory User.fromJson(String str) => User.fromMap(json.decode(str)); - String toJson() => json.encode(toMap()); + String toJson() => json.encode(toMap()); - factory User.fromMap(Map json) => User( + factory User.fromMap(Map json) => User( id: json["id"], + organizationId: json["organization_id"], + outletId: json["outlet_id"], name: json["name"], email: json["email"], - emailVerifiedAt: json["email_verified_at"] == null ? null : DateTime.parse(json["email_verified_at"]), - twoFactorSecret: json["two_factor_secret"], - twoFactorRecoveryCodes: json["two_factor_recovery_codes"], - twoFactorConfirmedAt: json["two_factor_confirmed_at"], - createdAt: json["created_at"] == null ? null : DateTime.parse(json["created_at"]), - updatedAt: json["updated_at"] == null ? null : DateTime.parse(json["updated_at"]), role: json["role"], - ); + isActive: json["is_active"], + createdAt: json["created_at"] == null + ? null + : DateTime.parse(json["created_at"]), + updatedAt: json["updated_at"] == null + ? null + : DateTime.parse(json["updated_at"]), + ); - Map toMap() => { + Map toMap() => { "id": id, + "organization_id": organizationId, + "outlet_id": outletId, "name": name, "email": email, - "email_verified_at": emailVerifiedAt?.toIso8601String(), - "two_factor_secret": twoFactorSecret, - "two_factor_recovery_codes": twoFactorRecoveryCodes, - "two_factor_confirmed_at": twoFactorConfirmedAt, + "role": role, + "is_active": isActive, "created_at": createdAt?.toIso8601String(), "updated_at": updatedAt?.toIso8601String(), - "role": role, - }; + }; } diff --git a/lib/data/models/response/category_response_model.dart b/lib/data/models/response/category_response_model.dart index d50ea51..7755386 100644 --- a/lib/data/models/response/category_response_model.dart +++ b/lib/data/models/response/category_response_model.dart @@ -1,91 +1,120 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:convert'; -class CategroyResponseModel { - final String status; - final List data; +class CategoryResponseModel { + final bool success; + final CategoryData data; + final dynamic errors; - CategroyResponseModel({ - required this.status, + CategoryResponseModel({ + required this.success, required this.data, + this.errors, }); - Map toMap() { - return { - 'status': status, - 'data': data.map((x) => x.toMap()).toList(), - }; - } - - factory CategroyResponseModel.fromMap(Map map) { - return CategroyResponseModel( - status: map['status'] as String, - data: List.from( - (map['data']).map( - (x) => CategoryModel.fromMap(x as Map), - ), - ), + factory CategoryResponseModel.fromMap(Map map) { + return CategoryResponseModel( + success: map['success'] as bool, + data: CategoryData.fromMap(map['data'] as Map), + errors: map['errors'], ); } - factory CategroyResponseModel.fromJson(String str) => - CategroyResponseModel.fromMap(json.decode(str)); - - String toJson() => json.encode(toMap()); -} - -class CategoryModel { - int? id; - String? name; - int? categoryId; - int? isSync; - String? image; - // DateTime createdAt; - // DateTime updatedAt; - - CategoryModel({this.id, this.name, this.categoryId, this.isSync, this.image}); + factory CategoryResponseModel.fromJson(String str) => + CategoryResponseModel.fromMap(json.decode(str)); Map toMap() { - return { - // 'id': id, - 'name': name, - 'is_sync': isSync ?? 1, - 'category_id': id, - 'image': image + return { + 'success': success, + 'data': data.toMap(), + 'errors': errors, }; } - factory CategoryModel.fromMap(Map map) { - return CategoryModel( - id: map['id'] as int?, - name: map['name'] as String?, - isSync: map['is_sync'] as int?, - categoryId: map['id'], - image: map['image']); - } - - factory CategoryModel.fromJson(String str) => - CategoryModel.fromMap(json.decode(str)); - String toJson() => json.encode(toMap()); +} - @override - bool operator ==(covariant CategoryModel other) { - if (identical(this, other)) return true; +class CategoryData { + final List categories; + final int totalCount; + final int page; + final int limit; + final int totalPages; - return other.id == id && - other.name == name && - other.categoryId == categoryId && - other.isSync == isSync && - other.image == image; + CategoryData({ + required this.categories, + required this.totalCount, + required this.page, + required this.limit, + required this.totalPages, + }); + + factory CategoryData.fromMap(Map map) { + return CategoryData( + categories: List.from( + (map['categories'] as List).map((x) => CategoryModel.fromMap(x)), + ), + totalCount: map['total_count'] as int, + page: map['page'] as int, + limit: map['limit'] as int, + totalPages: map['total_pages'] as int, + ); } - @override - int get hashCode { - return id.hashCode ^ - name.hashCode ^ - categoryId.hashCode ^ - isSync.hashCode ^ - image.hashCode; + Map toMap() { + return { + 'categories': categories.map((x) => x.toMap()).toList(), + 'total_count': totalCount, + 'page': page, + 'limit': limit, + 'total_pages': totalPages, + }; + } +} + +class CategoryModel { + String id; + final String organizationId; + final String name; + final String? description; + final String businessType; + final Map metadata; + final DateTime createdAt; + final DateTime updatedAt; + + CategoryModel({ + required this.id, + required this.organizationId, + required this.name, + this.description, + required this.businessType, + required this.metadata, + required this.createdAt, + required this.updatedAt, + }); + + factory CategoryModel.fromMap(Map map) { + return CategoryModel( + id: map['id'] as String, + organizationId: map['organization_id'] as String, + name: map['name'] as String, + description: map['description'] as String?, + businessType: map['business_type'] as String, + metadata: Map.from(map['metadata'] ?? {}), + createdAt: DateTime.parse(map['created_at'] as String), + updatedAt: DateTime.parse(map['updated_at'] as String), + ); + } + + Map toMap() { + return { + 'id': id, + 'organization_id': organizationId, + 'name': name, + 'description': description, + 'business_type': businessType, + 'metadata': metadata, + 'created_at': createdAt.toIso8601String(), + 'updated_at': updatedAt.toIso8601String(), + }; } } diff --git a/lib/data/models/response/customer_response_model.dart b/lib/data/models/response/customer_response_model.dart new file mode 100644 index 0000000..857ba4f --- /dev/null +++ b/lib/data/models/response/customer_response_model.dart @@ -0,0 +1,127 @@ +import 'dart:convert'; + +class CustomerResponseModel { + final bool? success; + final CustomerData? data; + final dynamic errors; + + CustomerResponseModel({ + this.success, + this.data, + this.errors, + }); + + factory CustomerResponseModel.fromJson(String str) => + CustomerResponseModel.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); + + factory CustomerResponseModel.fromMap(Map json) => + CustomerResponseModel( + success: json["success"], + data: json["data"] == null ? null : CustomerData.fromMap(json["data"]), + errors: json["errors"], + ); + + Map toMap() => { + "success": success, + "data": data?.toMap(), + "errors": errors, + }; +} + +class CustomerData { + final List? customers; + final int? totalCount; + final int? page; + final int? limit; + final int? totalPages; + + CustomerData({ + this.customers, + this.totalCount, + this.page, + this.limit, + this.totalPages, + }); + + factory CustomerData.fromMap(Map json) => CustomerData( + customers: json["data"] == null + ? [] + : List.from(json["data"].map((x) => Customer.fromMap(x))), + totalCount: json["total_count"], + page: json["page"], + limit: json["limit"], + totalPages: json["total_pages"], + ); + + Map toMap() => { + "data": customers == null + ? [] + : List.from(customers!.map((x) => x.toMap())), + "total_count": totalCount, + "page": page, + "limit": limit, + "total_pages": totalPages, + }; +} + +class Customer { + final String? id; + final String? organizationId; + final String? name; + final String? email; + final String? phone; + final String? address; + final bool? isDefault; + final bool? isActive; + final Map? metadata; + final DateTime? createdAt; + final DateTime? updatedAt; + + Customer({ + this.id, + this.organizationId, + this.name, + this.email, + this.phone, + this.address, + this.isDefault, + this.isActive, + this.metadata, + this.createdAt, + this.updatedAt, + }); + + factory Customer.fromMap(Map json) => Customer( + id: json["id"], + organizationId: json["organization_id"], + name: json["name"], + email: json["email"], + phone: json["phone"], + address: json["address"], + isDefault: json["is_default"], + isActive: json["is_active"], + metadata: json["metadata"] ?? {}, + createdAt: json["created_at"] == null + ? null + : DateTime.parse(json["created_at"]), + updatedAt: json["updated_at"] == null + ? null + : DateTime.parse(json["updated_at"]), + ); + + Map toMap() => { + "id": id, + "organization_id": organizationId, + "name": name, + "email": email, + "phone": phone, + "address": address, + "is_default": isDefault, + "is_active": isActive, + "metadata": metadata, + "created_at": createdAt?.toIso8601String(), + "updated_at": updatedAt?.toIso8601String(), + }; +} diff --git a/lib/data/models/response/dashboard_analytic_response_model.dart b/lib/data/models/response/dashboard_analytic_response_model.dart new file mode 100644 index 0000000..e9ac3d2 --- /dev/null +++ b/lib/data/models/response/dashboard_analytic_response_model.dart @@ -0,0 +1,248 @@ +class DashboardAnalyticResponseModel { + final bool success; + final DashboardAnalyticData data; + final dynamic errors; + + DashboardAnalyticResponseModel({ + required this.success, + required this.data, + this.errors, + }); + + /// Khusus untuk JSON string + factory DashboardAnalyticResponseModel.fromJson(Map json) => + DashboardAnalyticResponseModel.fromMap(json); + + /// Untuk menerima Map biasa (bukan dari JSON string) + factory DashboardAnalyticResponseModel.fromMap(Map map) { + return DashboardAnalyticResponseModel( + success: map['success'] ?? false, + data: DashboardAnalyticData.fromMap(map['data'] ?? {}), + errors: map['errors'], + ); + } + + Map toJson() => toMap(); + + Map toMap() => { + 'success': success, + 'data': data.toMap(), + 'errors': errors, + }; +} + +class DashboardAnalyticData { + final String organizationId; + final String outletId; + final String dateFrom; + final String dateTo; + final DashboardOverview overview; + final List topProducts; + final List paymentMethods; + final List recentSales; + + DashboardAnalyticData({ + required this.organizationId, + required this.outletId, + required this.dateFrom, + required this.dateTo, + required this.overview, + required this.topProducts, + required this.paymentMethods, + required this.recentSales, + }); + + factory DashboardAnalyticData.fromMap(Map map) => + DashboardAnalyticData( + organizationId: map['organization_id'], + outletId: map['outlet_id'], + dateFrom: map['date_from'], + dateTo: map['date_to'], + overview: DashboardOverview.fromMap(map['overview']), + topProducts: map['top_products'] == null + ? [] + : List.from( + map['top_products']?.map((x) => TopProduct.fromMap(x))), + paymentMethods: map['payment_methods'] == null + ? [] + : List.from(map['payment_methods'] + ?.map((x) => PaymentMethodAnalytic.fromMap(x))), + recentSales: map['recent_sales'] == null + ? [] + : List.from( + map['recent_sales']?.map((x) => RecentSale.fromMap(x))), + ); + + Map toMap() => { + 'organization_id': organizationId, + 'outlet_id': outletId, + 'date_from': dateFrom, + 'date_to': dateTo, + 'overview': overview.toMap(), + 'top_products': topProducts.map((x) => x.toMap()).toList(), + 'payment_methods': paymentMethods.map((x) => x.toMap()).toList(), + 'recent_sales': recentSales.map((x) => x.toMap()).toList(), + }; +} + +class DashboardOverview { + final int totalSales; + final int totalOrders; + final double averageOrderValue; + final int totalCustomers; + final int voidedOrders; + final int refundedOrders; + + DashboardOverview({ + required this.totalSales, + required this.totalOrders, + required this.averageOrderValue, + required this.totalCustomers, + required this.voidedOrders, + required this.refundedOrders, + }); + + factory DashboardOverview.fromMap(Map map) => + DashboardOverview( + totalSales: map['total_sales'], + totalOrders: map['total_orders'], + averageOrderValue: map['average_order_value']?.toDouble() ?? 0.0, + totalCustomers: map['total_customers'], + voidedOrders: map['voided_orders'], + refundedOrders: map['refunded_orders'], + ); + + Map toMap() => { + 'total_sales': totalSales, + 'total_orders': totalOrders, + 'average_order_value': averageOrderValue, + 'total_customers': totalCustomers, + 'voided_orders': voidedOrders, + 'refunded_orders': refundedOrders, + }; +} + +class TopProduct { + final String productId; + final String productName; + final String categoryId; + final String categoryName; + final int quantitySold; + final int revenue; + final double averagePrice; + final int orderCount; + + TopProduct({ + required this.productId, + required this.productName, + required this.categoryId, + required this.categoryName, + required this.quantitySold, + required this.revenue, + required this.averagePrice, + required this.orderCount, + }); + + factory TopProduct.fromMap(Map map) => TopProduct( + productId: map['product_id'], + productName: map['product_name'], + categoryId: map['category_id'], + categoryName: map['category_name'], + quantitySold: map['quantity_sold'], + revenue: map['revenue'], + averagePrice: map['average_price']?.toDouble() ?? 0.0, + orderCount: map['order_count'], + ); + + Map toMap() => { + 'product_id': productId, + 'product_name': productName, + 'category_id': categoryId, + 'category_name': categoryName, + 'quantity_sold': quantitySold, + 'revenue': revenue, + 'average_price': averagePrice, + 'order_count': orderCount, + }; +} + +class PaymentMethodAnalytic { + final String paymentMethodId; + final String paymentMethodName; + final String paymentMethodType; + final int totalAmount; + final int orderCount; + final int paymentCount; + final double percentage; + + PaymentMethodAnalytic({ + required this.paymentMethodId, + required this.paymentMethodName, + required this.paymentMethodType, + required this.totalAmount, + required this.orderCount, + required this.paymentCount, + required this.percentage, + }); + + factory PaymentMethodAnalytic.fromMap(Map map) => + PaymentMethodAnalytic( + paymentMethodId: map['payment_method_id'], + paymentMethodName: map['payment_method_name'], + paymentMethodType: map['payment_method_type'], + totalAmount: map['total_amount'], + orderCount: map['order_count'], + paymentCount: map['payment_count'], + percentage: map['percentage']?.toDouble() ?? 0.0, + ); + + Map toMap() => { + 'payment_method_id': paymentMethodId, + 'payment_method_name': paymentMethodName, + 'payment_method_type': paymentMethodType, + 'total_amount': totalAmount, + 'order_count': orderCount, + 'payment_count': paymentCount, + 'percentage': percentage, + }; +} + +class RecentSale { + final String date; + final int sales; + final int orders; + final int items; + final int tax; + final int discount; + final int netSales; + + RecentSale({ + required this.date, + required this.sales, + required this.orders, + required this.items, + required this.tax, + required this.discount, + required this.netSales, + }); + + factory RecentSale.fromMap(Map map) => RecentSale( + date: map['date'], + sales: map['sales'], + orders: map['orders'], + items: map['items'], + tax: map['tax'], + discount: map['discount'], + netSales: map['net_sales'], + ); + + Map toMap() => { + 'date': date, + 'sales': sales, + 'orders': orders, + 'items': items, + 'tax': tax, + 'discount': discount, + 'net_sales': netSales, + }; +} diff --git a/lib/data/models/response/delivery_response_model.dart b/lib/data/models/response/delivery_response_model.dart new file mode 100644 index 0000000..6debc2c --- /dev/null +++ b/lib/data/models/response/delivery_response_model.dart @@ -0,0 +1,11 @@ +class DeliveryModel { + String id; + String name; + String imageUrl; + + DeliveryModel({ + required this.id, + required this.name, + required this.imageUrl, + }); +} diff --git a/lib/data/models/response/file_response_model.dart b/lib/data/models/response/file_response_model.dart new file mode 100644 index 0000000..777d67f --- /dev/null +++ b/lib/data/models/response/file_response_model.dart @@ -0,0 +1,154 @@ +class FileResponseModel { + final FileModel data; + final String message; + final bool success; + + FileResponseModel({ + required this.data, + required this.message, + required this.success, + }); + + factory FileResponseModel.fromJson(Map json) { + return FileResponseModel( + data: FileModel.fromJson(json['data']), + message: json['message'] as String, + success: json['success'] as bool, + ); + } + + Map toJson() => { + 'data': data.toJson(), + 'message': message, + 'success': success, + }; + + factory FileResponseModel.fromMap(Map map) => + FileResponseModel.fromJson(map); + + Map toMap() => toJson(); + + FileResponseModel copyWith({ + FileModel? data, + String? message, + bool? success, + }) { + return FileResponseModel( + data: data ?? this.data, + message: message ?? this.message, + success: success ?? this.success, + ); + } + + @override + String toString() => + 'FileResponseModel(data: $data, message: $message, success: $success)'; +} + +class FileModel { + final String id; + final String organizationId; + final String userId; + final String fileName; + final String originalName; + final String fileUrl; + final int fileSize; + final String mimeType; + final String fileType; + final String uploadPath; + final bool isPublic; + final DateTime createdAt; + final DateTime updatedAt; + + FileModel({ + required this.id, + required this.organizationId, + required this.userId, + required this.fileName, + required this.originalName, + required this.fileUrl, + required this.fileSize, + required this.mimeType, + required this.fileType, + required this.uploadPath, + required this.isPublic, + required this.createdAt, + required this.updatedAt, + }); + + factory FileModel.fromJson(Map json) { + return FileModel( + id: json['id'] as String, + organizationId: json['organization_id'] as String, + userId: json['user_id'] as String, + fileName: json['file_name'] as String, + originalName: json['original_name'] as String, + fileUrl: json['file_url'] as String, + fileSize: json['file_size'] as int, + mimeType: json['mime_type'] as String, + fileType: json['file_type'] as String, + uploadPath: json['upload_path'] as String, + isPublic: json['is_public'] as bool, + createdAt: DateTime.parse(json['created_at'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + ); + } + + Map toJson() => { + 'id': id, + 'organization_id': organizationId, + 'user_id': userId, + 'file_name': fileName, + 'original_name': originalName, + 'file_url': fileUrl, + 'file_size': fileSize, + 'mime_type': mimeType, + 'file_type': fileType, + 'upload_path': uploadPath, + 'is_public': isPublic, + 'created_at': createdAt.toIso8601String(), + 'updated_at': updatedAt.toIso8601String(), + }; + + factory FileModel.fromMap(Map map) => + FileModel.fromJson(map); + + Map toMap() => toJson(); + + FileModel copyWith({ + String? id, + String? organizationId, + String? userId, + String? fileName, + String? originalName, + String? fileUrl, + int? fileSize, + String? mimeType, + String? fileType, + String? uploadPath, + bool? isPublic, + DateTime? createdAt, + DateTime? updatedAt, + }) { + return FileModel( + id: id ?? this.id, + organizationId: organizationId ?? this.organizationId, + userId: userId ?? this.userId, + fileName: fileName ?? this.fileName, + originalName: originalName ?? this.originalName, + fileUrl: fileUrl ?? this.fileUrl, + fileSize: fileSize ?? this.fileSize, + mimeType: mimeType ?? this.mimeType, + fileType: fileType ?? this.fileType, + uploadPath: uploadPath ?? this.uploadPath, + isPublic: isPublic ?? this.isPublic, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + String toString() { + return 'FileModel(id: $id, organizationId: $organizationId, userId: $userId, fileName: $fileName, originalName: $originalName, fileUrl: $fileUrl, fileSize: $fileSize, mimeType: $mimeType, fileType: $fileType, uploadPath: $uploadPath, isPublic: $isPublic, createdAt: $createdAt, updatedAt: $updatedAt)'; + } +} diff --git a/lib/data/models/response/order_remote_datasource.dart b/lib/data/models/response/order_remote_datasource.dart deleted file mode 100644 index 411da5d..0000000 --- a/lib/data/models/response/order_remote_datasource.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'dart:convert'; - -class OrderResponseModel { - String? status; - List? data; - - OrderResponseModel({ - this.status, - this.data, - }); - - factory OrderResponseModel.fromJson(String str) => - OrderResponseModel.fromMap(json.decode(str)); - - String toJson() => json.encode(toMap()); - - factory OrderResponseModel.fromMap(Map json) => - OrderResponseModel( - status: json["status"], - data: json["data"] == null - ? [] - : List.from( - json["data"]!.map((x) => ItemOrder.fromMap(x))), - ); - - Map toMap() => { - "status": status, - "data": - data == null ? [] : List.from(data!.map((x) => x.toMap())), - }; -} - -class ItemOrder { - int? id; - int? paymentAmount; - int? subTotal; - int? tax; - int? discount; - String? discountAmount; - int? serviceCharge; - int? total; - String? paymentMethod; - int? totalItem; - int? idKasir; - String? namaKasir; - DateTime? transactionTime; - DateTime? createdAt; - DateTime? updatedAt; - - ItemOrder({ - this.id, - this.paymentAmount, - this.subTotal, - this.tax, - this.discount, - this.discountAmount, - this.serviceCharge, - this.total, - this.paymentMethod, - this.totalItem, - this.idKasir, - this.namaKasir, - this.transactionTime, - this.createdAt, - this.updatedAt, - }); - - factory ItemOrder.fromJson(String str) => ItemOrder.fromMap(json.decode(str)); - - String toJson() => json.encode(toMap()); - - factory ItemOrder.fromMap(Map json) => ItemOrder( - id: json["id"], - paymentAmount: json["payment_amount"], - subTotal: json["sub_total"], - tax: json["tax"], - discount: json["discount"], - discountAmount: json["discount_amount"], - serviceCharge: json["service_charge"], - total: json["total"], - paymentMethod: json["payment_method"]!, - totalItem: json["total_item"], - idKasir: json["id_kasir"], - namaKasir: json["nama_kasir"], - transactionTime: json["transaction_time"] == null - ? null - : DateTime.parse(json["transaction_time"]), - createdAt: json["created_at"] == null - ? null - : DateTime.parse(json["created_at"]), - updatedAt: json["updated_at"] == null - ? null - : DateTime.parse(json["updated_at"]), - ); - - Map toMap() => { - "id": id, - "payment_amount": paymentAmount, - "sub_total": subTotal, - "tax": tax, - "discount": discount, - "discount_amount": discountAmount, - "service_charge": serviceCharge, - "total": total, - "payment_method": paymentMethod, - "total_item": totalItem, - "id_kasir": idKasir, - "nama_kasir": namaKasir, - "transaction_time": transactionTime?.toIso8601String(), - "created_at": createdAt?.toIso8601String(), - "updated_at": updatedAt?.toIso8601String(), - }; -} diff --git a/lib/data/models/response/order_response_model.dart b/lib/data/models/response/order_response_model.dart new file mode 100644 index 0000000..178d5e0 --- /dev/null +++ b/lib/data/models/response/order_response_model.dart @@ -0,0 +1,382 @@ +import 'dart:convert'; + +class OrderDetailResponseModel { + final bool? success; + final Order? data; + final dynamic errors; + + OrderDetailResponseModel({ + this.success, + this.data, + this.errors, + }); + + factory OrderDetailResponseModel.fromJson(String str) => + OrderDetailResponseModel.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); + + factory OrderDetailResponseModel.fromMap(Map json) => + OrderDetailResponseModel( + success: json["success"], + data: json["data"] == null ? null : Order.fromMap(json["data"]), + errors: json["errors"], + ); + + Map toMap() => { + "success": success, + "data": data?.toMap(), + "errors": errors, + }; +} + +class OrderResponseModel { + final bool? success; + final OrderData? data; + + OrderResponseModel({ + this.success, + this.data, + }); + + factory OrderResponseModel.fromJson(String str) => + OrderResponseModel.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); + + factory OrderResponseModel.fromMap(Map map) { + return OrderResponseModel( + success: map['success'] ?? false, + data: OrderData.fromMap(map['data']), + ); + } + + Map toMap() { + return { + 'success': success, + 'data': data?.toMap(), + }; + } +} + +class OrderData { + final List? orders; + final List? payments; + final int? totalCount; + final int? page; + final int? limit; + final int? totalPages; + + OrderData({ + required this.orders, + required this.payments, + required this.totalCount, + required this.page, + required this.limit, + required this.totalPages, + }); + + factory OrderData.fromMap(Map map) { + return OrderData( + orders: map["orders"] == null + ? [] + : List.from(map['orders']?.map((x) => Order.fromMap(x))), + payments: map["payments"] == null + ? [] + : List.from(map['payments']?.map((x) => Payment.fromMap(x))), + totalCount: map['total_count'], + page: map['page'], + limit: map['limit'], + totalPages: map['total_pages'], + ); + } + + Map toMap() { + return { + 'orders': orders?.map((x) => x.toMap()).toList(), + 'payments': payments?.map((x) => x.toMap()).toList(), + 'total_count': totalCount, + 'page': page, + 'limit': limit, + 'total_pages': totalPages, + }; + } +} + +class Order { + final String? id; + final String? orderNumber; + final String? outletId; + final String? userId; + final String? tableNumber; + final String? orderType; + final String? status; + final int? subtotal; + final int? taxAmount; + final int? discountAmount; + final int? totalAmount; + final int? totalCost; + final int? remainingAmount; + final String? paymentStatus; + final int? refundAmount; + final bool? isVoid; + final bool? isRefund; + final String? notes; + final Map? metadata; + final DateTime? createdAt; + final DateTime? updatedAt; + final List? orderItems; + final List? payments; + final int? totalPaid; + final int? paymentCount; + final String? splitType; + + Order({ + this.id, + this.orderNumber, + this.outletId, + this.userId, + this.tableNumber, + this.orderType, + this.status, + this.subtotal, + this.taxAmount, + this.discountAmount, + this.totalAmount, + this.totalCost, + this.remainingAmount, + this.paymentStatus, + this.refundAmount, + this.isVoid, + this.isRefund, + this.notes, + this.metadata, + this.createdAt, + this.updatedAt, + this.orderItems, + this.payments, + this.totalPaid, + this.paymentCount, + this.splitType, + }); + + factory Order.fromMap(Map map) { + return Order( + id: map['id'], + orderNumber: map['order_number'], + outletId: map['outlet_id'], + userId: map['user_id'], + tableNumber: map['table_number'], + orderType: map['order_type'], + status: map['status'], + subtotal: map['subtotal'], + taxAmount: map['tax_amount'], + discountAmount: map['discount_amount'], + totalAmount: map['total_amount'], + totalCost: map['total_cost'], + remainingAmount: map['remaining_amount'], + paymentStatus: map['payment_status'], + refundAmount: map['refund_amount'], + isVoid: map['is_void'], + isRefund: map['is_refund'], + notes: map['notes'], + metadata: map['metadata'] ?? {}, + createdAt: DateTime.parse(map['created_at']), + updatedAt: DateTime.parse(map['updated_at']), + orderItems: map["order_items"] == null + ? [] + : List.from( + map['order_items'].map((x) => OrderItem.fromMap(x))), + payments: map["payments"] == null + ? [] + : List.from(map['payments'].map((x) => Payment.fromMap(x))), + totalPaid: map['total_paid'], + paymentCount: map['payment_count'], + splitType: map['split_type'] ?? "", + ); + } + + Map toMap() { + return { + 'id': id, + 'order_number': orderNumber, + 'outlet_id': outletId, + 'user_id': userId, + 'table_number': tableNumber, + 'order_type': orderType, + 'status': status, + 'subtotal': subtotal, + 'tax_amount': taxAmount, + 'discount_amount': discountAmount, + 'total_amount': totalAmount, + 'total_cost': totalCost, + 'remaining_amount': remainingAmount, + 'payment_status': paymentStatus, + 'refund_amount': refundAmount, + 'is_void': isVoid, + 'is_refund': isRefund, + 'notes': notes, + 'metadata': metadata, + 'created_at': createdAt?.toIso8601String(), + 'updated_at': updatedAt?.toIso8601String(), + 'order_items': orderItems?.map((x) => x.toMap()).toList(), + 'payments': payments?.map((x) => x.toMap()).toList(), + 'total_paid': totalPaid, + 'payment_count': paymentCount, + 'split_type': splitType, + }; + } +} + +class OrderItem { + String? id; + String? orderId; + String? productId; + String? productName; + String? productVariantId; + String? productVariantName; + int? quantity; + int? unitPrice; + int? totalPrice; + List? modifiers; + String? notes; + String? status; + DateTime? createdAt; + DateTime? updatedAt; + String? printerType; + int? paidQuantity; + + OrderItem({ + this.id, + this.orderId, + this.productId, + this.productName, + this.productVariantId, + this.productVariantName, + this.quantity, + this.unitPrice, + this.totalPrice, + this.modifiers, + this.notes, + this.status, + this.createdAt, + this.updatedAt, + this.printerType, + this.paidQuantity, + }); + + factory OrderItem.fromMap(Map map) { + return OrderItem( + id: map['id'], + orderId: map['order_id'], + productId: map['product_id'], + productName: map['product_name'], + productVariantId: map['product_variant_id'], + productVariantName: map['product_variant_name'], + quantity: map['quantity'], + unitPrice: map['unit_price'], + totalPrice: map['total_price'], + modifiers: + map['modifiers'] == null ? [] : List.from(map['modifiers']), + notes: map['notes'], + status: map['status'], + createdAt: DateTime.parse(map['created_at']), + updatedAt: DateTime.parse(map['updated_at']), + printerType: map['printer_type'], + paidQuantity: map['paid_quantity'], + ); + } + + Map toMap() { + return { + 'id': id, + 'order_id': orderId, + 'product_id': productId, + 'product_name': productName, + 'product_variant_id': productVariantId, + 'product_variant_name': productVariantName, + 'quantity': quantity, + 'unit_price': unitPrice, + 'total_price': totalPrice, + 'modifiers': modifiers, + 'notes': notes, + 'status': status, + 'created_at': createdAt?.toIso8601String(), + 'updated_at': updatedAt?.toIso8601String(), + 'printer_type': printerType, + 'paid_quantity': paidQuantity, + }; + } +} + +class Payment { + final String? id; + final String? orderId; + final String? paymentMethodId; + final String? paymentMethodName; + final String? paymentMethodType; + final int? amount; + final String? status; + final int? splitNumber; + final int? splitTotal; + final String? splitDescription; + final int? refundAmount; + final Map? metadata; + final DateTime? createdAt; + final DateTime? updatedAt; + + Payment({ + this.id, + this.orderId, + this.paymentMethodId, + this.paymentMethodName, + this.paymentMethodType, + this.amount, + this.status, + this.splitNumber, + this.splitTotal, + this.splitDescription, + this.refundAmount, + this.metadata, + this.createdAt, + this.updatedAt, + }); + + factory Payment.fromMap(Map map) { + return Payment( + id: map['id'], + orderId: map['order_id'], + paymentMethodId: map['payment_method_id'], + paymentMethodName: map['payment_method_name'], + paymentMethodType: map['payment_method_type'], + amount: map['amount'], + status: map['status'], + splitNumber: map['split_number'], + splitTotal: map['split_total'], + splitDescription: map['split_description'], + refundAmount: map['refund_amount'], + metadata: map['metadata'] ?? {}, + createdAt: DateTime.parse(map['created_at']), + updatedAt: DateTime.parse(map['updated_at']), + ); + } + + Map toMap() { + return { + 'id': id, + 'order_id': orderId, + 'payment_method_id': paymentMethodId, + 'payment_method_name': paymentMethodName, + 'payment_method_type': paymentMethodType, + 'amount': amount, + 'status': status, + 'split_number': splitNumber, + 'split_total': splitTotal, + 'split_description': splitDescription, + 'refund_amount': refundAmount, + 'metadata': metadata, + 'created_at': createdAt?.toIso8601String(), + 'updated_at': updatedAt?.toIso8601String(), + }; + } +} diff --git a/lib/data/models/response/payment_method_analytic_response_model.dart b/lib/data/models/response/payment_method_analytic_response_model.dart new file mode 100644 index 0000000..6796cae --- /dev/null +++ b/lib/data/models/response/payment_method_analytic_response_model.dart @@ -0,0 +1,173 @@ +class PaymentMethodAnalyticResponseModel { + final bool success; + final PaymentMethodAnalyticData data; + final dynamic errors; + + PaymentMethodAnalyticResponseModel({ + required this.success, + required this.data, + this.errors, + }); + + factory PaymentMethodAnalyticResponseModel.fromJson( + Map json) => + PaymentMethodAnalyticResponseModel.fromMap(json); + + Map toJson() => toMap(); + + factory PaymentMethodAnalyticResponseModel.fromMap(Map map) { + return PaymentMethodAnalyticResponseModel( + success: map['success'], + data: PaymentMethodAnalyticData.fromMap(map['data']), + errors: map['errors'], + ); + } + + Map toMap() { + return { + 'success': success, + 'data': data.toMap(), + 'errors': errors, + }; + } +} + +class PaymentMethodAnalyticData { + final String organizationId; + final String outletId; + final DateTime dateFrom; + final DateTime dateTo; + final String groupBy; + final PaymentSummary summary; + final List data; + + PaymentMethodAnalyticData({ + required this.organizationId, + required this.outletId, + required this.dateFrom, + required this.dateTo, + required this.groupBy, + required this.summary, + required this.data, + }); + + factory PaymentMethodAnalyticData.fromJson(Map json) => + PaymentMethodAnalyticData.fromMap(json); + + Map toJson() => toMap(); + + factory PaymentMethodAnalyticData.fromMap(Map map) { + return PaymentMethodAnalyticData( + organizationId: map['organization_id'], + outletId: map['outlet_id'], + dateFrom: DateTime.parse(map['date_from']), + dateTo: DateTime.parse(map['date_to']), + groupBy: map['group_by'], + summary: PaymentSummary.fromMap(map['summary']), + data: map['data'] == null + ? [] + : List.from( + map['data']?.map((x) => PaymentMethodAnalyticItem.fromMap(x)) ?? + [], + ), + ); + } + + Map toMap() { + return { + 'organization_id': organizationId, + 'outlet_id': outletId, + 'date_from': dateFrom.toIso8601String(), + 'date_to': dateTo.toIso8601String(), + 'group_by': groupBy, + 'summary': summary.toMap(), + 'data': data.map((x) => x.toMap()).toList(), + }; + } +} + +class PaymentSummary { + final int totalAmount; + final int totalOrders; + final int totalPayments; + final double averageOrderValue; + + PaymentSummary({ + required this.totalAmount, + required this.totalOrders, + required this.totalPayments, + required this.averageOrderValue, + }); + + factory PaymentSummary.fromJson(Map json) => + PaymentSummary.fromMap(json); + + Map toJson() => toMap(); + + factory PaymentSummary.fromMap(Map map) { + return PaymentSummary( + totalAmount: map['total_amount'], + totalOrders: map['total_orders'], + totalPayments: map['total_payments'], + averageOrderValue: (map['average_order_value'] as num).toDouble(), + ); + } + + Map toMap() { + return { + 'total_amount': totalAmount, + 'total_orders': totalOrders, + 'total_payments': totalPayments, + 'average_order_value': averageOrderValue, + }; + } +} + +class PaymentMethodAnalyticItem { + final String paymentMethodId; + final String paymentMethodName; + final String paymentMethodType; + final int totalAmount; + final int orderCount; + final int paymentCount; + final int percentage; + + PaymentMethodAnalyticItem({ + required this.paymentMethodId, + required this.paymentMethodName, + required this.paymentMethodType, + required this.totalAmount, + required this.orderCount, + required this.paymentCount, + required this.percentage, + }); + + factory PaymentMethodAnalyticItem.fromJson(Map json) => + PaymentMethodAnalyticItem.fromMap(json); + + Map toJson() => toMap(); + + factory PaymentMethodAnalyticItem.fromMap(Map map) { + return PaymentMethodAnalyticItem( + paymentMethodId: map['payment_method_id'], + paymentMethodName: map['payment_method_name'], + paymentMethodType: map['payment_method_type'], + totalAmount: map['total_amount'], + orderCount: map['order_count'], + paymentCount: map['payment_count'], + percentage: map['percentage'], + ); + } + + Map toMap() { + return { + 'payment_method_id': paymentMethodId, + 'payment_method_name': paymentMethodName, + 'payment_method_type': paymentMethodType, + 'total_amount': totalAmount, + 'order_count': orderCount, + 'payment_count': paymentCount, + 'percentage': percentage, + }; + } +} diff --git a/lib/data/models/response/payment_methods_response_model.dart b/lib/data/models/response/payment_methods_response_model.dart index ff08cb9..9207865 100644 --- a/lib/data/models/response/payment_methods_response_model.dart +++ b/lib/data/models/response/payment_methods_response_model.dart @@ -1,12 +1,14 @@ import 'dart:convert'; class PaymentMethodsResponseModel { - final String? status; - final List? data; + final bool? success; + final PaymentMethodsData? data; + final dynamic errors; PaymentMethodsResponseModel({ - this.status, + this.success, this.data, + this.errors, }); factory PaymentMethodsResponseModel.fromJson(String str) => @@ -16,51 +18,83 @@ class PaymentMethodsResponseModel { factory PaymentMethodsResponseModel.fromMap(Map json) => PaymentMethodsResponseModel( - status: json["status"], + success: json["success"], data: json["data"] == null - ? [] - : List.from( - json["data"]!.map((x) => PaymentMethod.fromMap(x))), + ? null + : PaymentMethodsData.fromMap(json["data"]), + errors: json["errors"], ); Map toMap() => { - "status": status, - "data": data == null + "success": success, + "data": data?.toMap(), + "errors": errors, + }; +} + +class PaymentMethodsData { + final List? paymentMethods; + final int? totalCount; + final int? page; + final int? limit; + final int? totalPages; + + PaymentMethodsData({ + this.paymentMethods, + this.totalCount, + this.page, + this.limit, + this.totalPages, + }); + + factory PaymentMethodsData.fromMap(Map json) => + PaymentMethodsData( + paymentMethods: json["payment_methods"] == null ? [] - : List.from(data!.map((x) => x.toMap())), + : List.from( + json["payment_methods"].map((x) => PaymentMethod.fromMap(x))), + totalCount: json["total_count"], + page: json["page"], + limit: json["limit"], + totalPages: json["total_pages"], + ); + + Map toMap() => { + "payment_methods": paymentMethods == null + ? [] + : List.from(paymentMethods!.map((x) => x.toMap())), + "total_count": totalCount, + "page": page, + "limit": limit, + "total_pages": totalPages, }; } class PaymentMethod { - final int? id; + final String? id; + final String? organizationId; final String? name; - final String? description; + final String? type; final bool? isActive; - final int? sortOrder; final DateTime? createdAt; final DateTime? updatedAt; PaymentMethod({ this.id, + this.organizationId, this.name, - this.description, + this.type, this.isActive, - this.sortOrder, this.createdAt, this.updatedAt, }); - factory PaymentMethod.fromJson(String str) => - PaymentMethod.fromMap(json.decode(str)); - - String toJson() => json.encode(toMap()); - factory PaymentMethod.fromMap(Map json) => PaymentMethod( id: json["id"], + organizationId: json["organization_id"], name: json["name"], - description: json["description"], + type: json["type"], isActive: json["is_active"], - sortOrder: json["sort_order"], createdAt: json["created_at"] == null ? null : DateTime.parse(json["created_at"]), @@ -71,11 +105,11 @@ class PaymentMethod { Map toMap() => { "id": id, + "organization_id": organizationId, "name": name, - "description": description, + "type": type, "is_active": isActive, - "sort_order": sortOrder, "created_at": createdAt?.toIso8601String(), "updated_at": updatedAt?.toIso8601String(), }; -} \ No newline at end of file +} diff --git a/lib/data/models/response/payment_response_model.dart b/lib/data/models/response/payment_response_model.dart new file mode 100644 index 0000000..bfaf3ed --- /dev/null +++ b/lib/data/models/response/payment_response_model.dart @@ -0,0 +1,95 @@ +import 'dart:convert'; + +class PaymentSuccessResponseModel { + final bool? success; + final PaymentData? data; + final dynamic errors; + + PaymentSuccessResponseModel({ + this.success, + this.data, + this.errors, + }); + + factory PaymentSuccessResponseModel.fromJson(String str) => + PaymentSuccessResponseModel.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); + + factory PaymentSuccessResponseModel.fromMap(Map json) => + PaymentSuccessResponseModel( + success: json["success"], + data: json["data"] == null ? null : PaymentData.fromMap(json["data"]), + errors: json["errors"], + ); + + Map toMap() => { + "success": success, + "data": data?.toMap(), + "errors": errors, + }; +} + +class PaymentData { + final String? id; + final String? orderId; + final String? paymentMethodId; + final int? amount; + final String? status; + final String? transactionId; + final int? splitNumber; + final int? splitTotal; + final String? splitDescription; + final int? refundAmount; + final DateTime? createdAt; + final DateTime? updatedAt; + + PaymentData({ + this.id, + this.orderId, + this.paymentMethodId, + this.amount, + this.status, + this.transactionId, + this.splitNumber, + this.splitTotal, + this.splitDescription, + this.refundAmount, + this.createdAt, + this.updatedAt, + }); + + factory PaymentData.fromMap(Map json) => PaymentData( + id: json["id"], + orderId: json["order_id"], + paymentMethodId: json["payment_method_id"], + amount: json["amount"], + status: json["status"], + transactionId: json["transaction_id"], + splitNumber: json["split_number"], + splitTotal: json["split_total"], + splitDescription: json["split_description"], + refundAmount: json["refund_amount"], + createdAt: json["created_at"] == null + ? null + : DateTime.parse(json["created_at"]), + updatedAt: json["updated_at"] == null + ? null + : DateTime.parse(json["updated_at"]), + ); + + Map toMap() => { + "id": id, + "order_id": orderId, + "payment_method_id": paymentMethodId, + "amount": amount, + "status": status, + "transaction_id": transactionId, + "split_number": splitNumber, + "split_total": splitTotal, + "split_description": splitDescription, + "refund_amount": refundAmount, + "created_at": createdAt?.toIso8601String(), + "updated_at": updatedAt?.toIso8601String(), + }; +} diff --git a/lib/data/models/response/product_analytic_response_model.dart b/lib/data/models/response/product_analytic_response_model.dart new file mode 100644 index 0000000..35a9f4f --- /dev/null +++ b/lib/data/models/response/product_analytic_response_model.dart @@ -0,0 +1,145 @@ +class ProductAnalyticResponseModel { + final bool success; + final ProductAnalyticData data; + final dynamic errors; + + ProductAnalyticResponseModel({ + required this.success, + required this.data, + this.errors, + }); + + factory ProductAnalyticResponseModel.fromJson(Map json) => + ProductAnalyticResponseModel.fromMap(json); + + Map toJson() => toMap(); + + factory ProductAnalyticResponseModel.fromMap(Map map) { + return ProductAnalyticResponseModel( + success: map['success'] ?? false, + data: ProductAnalyticData.fromMap(map['data']), + errors: map['errors'], + ); + } + + Map toMap() { + return { + 'success': success, + 'data': data.toMap(), + 'errors': errors, + }; + } +} + +class ProductAnalyticData { + final String organizationId; + final String outletId; + final DateTime dateFrom; + final DateTime dateTo; + final List data; + + ProductAnalyticData({ + required this.organizationId, + required this.outletId, + required this.dateFrom, + required this.dateTo, + required this.data, + }); + + factory ProductAnalyticData.fromMap(Map map) => + ProductAnalyticData( + 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.from( + map['data'].map((x) => ProductAnalyticItem.fromMap(x)), + ), + ); + + Map toMap() => { + 'organization_id': organizationId, + 'outlet_id': outletId, + 'date_from': dateFrom.toIso8601String(), + 'date_to': dateTo.toIso8601String(), + 'data': data.map((x) => x.toMap()).toList(), + }; +} + +class ProductAnalyticItem { + final String productId; + final String productName; + final String categoryId; + final String categoryName; + final int quantitySold; + final int revenue; + final double averagePrice; + final int orderCount; + + ProductAnalyticItem({ + required this.productId, + required this.productName, + required this.categoryId, + required this.categoryName, + required this.quantitySold, + required this.revenue, + required this.averagePrice, + required this.orderCount, + }); + + factory ProductAnalyticItem.fromMap(Map map) => + ProductAnalyticItem( + productId: map['product_id'], + productName: map['product_name'], + categoryId: map['category_id'], + categoryName: map['category_name'], + quantitySold: map['quantity_sold'], + revenue: map['revenue'], + averagePrice: (map['average_price'] as num).toDouble(), + orderCount: map['order_count'], + ); + + Map toMap() => { + 'product_id': productId, + 'product_name': productName, + 'category_id': categoryId, + 'category_name': categoryName, + 'quantity_sold': quantitySold, + 'revenue': revenue, + 'average_price': averagePrice, + 'order_count': orderCount, + }; +} + +class ProductInsights { + final List topProducts; + final ProductAnalyticItem? bestProduct; + final List categorySummary; + final int totalProducts; + final int totalRevenue; + final int totalQuantitySold; + + ProductInsights({ + required this.topProducts, + required this.bestProduct, + required this.categorySummary, + required this.totalProducts, + required this.totalRevenue, + required this.totalQuantitySold, + }); +} + +// Category summary class +class CategorySummary { + final String categoryName; + int productCount; + int totalRevenue; + + CategorySummary({ + required this.categoryName, + required this.productCount, + required this.totalRevenue, + }); +} diff --git a/lib/data/models/response/product_response_model.dart b/lib/data/models/response/product_response_model.dart index 07330d6..7063c7a 100644 --- a/lib/data/models/response/product_response_model.dart +++ b/lib/data/models/response/product_response_model.dart @@ -1,17 +1,15 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:convert'; -import 'package:flutter/foundation.dart'; - -import 'package:enaklo_pos/presentation/home/pages/confirm_payment_page.dart'; - class ProductResponseModel { - final String? status; - final List? data; + final bool? success; + final ProductData? data; + final dynamic errors; ProductResponseModel({ - this.status, + this.success, this.data, + this.errors, }); factory ProductResponseModel.fromJson(String str) => @@ -21,50 +19,90 @@ class ProductResponseModel { factory ProductResponseModel.fromMap(Map json) => ProductResponseModel( - status: json["status"], - data: json["data"] == null - ? [] - : List.from(json["data"]!.map((x) => Product.fromMap(x))), + success: json["success"], + data: json["data"] == null ? null : ProductData.fromMap(json["data"]), + errors: json["errors"], ); Map toMap() => { - "status": status, - "data": - data == null ? [] : List.from(data!.map((x) => x.toMap())), + "success": success, + "data": data?.toMap(), + "errors": errors, + }; +} + +class ProductData { + final List? products; + final int? totalCount; + final int? page; + final int? limit; + final int? totalPages; + + ProductData({ + this.products, + this.totalCount, + this.page, + this.limit, + this.totalPages, + }); + + factory ProductData.fromMap(Map json) => ProductData( + products: json["products"] == null + ? [] + : List.from( + json["products"].map((x) => Product.fromMap(x))), + totalCount: json["total_count"], + page: json["page"], + limit: json["limit"], + totalPages: json["total_pages"], + ); + + Map toMap() => { + "products": products == null + ? [] + : List.from(products!.map((x) => x.toMap())), + "total_count": totalCount, + "page": page, + "limit": limit, + "total_pages": totalPages, }; } class Product { - final int? id; - final int? productId; - final int? categoryId; + final String? id; + final String? organizationId; + final String? categoryId; + final String? sku; final String? name; final String? description; - final String? image; - final String? price; - final int? stock; - final int? status; - final int? isFavorite; + final int? price; + final int? cost; + final String? businessType; + final String? imageUrl; + final String? printerType; + final Map? metadata; + final bool? isActive; final DateTime? createdAt; final DateTime? updatedAt; - final Category? category; - final String? printerType; + final List? variants; Product({ this.id, - this.productId, + this.organizationId, this.categoryId, + this.sku, this.name, this.description, - this.image, this.price, - this.stock, - this.status, - this.isFavorite, + this.cost, + this.businessType, + this.imageUrl, + this.printerType, + this.metadata, + this.isActive, this.createdAt, this.updatedAt, - this.category, - this.printerType, + this.variants, }); factory Product.fromJson(String str) => Product.fromMap(json.decode(str)); @@ -72,91 +110,102 @@ class Product { String toJson() => json.encode(toMap()); factory Product.fromMap(Map json) => Product( - id: json["id"] is String ? int.tryParse(json["id"]) : json["id"], - productId: json["product_id"] is String ? int.tryParse(json["product_id"]) : json["product_id"], - categoryId: json["category_id"] is String - ? int.tryParse(json["category_id"]) - : json["category_id"], + id: json["id"], + organizationId: json["organization_id"], + categoryId: json["category_id"], + sku: json["sku"], name: json["name"], description: json["description"], - image: json["image"], - // price: json["price"].substring(0, json["price"].length - 3), - price: json["price"].toString().replaceAll('.00', ''), - stock: json["stock"] is String ? int.tryParse(json["stock"]) : json["stock"], - status: json["status"] is String ? int.tryParse(json["status"]) : json["status"], - isFavorite: json["is_favorite"] is String ? int.tryParse(json["is_favorite"]) : json["is_favorite"], + price: json["price"], + cost: json["cost"], + businessType: json["business_type"], + imageUrl: json["image_url"], + printerType: json["printer_type"], + metadata: json["metadata"] ?? {}, + isActive: json["is_active"], createdAt: json["created_at"] == null ? null : DateTime.parse(json["created_at"]), updatedAt: json["updated_at"] == null ? null : DateTime.parse(json["updated_at"]), - category: json["category"] == null - ? null - : Category.fromMap(json["category"]), - printerType: json["printer_type"] ?? 'bar', + variants: json["variants"] == null + ? [] + : List.from( + json["variants"].map((x) => ProductVariant.fromMap(x))), ); factory Product.fromOrderMap(Map json) => Product( id: json["id_product"], - price: json["price"].toString(), + price: json["price"], ); factory Product.fromLocalMap(Map json) => Product( id: json["id"], - productId: json["product_id"], - categoryId: json["categoryId"], - category: Category( - id: json["categoryId"], - name: json["categoryName"], - ), + organizationId: json["organization_id"], + categoryId: json["category_id"], + sku: json["sku"], name: json["name"], description: json["description"], - image: json["image"], price: json["price"], - stock: json["stock"], - status: json["status"], - isFavorite: json["isFavorite"], - createdAt: json["createdAt"] == null + cost: json["cost"], + businessType: json["business_type"], + imageUrl: json["image_url"], + printerType: json["printer_type"], + metadata: json["metadata"] ?? {}, + isActive: json["is_active"], + createdAt: json["created_at"] == null ? null - : DateTime.parse(json["createdAt"]), - updatedAt: json["updatedAt"] == null + : DateTime.parse(json["created_at"]), + updatedAt: json["updated_at"] == null ? null - : DateTime.parse(json["updatedAt"]), - printerType: json["printer_type"] ?? 'bar', + : DateTime.parse(json["updated_at"]), + variants: json["variants"] == null + ? [] + : List.from( + json["variants"].map((x) => ProductVariant.fromMap(x))), ); Map toLocalMap() => { - "product_id": id, - "categoryId": categoryId, - "categoryName": category?.name, + "id": id, + "organization_id": organizationId, + "category_id": categoryId, + "sku": sku, "name": name, "description": description, - "image": image, - "price": price?.replaceAll(RegExp(r'\.0+$'), ''), - "stock": stock, - "status": status, - "isFavorite": isFavorite, - "createdAt": createdAt?.toIso8601String(), - "updatedAt": updatedAt?.toIso8601String(), + "price": price, + "cost": cost, + "business_type": businessType, + "image_url": imageUrl, "printer_type": printerType, + "metadata": metadata, + "is_active": isActive, + "created_at": createdAt?.toIso8601String(), + "updated_at": updatedAt?.toIso8601String(), + "variants": variants == null + ? [] + : List.from(variants!.map((x) => x.toMap())), }; Map toMap() => { "id": id, - "product_id": productId, + "organization_id": organizationId, "category_id": categoryId, + "sku": sku, "name": name, "description": description, - "image": image, "price": price, - "stock": stock, - "status": status, - "is_favorite": isFavorite, + "cost": cost, + "business_type": businessType, + "image_url": imageUrl, + "printer_type": printerType, + "metadata": metadata, + "is_active": isActive, "created_at": createdAt?.toIso8601String(), "updated_at": updatedAt?.toIso8601String(), - "category": category?.toMap(), - "printer_type": printerType, + "variants": variants == null + ? [] + : List.from(variants!.map((x) => x.toMap())), }; @override @@ -164,70 +213,88 @@ class Product { if (identical(this, other)) return true; return other.id == id && - other.productId == productId && + other.organizationId == organizationId && other.categoryId == categoryId && + other.sku == sku && other.name == name && other.description == description && - other.image == image && other.price == price && - other.stock == stock && - other.status == status && - other.isFavorite == isFavorite && + other.cost == cost && + other.businessType == businessType && + other.imageUrl == imageUrl && + other.printerType == printerType && + other.metadata == metadata && + other.isActive == isActive && other.createdAt == createdAt && other.updatedAt == updatedAt && - other.category == category && - other.printerType == printerType; + _listEquals(other.variants, variants); + } + + bool _listEquals(List? a, List? 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 ^ - productId.hashCode ^ + organizationId.hashCode ^ categoryId.hashCode ^ + sku.hashCode ^ name.hashCode ^ description.hashCode ^ - image.hashCode ^ price.hashCode ^ - stock.hashCode ^ - status.hashCode ^ - isFavorite.hashCode ^ + cost.hashCode ^ + businessType.hashCode ^ + imageUrl.hashCode ^ + printerType.hashCode ^ + metadata.hashCode ^ + isActive.hashCode ^ createdAt.hashCode ^ updatedAt.hashCode ^ - category.hashCode ^ - printerType.hashCode; + variants.hashCode; } Product copyWith({ - int? id, - int? productId, - int? categoryId, + String? id, + String? organizationId, + String? categoryId, + String? sku, String? name, String? description, - String? image, - String? price, - int? stock, - int? status, - int? isFavorite, + int? price, + int? cost, + String? businessType, + String? imageUrl, + String? printerType, + Map? metadata, + bool? isActive, DateTime? createdAt, DateTime? updatedAt, - Category? category, - String? printerType, + List? variants, }) { return Product( id: id ?? this.id, - productId: productId ?? this.productId, + organizationId: organizationId ?? this.organizationId, categoryId: categoryId ?? this.categoryId, + sku: sku ?? this.sku, name: name ?? this.name, description: description ?? this.description, - image: image ?? this.image, price: price ?? this.price, - stock: stock ?? this.stock, - status: status ?? this.status, - isFavorite: isFavorite ?? this.isFavorite, + 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, - category: category ?? this.category, - printerType: printerType ?? this.printerType, + variants: variants ?? this.variants, ); } } @@ -297,3 +364,51 @@ class Category { updatedAt.hashCode; } } + +class ProductVariant { + final String? id; + final String? productId; + final String? name; + final int? priceModifier; + final int? cost; + final Map? metadata; + final DateTime? createdAt; + final DateTime? updatedAt; + + ProductVariant({ + this.id, + this.productId, + this.name, + this.priceModifier, + this.cost, + this.metadata, + this.createdAt, + this.updatedAt, + }); + + factory ProductVariant.fromMap(Map json) => ProductVariant( + id: json["id"], + productId: json["product_id"], + name: json["name"], + priceModifier: json["price_modifier"], + cost: json["cost"], + metadata: json["metadata"] ?? {}, + createdAt: json["created_at"] == null + ? null + : DateTime.parse(json["created_at"]), + updatedAt: json["updated_at"] == null + ? null + : DateTime.parse(json["updated_at"]), + ); + + Map toMap() => { + "id": id, + "product_id": productId, + "name": name, + "price_modifier": priceModifier, + "cost": cost, + "metadata": metadata, + "created_at": createdAt?.toIso8601String(), + "updated_at": updatedAt?.toIso8601String(), + }; +} diff --git a/lib/data/models/response/profit_loss_response_model.dart b/lib/data/models/response/profit_loss_response_model.dart new file mode 100644 index 0000000..a69c7a1 --- /dev/null +++ b/lib/data/models/response/profit_loss_response_model.dart @@ -0,0 +1,277 @@ +class ProfitLossResponseModel { + final bool success; + final ProfitLossData data; + final dynamic errors; + + ProfitLossResponseModel({ + required this.success, + required this.data, + this.errors, + }); + + /// Dari JSON ke model (misal response dari API) + factory ProfitLossResponseModel.fromJson(Map json) { + return ProfitLossResponseModel( + success: json['success'], + data: ProfitLossData.fromMap(json['data']), + errors: json['errors'], + ); + } + + /// Dari model ke JSON (misal saat kirim ke API) + Map toJson() { + return { + 'success': success, + 'data': data.toMap(), + 'errors': errors, + }; + } + + /// Alias dari fromJson, kadang dibutuhkan untuk naming yang konsisten + factory ProfitLossResponseModel.fromMap(Map map) { + return ProfitLossResponseModel( + success: map['success'], + data: ProfitLossData.fromMap(map['data']), + errors: map['errors'], + ); + } + + /// Alias dari toJson + Map toMap() { + return { + 'success': success, + 'data': data.toMap(), + 'errors': errors, + }; + } +} + +class ProfitLossData { + final String organizationId; + final String dateFrom; + final String dateTo; + final String groupBy; + final ProfitLossSummary summary; + final List data; + final List productData; + + ProfitLossData({ + required this.organizationId, + required this.dateFrom, + required this.dateTo, + required this.groupBy, + required this.summary, + required this.data, + required this.productData, + }); + + factory ProfitLossData.fromMap(Map map) { + return ProfitLossData( + organizationId: map['organization_id'], + dateFrom: map['date_from'], + dateTo: map['date_to'], + groupBy: map['group_by'], + summary: ProfitLossSummary.fromMap(map['summary']), + data: map['data'] == null + ? [] + : List.from( + map['data'].map((x) => ProfitLossItem.fromMap(x))), + productData: map['product_data'] == null + ? [] + : List.from( + map['product_data'].map((x) => ProfitLossProduct.fromMap(x))), + ); + } + + Map toMap() { + return { + 'organization_id': organizationId, + 'date_from': dateFrom, + 'date_to': dateTo, + 'group_by': groupBy, + 'summary': summary.toMap(), + 'data': data.map((x) => x.toMap()).toList(), + 'product_data': productData.map((x) => x.toMap()).toList(), + }; + } +} + +class ProfitLossSummary { + final int totalRevenue; + final int totalCost; + final int grossProfit; + final double grossProfitMargin; + final int totalTax; + final int totalDiscount; + final int netProfit; + final double netProfitMargin; + final int totalOrders; + final int averageProfit; + final double profitabilityRatio; + + ProfitLossSummary({ + required this.totalRevenue, + required this.totalCost, + required this.grossProfit, + required this.grossProfitMargin, + required this.totalTax, + required this.totalDiscount, + required this.netProfit, + required this.netProfitMargin, + required this.totalOrders, + required this.averageProfit, + required this.profitabilityRatio, + }); + + factory ProfitLossSummary.fromMap(Map map) { + return ProfitLossSummary( + totalRevenue: map['total_revenue'], + totalCost: map['total_cost'], + grossProfit: map['gross_profit'], + grossProfitMargin: (map['gross_profit_margin'] as num).toDouble(), + totalTax: map['total_tax'], + totalDiscount: map['total_discount'], + netProfit: map['net_profit'], + netProfitMargin: (map['net_profit_margin'] as num).toDouble(), + totalOrders: map['total_orders'], + averageProfit: map['average_profit'], + profitabilityRatio: (map['profitability_ratio'] as num).toDouble(), + ); + } + + Map toMap() { + return { + 'total_revenue': totalRevenue, + 'total_cost': totalCost, + 'gross_profit': grossProfit, + 'gross_profit_margin': grossProfitMargin, + 'total_tax': totalTax, + 'total_discount': totalDiscount, + 'net_profit': netProfit, + 'net_profit_margin': netProfitMargin, + 'total_orders': totalOrders, + 'average_profit': averageProfit, + 'profitability_ratio': profitabilityRatio, + }; + } +} + +class ProfitLossItem { + final String date; + final int revenue; + final int cost; + final int grossProfit; + final double grossProfitMargin; + final int tax; + final int discount; + final int netProfit; + final double netProfitMargin; + final int orders; + + ProfitLossItem({ + required this.date, + required this.revenue, + required this.cost, + required this.grossProfit, + required this.grossProfitMargin, + required this.tax, + required this.discount, + required this.netProfit, + required this.netProfitMargin, + required this.orders, + }); + + factory ProfitLossItem.fromMap(Map map) { + return ProfitLossItem( + date: map['date'], + revenue: map['revenue'], + cost: map['cost'], + grossProfit: map['gross_profit'], + grossProfitMargin: (map['gross_profit_margin'] as num).toDouble(), + tax: map['tax'], + discount: map['discount'], + netProfit: map['net_profit'], + netProfitMargin: (map['net_profit_margin'] as num).toDouble(), + orders: map['orders'], + ); + } + + Map toMap() { + return { + 'date': date, + 'revenue': revenue, + 'cost': cost, + 'gross_profit': grossProfit, + 'gross_profit_margin': grossProfitMargin, + 'tax': tax, + 'discount': discount, + 'net_profit': netProfit, + 'net_profit_margin': netProfitMargin, + 'orders': orders, + }; + } +} + +class ProfitLossProduct { + final String productId; + final String productName; + final String categoryId; + final String categoryName; + final int quantitySold; + final int revenue; + final int cost; + final int grossProfit; + final double grossProfitMargin; + final int averagePrice; + final int averageCost; + final int profitPerUnit; + + ProfitLossProduct({ + required this.productId, + required this.productName, + required this.categoryId, + required this.categoryName, + required this.quantitySold, + required this.revenue, + required this.cost, + required this.grossProfit, + required this.grossProfitMargin, + required this.averagePrice, + required this.averageCost, + required this.profitPerUnit, + }); + + factory ProfitLossProduct.fromMap(Map map) { + return ProfitLossProduct( + productId: map['product_id'], + productName: map['product_name'], + categoryId: map['category_id'], + categoryName: map['category_name'], + quantitySold: map['quantity_sold'], + revenue: map['revenue'], + cost: map['cost'], + grossProfit: map['gross_profit'], + grossProfitMargin: (map['gross_profit_margin'] as num).toDouble(), + averagePrice: map['average_price'], + averageCost: map['average_cost'], + profitPerUnit: map['profit_per_unit'], + ); + } + + Map toMap() { + return { + 'product_id': productId, + 'product_name': productName, + 'category_id': categoryId, + 'category_name': categoryName, + 'quantity_sold': quantitySold, + 'revenue': revenue, + 'cost': cost, + 'gross_profit': grossProfit, + 'gross_profit_margin': grossProfitMargin, + 'average_price': averagePrice, + 'average_cost': averageCost, + 'profit_per_unit': profitPerUnit, + }; + } +} diff --git a/lib/data/models/response/sales_analytic_response_model.dart b/lib/data/models/response/sales_analytic_response_model.dart new file mode 100644 index 0000000..9176304 --- /dev/null +++ b/lib/data/models/response/sales_analytic_response_model.dart @@ -0,0 +1,195 @@ +class SalesAnalyticResponseModel { + final bool success; + final SalesAnalyticData data; + final dynamic errors; + + SalesAnalyticResponseModel({ + required this.success, + required this.data, + this.errors, + }); + + factory SalesAnalyticResponseModel.fromJson(Map json) => + SalesAnalyticResponseModel.fromMap(json); + + Map toJson() => toMap(); + + factory SalesAnalyticResponseModel.fromMap(Map map) { + return SalesAnalyticResponseModel( + success: map['success'], + data: SalesAnalyticData.fromMap(map['data']), + errors: map['errors'], + ); + } + + Map toMap() { + return { + 'success': success, + 'data': data.toMap(), + 'errors': errors, + }; + } +} + +class SalesAnalyticData { + final String organizationId; + final String outletId; + final DateTime dateFrom; + final DateTime dateTo; + final String groupBy; + final SalesSummary summary; + final List data; + + SalesAnalyticData({ + required this.organizationId, + required this.outletId, + required this.dateFrom, + required this.dateTo, + required this.groupBy, + required this.summary, + required this.data, + }); + + factory SalesAnalyticData.fromJson(Map json) => + SalesAnalyticData.fromMap(json); + + Map toJson() => toMap(); + + factory SalesAnalyticData.fromMap(Map map) { + return SalesAnalyticData( + organizationId: map['organization_id'], + outletId: map['outlet_id'], + dateFrom: DateTime.parse(map['date_from']), + dateTo: DateTime.parse(map['date_to']), + groupBy: map['group_by'], + summary: SalesSummary.fromMap(map['summary']), + data: List.from( + map['data']?.map((x) => SalesAnalyticItem.fromMap(x)) ?? [], + ), + ); + } + + Map toMap() { + return { + 'organization_id': organizationId, + 'outlet_id': outletId, + 'date_from': dateFrom.toIso8601String(), + 'date_to': dateTo.toIso8601String(), + 'group_by': groupBy, + 'summary': summary.toMap(), + 'data': data.map((x) => x.toMap()).toList(), + }; + } +} + +class SalesSummary { + final int totalSales; + final int totalOrders; + final int totalItems; + final double averageOrderValue; + final int totalTax; + final int totalDiscount; + final int netSales; + + SalesSummary({ + required this.totalSales, + required this.totalOrders, + required this.totalItems, + required this.averageOrderValue, + required this.totalTax, + required this.totalDiscount, + required this.netSales, + }); + + factory SalesSummary.fromJson(Map json) => + SalesSummary.fromMap(json); + + Map toJson() => toMap(); + + factory SalesSummary.fromMap(Map map) { + return SalesSummary( + totalSales: map['total_sales'], + totalOrders: map['total_orders'], + totalItems: map['total_items'], + averageOrderValue: (map['average_order_value'] as num).toDouble(), + totalTax: map['total_tax'], + totalDiscount: map['total_discount'], + netSales: map['net_sales'], + ); + } + + Map toMap() { + return { + 'total_sales': totalSales, + 'total_orders': totalOrders, + 'total_items': totalItems, + 'average_order_value': averageOrderValue, + 'total_tax': totalTax, + 'total_discount': totalDiscount, + 'net_sales': netSales, + }; + } +} + +class SalesAnalyticItem { + final DateTime date; + final int sales; + final int orders; + final int items; + final int tax; + final int discount; + final int netSales; + + SalesAnalyticItem({ + required this.date, + required this.sales, + required this.orders, + required this.items, + required this.tax, + required this.discount, + required this.netSales, + }); + + factory SalesAnalyticItem.fromJson(Map json) => + SalesAnalyticItem.fromMap(json); + + Map toJson() => toMap(); + + factory SalesAnalyticItem.fromMap(Map map) { + return SalesAnalyticItem( + date: DateTime.parse(map['date']), + sales: map['sales'], + orders: map['orders'], + items: map['items'], + tax: map['tax'], + discount: map['discount'], + netSales: map['net_sales'], + ); + } + + Map toMap() { + return { + 'date': date.toIso8601String(), + 'sales': sales, + 'orders': orders, + 'items': items, + 'tax': tax, + 'discount': discount, + 'net_sales': netSales, + }; + } +} + +class SalesInsights { + final List originalData; + final List sortedDailyData; + final SalesAnalyticItem? highestRevenueDay; + final SalesSummary summary; + + SalesInsights({ + required this.originalData, + required this.sortedDailyData, + required this.highestRevenueDay, + required this.summary, + }); +} diff --git a/lib/data/models/response/table_model.dart b/lib/data/models/response/table_model.dart index 85e81d3..01291ce 100644 --- a/lib/data/models/response/table_model.dart +++ b/lib/data/models/response/table_model.dart @@ -1,74 +1,132 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first -import 'dart:ui'; +import 'dart:convert'; + +class TableResponseModel { + final bool? success; + final TableData? data; + final dynamic errors; + + TableResponseModel({ + this.success, + this.data, + this.errors, + }); + + factory TableResponseModel.fromJson(String str) => + TableResponseModel.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); + + factory TableResponseModel.fromMap(Map json) => + TableResponseModel( + success: json["success"], + data: json["data"] == null ? null : TableData.fromMap(json["data"]), + errors: json["errors"], + ); + + Map toMap() => { + "success": success, + "data": data?.toMap(), + "errors": errors, + }; +} + +class TableData { + final List? tables; + final int? totalCount; + final int? page; + final int? limit; + final int? totalPages; + + TableData({ + this.tables, + this.totalCount, + this.page, + this.limit, + this.totalPages, + }); + + factory TableData.fromMap(Map json) => TableData( + tables: json["tables"] == null + ? [] + : List.from( + json["tables"].map((x) => TableModel.fromMap(x))), + totalCount: json["total_count"], + page: json["page"], + limit: json["limit"], + totalPages: json["total_pages"], + ); + + Map toMap() => { + "tables": tables == null + ? [] + : List.from(tables!.map((x) => x.toMap())), + "total_count": totalCount, + "page": page, + "limit": limit, + "total_pages": totalPages, + }; +} class TableModel { - int? id; - final String tableName; - final String startTime; - final String status; - final int orderId; - final int paymentAmount; - final Offset position; + String? id; + String? organizationId; + String? outletId; + String? tableName; + String? status; + int? paymentAmount; + double? positionX; + double? positionY; + int? capacity; + bool? isActive; + DateTime? createdAt; + DateTime? updatedAt; TableModel({ this.id, - required this.tableName, - required this.startTime, - required this.status, - required this.orderId, - required this.paymentAmount, - required this.position, + this.organizationId, + this.outletId, + this.tableName, + this.status, + this.paymentAmount, + this.positionX, + this.positionY, + this.capacity, + this.isActive, + this.createdAt, + this.updatedAt, }); - @override + factory TableModel.fromMap(Map json) => TableModel( + id: json["id"], + organizationId: json["organization_id"], + outletId: json["outlet_id"], + tableName: json["table_name"], + status: json["status"], + paymentAmount: json["payment_amount"], + positionX: json["position_x"]?.toDouble(), + positionY: json["position_y"]?.toDouble(), + capacity: json["capacity"], + isActive: json["is_active"], + createdAt: json["created_at"] == null + ? null + : DateTime.parse(json["created_at"]), + updatedAt: json["updated_at"] == null + ? null + : DateTime.parse(json["updated_at"]), + ); - // from map - factory TableModel.fromMap(Map map) { - return TableModel( - id: map['id'], - tableName: map['table_name'], - startTime: map['start_time'], - status: map['status'], - orderId: map['order_id'], - paymentAmount: map['payment_amount'], - position: Offset(map['x_position'], map['y_position']), - ); - } - - // to map - Map toMap() { - return { - 'table_name': tableName, - 'status': status, - 'start_time': startTime, - 'order_id': orderId, - 'payment_amount': paymentAmount, - 'x_position': position.dx, - 'y_position': position.dy, - }; - } - - @override - bool operator ==(covariant TableModel other) { - if (identical(this, other)) return true; - - return other.id == id && - other.tableName == tableName && - other.startTime == startTime && - other.status == status && - other.orderId == orderId && - other.paymentAmount == paymentAmount && - other.position == position; - } - - @override - int get hashCode { - return id.hashCode ^ - tableName.hashCode ^ - startTime.hashCode ^ - status.hashCode ^ - orderId.hashCode ^ - paymentAmount.hashCode ^ - position.hashCode; - } + Map toMap() => { + "id": id, + "organization_id": organizationId, + "outlet_id": outletId, + "table_name": tableName, + "status": status, + "payment_amount": paymentAmount, + "position_x": positionX, + "position_y": positionY, + "capacity": capacity, + "is_active": isActive, + "created_at": createdAt?.toIso8601String(), + "updated_at": updatedAt?.toIso8601String(), + }; } diff --git a/lib/data/type/bussines_type.dart b/lib/data/type/bussines_type.dart new file mode 100644 index 0000000..58695bd --- /dev/null +++ b/lib/data/type/bussines_type.dart @@ -0,0 +1,17 @@ +enum BusinessType { + restaurant('restaurant'), + retail('retail'), + service('service'), + ticketing('ticketing'), + entertainment('entertainment'); + + final String value; + const BusinessType(this.value); + + static BusinessType fromString(String value) { + return BusinessType.values.firstWhere( + (type) => type.value == value, + orElse: () => BusinessType.restaurant, + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index e3017be..e6acfe8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,29 @@ import 'dart:developer'; +import 'package:enaklo_pos/core/constants/theme.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/customer_remote_datasource.dart'; +import 'package:enaklo_pos/data/datasources/file_remote_datasource.dart'; +import 'package:enaklo_pos/data/datasources/outlet_remote_data_source.dart'; +import 'package:enaklo_pos/data/datasources/table_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_loader/customer_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/order_form/order_form_bloc.dart'; +import 'package:enaklo_pos/presentation/home/bloc/outlet_loader/outlet_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/refund/bloc/refund_bloc.dart'; +import 'package:enaklo_pos/presentation/report/blocs/profit_loss/profit_loss_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/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/table/blocs/transfer_table/transfer_table_bloc.dart'; +import 'package:enaklo_pos/presentation/void/bloc/void_order_bloc.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:enaklo_pos/data/dataoutputs/laman_print.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/category_remote_datasource.dart'; @@ -10,7 +32,6 @@ import 'package:enaklo_pos/data/datasources/midtrans_remote_datasource.dart'; import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart'; import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; import 'package:enaklo_pos/data/datasources/product_remote_datasource.dart'; -import 'package:enaklo_pos/data/datasources/order_item_remote_datasource.dart'; import 'package:enaklo_pos/data/datasources/payment_methods_remote_datasource.dart'; import 'package:enaklo_pos/data/datasources/settings_local_datasource.dart'; import 'package:enaklo_pos/presentation/auth/bloc/logout/logout_bloc.dart'; @@ -39,7 +60,6 @@ import 'package:enaklo_pos/presentation/setting/bloc/update_product/update_produ import 'package:enaklo_pos/presentation/setting/bloc/update_printer/update_printer_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/generate_table/generate_table_bloc.dart'; import 'package:enaklo_pos/presentation/table/blocs/get_table/get_table_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/local_product/local_product_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/order/order_bloc.dart'; @@ -51,21 +71,20 @@ 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/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/table/pages/new_table_management_page.dart'; -import 'package:google_fonts/google_fonts.dart'; -// import 'package:imin_printer/imin_printer.dart'; +import 'package:flutter/services.dart'; -import 'core/constants/colors.dart'; import 'presentation/auth/bloc/login/login_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'presentation/home/pages/dashboard_page.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:hive/hive.dart'; -import 'package:path_provider/path_provider.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); + await SystemChrome.setPreferredOrientations([ + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight, + ]); // await LamanPrint.init(); // final dir = await getApplicationDocumentsDirectory(); // Hive.init(dir.path); @@ -116,7 +135,8 @@ class _MyAppState extends State { LocalProductBloc(ProductLocalDatasource.instance), ), BlocProvider( - create: (context) => CheckoutBloc(settingsLocalDatasource: SettingsLocalDatasource()), + create: (context) => + CheckoutBloc(settingsLocalDatasource: SettingsLocalDatasource()), ), BlocProvider( create: (context) => TaxSettingsBloc(SettingsLocalDatasource()), @@ -140,13 +160,13 @@ class _MyAppState extends State { create: (context) => TransactionReportBloc(OrderRemoteDatasource()), ), BlocProvider( - create: (context) => CreateTableBloc(), + create: (context) => CreateTableBloc(TableRemoteDataSource()), ), BlocProvider( - create: (context) => ChangePositionTableBloc(), + create: (context) => ChangePositionTableBloc(TableRemoteDataSource()), ), BlocProvider( - create: (context) => GetTableBloc(), + create: (context) => GetTableBloc(TableRemoteDataSource()), ), BlocProvider( create: (context) => UpdateTableBloc(), @@ -159,7 +179,7 @@ class _MyAppState extends State { LastOrderTableBloc(ProductLocalDatasource.instance), ), BlocProvider( - create: (context) => GetTableStatusBloc(), + create: (context) => GetTableStatusBloc(TableRemoteDataSource()), ), BlocProvider( create: (context) => AddProductBloc(ProductRemoteDatasource()), @@ -174,16 +194,20 @@ class _MyAppState extends State { create: (context) => GetCategoriesBloc(CategoryRemoteDatasource()), ), BlocProvider( - create: (context) => SummaryBloc(OrderRemoteDatasource()), + create: (context) => SummaryBloc(AnalyticRemoteDatasource()), ), BlocProvider( - create: (context) => ProductSalesBloc(OrderItemRemoteDatasource()), + create: (context) => ProfitLossBloc(AnalyticRemoteDatasource()), ), BlocProvider( - create: (context) => ItemSalesReportBloc(OrderItemRemoteDatasource()), + create: (context) => ProductSalesBloc(AnalyticRemoteDatasource()), ), BlocProvider( - create: (context) => PaymentMethodReportBloc(OrderRemoteDatasource()), + create: (context) => ItemSalesReportBloc(AnalyticRemoteDatasource()), + ), + BlocProvider( + create: (context) => + PaymentMethodReportBloc(AnalyticRemoteDatasource()), ), BlocProvider( create: (context) => DaySalesBloc(ProductLocalDatasource.instance), @@ -192,7 +216,8 @@ class _MyAppState extends State { create: (context) => QrisBloc(MidtransRemoteDatasource()), ), BlocProvider( - create: (context) => PaymentMethodsBloc(PaymentMethodsRemoteDatasource()), + create: (context) => + PaymentMethodsBloc(PaymentMethodsRemoteDatasource()), ), BlocProvider( create: (context) => OnlineCheckerBloc(), @@ -218,29 +243,57 @@ class _MyAppState extends State { BlocProvider( create: (context) => AddOrderItemsBloc(OrderRemoteDatasource()), ), + BlocProvider( + create: (context) => ProductLoaderBloc(ProductRemoteDatasource()), + ), + BlocProvider( + create: (context) => OrderFormBloc(OrderRemoteDatasource()), + ), + BlocProvider( + create: (context) => OrderLoaderBloc(OrderRemoteDatasource()), + ), + BlocProvider( + create: (context) => OutletLoaderBloc(OutletRemoteDataSource()), + ), + BlocProvider( + create: (context) => CustomerLoaderBloc(CustomerRemoteDataSource()), + ), + BlocProvider( + create: (context) => CustomerFormBloc(CustomerRemoteDataSource()), + ), + BlocProvider( + create: (context) => PaymentFormBloc(OrderRemoteDatasource()), + ), + BlocProvider( + create: (context) => CurrentOutletBloc(OutletRemoteDataSource()), + ), + BlocProvider( + create: (context) => VoidOrderBloc(OrderRemoteDatasource()), + ), + BlocProvider( + create: (context) => RefundBloc(OrderRemoteDatasource()), + ), + BlocProvider( + create: (context) => UserUpdateOutletBloc(UserRemoteDatasource()), + ), + BlocProvider( + create: (context) => UploadFileBloc(FileRemoteDataSource()), + ), + BlocProvider( + create: (context) => CategoryLoaderBloc(CategoryRemoteDatasource()), + ), + BlocProvider( + create: (context) => GetPrinterTicketBloc(), + ), + BlocProvider( + create: (context) => TransferTableBloc(TableRemoteDataSource()), + ), ], child: MaterialApp( + navigatorKey: AuthInterceptor.navigatorKey, debugShowCheckedModeBanner: false, title: 'POS Resto App', - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: AppColors.primary), - useMaterial3: true, - textTheme: GoogleFonts.quicksandTextTheme( - Theme.of(context).textTheme, - ), - appBarTheme: AppBarTheme( - color: AppColors.white, - elevation: 0, - titleTextStyle: GoogleFonts.quicksand( - color: AppColors.primary, - fontSize: 16.0, - fontWeight: FontWeight.w500, - ), - iconTheme: const IconThemeData( - color: AppColors.primary, - ), - ), - ), + theme: getApplicationTheme, home: FutureBuilder( future: AuthLocalDataSource().isAuthDataExists(), builder: (context, snapshot) { diff --git a/lib/presentation/auth/login_page.dart b/lib/presentation/auth/login_page.dart index db91d5d..8c8aa96 100644 --- a/lib/presentation/auth/login_page.dart +++ b/lib/presentation/auth/login_page.dart @@ -49,7 +49,7 @@ class _LoginPageState extends State { const SpaceHeight(24.0), const Center( child: Text( - 'Enaklo POS', + 'Apskel POS', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w700, diff --git a/lib/presentation/customer/bloc/customer_form/customer_form_bloc.dart b/lib/presentation/customer/bloc/customer_form/customer_form_bloc.dart new file mode 100644 index 0000000..8a5b8ef --- /dev/null +++ b/lib/presentation/customer/bloc/customer_form/customer_form_bloc.dart @@ -0,0 +1,28 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/customer_remote_datasource.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'customer_form_event.dart'; +part 'customer_form_state.dart'; +part 'customer_form_bloc.freezed.dart'; + +class CustomerFormBloc extends Bloc { + final CustomerRemoteDataSource _customerRemoteDataSource; + CustomerFormBloc(this._customerRemoteDataSource) + : super(CustomerFormState.initial()) { + on<_Create>((event, emit) async { + emit(const _Loading()); + final result = await _customerRemoteDataSource.createCustomer( + name: event.name, + address: event.address, + phone: event.phoneNumber, + email: event.email, + isActive: event.isActive, + ); + result.fold( + (l) => emit(_Error(l)), + (r) => emit(_Success()), + ); + }); + } +} diff --git a/lib/presentation/customer/bloc/customer_form/customer_form_bloc.freezed.dart b/lib/presentation/customer/bloc/customer_form/customer_form_bloc.freezed.dart new file mode 100644 index 0000000..fe82761 --- /dev/null +++ b/lib/presentation/customer/bloc/customer_form/customer_form_bloc.freezed.dart @@ -0,0 +1,908 @@ +// 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 'customer_form_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$CustomerFormEvent { + String get name => throw _privateConstructorUsedError; + String get address => throw _privateConstructorUsedError; + String get phoneNumber => throw _privateConstructorUsedError; + String get email => throw _privateConstructorUsedError; + bool get isActive => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(String name, String address, String phoneNumber, + String email, bool isActive) + create, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String name, String address, String phoneNumber, + String email, bool isActive)? + create, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String name, String address, String phoneNumber, + String email, bool isActive)? + create, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Create value) create, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Create value)? create, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Create value)? create, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Create a copy of CustomerFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CustomerFormEventCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CustomerFormEventCopyWith<$Res> { + factory $CustomerFormEventCopyWith( + CustomerFormEvent value, $Res Function(CustomerFormEvent) then) = + _$CustomerFormEventCopyWithImpl<$Res, CustomerFormEvent>; + @useResult + $Res call( + {String name, + String address, + String phoneNumber, + String email, + bool isActive}); +} + +/// @nodoc +class _$CustomerFormEventCopyWithImpl<$Res, $Val extends CustomerFormEvent> + implements $CustomerFormEventCopyWith<$Res> { + _$CustomerFormEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CustomerFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? address = null, + Object? phoneNumber = null, + Object? email = null, + Object? isActive = null, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + address: null == address + ? _value.address + : address // ignore: cast_nullable_to_non_nullable + as String, + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$CreateImplCopyWith<$Res> + implements $CustomerFormEventCopyWith<$Res> { + factory _$$CreateImplCopyWith( + _$CreateImpl value, $Res Function(_$CreateImpl) then) = + __$$CreateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String name, + String address, + String phoneNumber, + String email, + bool isActive}); +} + +/// @nodoc +class __$$CreateImplCopyWithImpl<$Res> + extends _$CustomerFormEventCopyWithImpl<$Res, _$CreateImpl> + implements _$$CreateImplCopyWith<$Res> { + __$$CreateImplCopyWithImpl( + _$CreateImpl _value, $Res Function(_$CreateImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? address = null, + Object? phoneNumber = null, + Object? email = null, + Object? isActive = null, + }) { + return _then(_$CreateImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + address: null == address + ? _value.address + : address // ignore: cast_nullable_to_non_nullable + as String, + phoneNumber: null == phoneNumber + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$CreateImpl implements _Create { + const _$CreateImpl( + {required this.name, + required this.address, + required this.phoneNumber, + required this.email, + required this.isActive}); + + @override + final String name; + @override + final String address; + @override + final String phoneNumber; + @override + final String email; + @override + final bool isActive; + + @override + String toString() { + return 'CustomerFormEvent.create(name: $name, address: $address, phoneNumber: $phoneNumber, email: $email, isActive: $isActive)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CreateImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.address, address) || other.address == address) && + (identical(other.phoneNumber, phoneNumber) || + other.phoneNumber == phoneNumber) && + (identical(other.email, email) || other.email == email) && + (identical(other.isActive, isActive) || + other.isActive == isActive)); + } + + @override + int get hashCode => + Object.hash(runtimeType, name, address, phoneNumber, email, isActive); + + /// Create a copy of CustomerFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CreateImplCopyWith<_$CreateImpl> get copyWith => + __$$CreateImplCopyWithImpl<_$CreateImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String name, String address, String phoneNumber, + String email, bool isActive) + create, + }) { + return create(name, address, phoneNumber, email, isActive); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String name, String address, String phoneNumber, + String email, bool isActive)? + create, + }) { + return create?.call(name, address, phoneNumber, email, isActive); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String name, String address, String phoneNumber, + String email, bool isActive)? + create, + required TResult orElse(), + }) { + if (create != null) { + return create(name, address, phoneNumber, email, isActive); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Create value) create, + }) { + return create(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Create value)? create, + }) { + return create?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Create value)? create, + required TResult orElse(), + }) { + if (create != null) { + return create(this); + } + return orElse(); + } +} + +abstract class _Create implements CustomerFormEvent { + const factory _Create( + {required final String name, + required final String address, + required final String phoneNumber, + required final String email, + required final bool isActive}) = _$CreateImpl; + + @override + String get name; + @override + String get address; + @override + String get phoneNumber; + @override + String get email; + @override + bool get isActive; + + /// Create a copy of CustomerFormEvent + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CreateImplCopyWith<_$CreateImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$CustomerFormState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CustomerFormStateCopyWith<$Res> { + factory $CustomerFormStateCopyWith( + CustomerFormState value, $Res Function(CustomerFormState) then) = + _$CustomerFormStateCopyWithImpl<$Res, CustomerFormState>; +} + +/// @nodoc +class _$CustomerFormStateCopyWithImpl<$Res, $Val extends CustomerFormState> + implements $CustomerFormStateCopyWith<$Res> { + _$CustomerFormStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CustomerFormState + /// 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 _$CustomerFormStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerFormState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'CustomerFormState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements CustomerFormState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$CustomerFormStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerFormState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'CustomerFormState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class _Loading implements CustomerFormState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$SuccessImplCopyWith<$Res> { + factory _$$SuccessImplCopyWith( + _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = + __$$SuccessImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SuccessImplCopyWithImpl<$Res> + extends _$CustomerFormStateCopyWithImpl<$Res, _$SuccessImpl> + implements _$$SuccessImplCopyWith<$Res> { + __$$SuccessImplCopyWithImpl( + _$SuccessImpl _value, $Res Function(_$SuccessImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerFormState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SuccessImpl implements _Success { + const _$SuccessImpl(); + + @override + String toString() { + return 'CustomerFormState.success()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SuccessImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return success(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return success?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return success(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return success?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(this); + } + return orElse(); + } +} + +abstract class _Success implements CustomerFormState { + const factory _Success() = _$SuccessImpl; +} + +/// @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 _$CustomerFormStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerFormState + /// 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 'CustomerFormState.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 CustomerFormState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements CustomerFormState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of CustomerFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/customer/bloc/customer_form/customer_form_event.dart b/lib/presentation/customer/bloc/customer_form/customer_form_event.dart new file mode 100644 index 0000000..cf1b06f --- /dev/null +++ b/lib/presentation/customer/bloc/customer_form/customer_form_event.dart @@ -0,0 +1,11 @@ +part of 'customer_form_bloc.dart'; + +@freezed +class CustomerFormEvent with _$CustomerFormEvent { + const factory CustomerFormEvent.create( + {required String name, + required String address, + required String phoneNumber, + required String email, + required bool isActive}) = _Create; +} diff --git a/lib/presentation/customer/bloc/customer_form/customer_form_state.dart b/lib/presentation/customer/bloc/customer_form/customer_form_state.dart new file mode 100644 index 0000000..0fb5c71 --- /dev/null +++ b/lib/presentation/customer/bloc/customer_form/customer_form_state.dart @@ -0,0 +1,9 @@ +part of 'customer_form_bloc.dart'; + +@freezed +class CustomerFormState with _$CustomerFormState { + const factory CustomerFormState.initial() = _Initial; + const factory CustomerFormState.loading() = _Loading; + const factory CustomerFormState.success() = _Success; + const factory CustomerFormState.error(String message) = _Error; +} diff --git a/lib/presentation/customer/bloc/customer_loader/customer_loader_bloc.dart b/lib/presentation/customer/bloc/customer_loader/customer_loader_bloc.dart new file mode 100644 index 0000000..f4c2824 --- /dev/null +++ b/lib/presentation/customer/bloc/customer_loader/customer_loader_bloc.dart @@ -0,0 +1,28 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/customer_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'customer_loader_event.dart'; +part 'customer_loader_state.dart'; +part 'customer_loader_bloc.freezed.dart'; + +class CustomerLoaderBloc + extends Bloc { + final CustomerRemoteDataSource _customerRemoteDataSource; + CustomerLoaderBloc( + this._customerRemoteDataSource, + ) : super(CustomerLoaderState.initial()) { + on<_GetCustomer>((event, emit) async { + emit(const _Loading()); + final result = await _customerRemoteDataSource.getCustomers(); + result.fold( + (l) => emit(_Error(l)), + (r) => emit(_Loaded( + r.data?.customers ?? [], + r.data?.totalCount ?? 0, + )), + ); + }); + } +} diff --git a/lib/presentation/customer/bloc/customer_loader/customer_loader_bloc.freezed.dart b/lib/presentation/customer/bloc/customer_loader/customer_loader_bloc.freezed.dart new file mode 100644 index 0000000..4705a14 --- /dev/null +++ b/lib/presentation/customer/bloc/customer_loader/customer_loader_bloc.freezed.dart @@ -0,0 +1,809 @@ +// 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 'customer_loader_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$CustomerLoaderEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() getCustomer, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? getCustomer, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? getCustomer, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_GetCustomer value) getCustomer, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetCustomer value)? getCustomer, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetCustomer value)? getCustomer, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CustomerLoaderEventCopyWith<$Res> { + factory $CustomerLoaderEventCopyWith( + CustomerLoaderEvent value, $Res Function(CustomerLoaderEvent) then) = + _$CustomerLoaderEventCopyWithImpl<$Res, CustomerLoaderEvent>; +} + +/// @nodoc +class _$CustomerLoaderEventCopyWithImpl<$Res, $Val extends CustomerLoaderEvent> + implements $CustomerLoaderEventCopyWith<$Res> { + _$CustomerLoaderEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CustomerLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$GetCustomerImplCopyWith<$Res> { + factory _$$GetCustomerImplCopyWith( + _$GetCustomerImpl value, $Res Function(_$GetCustomerImpl) then) = + __$$GetCustomerImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$GetCustomerImplCopyWithImpl<$Res> + extends _$CustomerLoaderEventCopyWithImpl<$Res, _$GetCustomerImpl> + implements _$$GetCustomerImplCopyWith<$Res> { + __$$GetCustomerImplCopyWithImpl( + _$GetCustomerImpl _value, $Res Function(_$GetCustomerImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$GetCustomerImpl implements _GetCustomer { + const _$GetCustomerImpl(); + + @override + String toString() { + return 'CustomerLoaderEvent.getCustomer()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$GetCustomerImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() getCustomer, + }) { + return getCustomer(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? getCustomer, + }) { + return getCustomer?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? getCustomer, + required TResult orElse(), + }) { + if (getCustomer != null) { + return getCustomer(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetCustomer value) getCustomer, + }) { + return getCustomer(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetCustomer value)? getCustomer, + }) { + return getCustomer?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetCustomer value)? getCustomer, + required TResult orElse(), + }) { + if (getCustomer != null) { + return getCustomer(this); + } + return orElse(); + } +} + +abstract class _GetCustomer implements CustomerLoaderEvent { + const factory _GetCustomer() = _$GetCustomerImpl; +} + +/// @nodoc +mixin _$CustomerLoaderState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List customers, int totalCustomer) + loaded, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List customers, int totalCustomer)? loaded, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List customers, int totalCustomer)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + 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? 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 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 $CustomerLoaderStateCopyWith<$Res> { + factory $CustomerLoaderStateCopyWith( + CustomerLoaderState value, $Res Function(CustomerLoaderState) then) = + _$CustomerLoaderStateCopyWithImpl<$Res, CustomerLoaderState>; +} + +/// @nodoc +class _$CustomerLoaderStateCopyWithImpl<$Res, $Val extends CustomerLoaderState> + implements $CustomerLoaderStateCopyWith<$Res> { + _$CustomerLoaderStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CustomerLoaderState + /// 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 _$CustomerLoaderStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'CustomerLoaderState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List customers, int totalCustomer) + loaded, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List customers, int totalCustomer)? loaded, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List customers, int totalCustomer)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 CustomerLoaderState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$CustomerLoaderStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'CustomerLoaderState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List customers, int totalCustomer) + loaded, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List customers, int totalCustomer)? loaded, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List customers, int totalCustomer)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 CustomerLoaderState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$LoadedImplCopyWith<$Res> { + factory _$$LoadedImplCopyWith( + _$LoadedImpl value, $Res Function(_$LoadedImpl) then) = + __$$LoadedImplCopyWithImpl<$Res>; + @useResult + $Res call({List customers, int totalCustomer}); +} + +/// @nodoc +class __$$LoadedImplCopyWithImpl<$Res> + extends _$CustomerLoaderStateCopyWithImpl<$Res, _$LoadedImpl> + implements _$$LoadedImplCopyWith<$Res> { + __$$LoadedImplCopyWithImpl( + _$LoadedImpl _value, $Res Function(_$LoadedImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? customers = null, + Object? totalCustomer = null, + }) { + return _then(_$LoadedImpl( + null == customers + ? _value._customers + : customers // ignore: cast_nullable_to_non_nullable + as List, + null == totalCustomer + ? _value.totalCustomer + : totalCustomer // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +class _$LoadedImpl implements _Loaded { + const _$LoadedImpl(final List customers, this.totalCustomer) + : _customers = customers; + + final List _customers; + @override + List get customers { + if (_customers is EqualUnmodifiableListView) return _customers; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_customers); + } + + @override + final int totalCustomer; + + @override + String toString() { + return 'CustomerLoaderState.loaded(customers: $customers, totalCustomer: $totalCustomer)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoadedImpl && + const DeepCollectionEquality() + .equals(other._customers, _customers) && + (identical(other.totalCustomer, totalCustomer) || + other.totalCustomer == totalCustomer)); + } + + @override + int get hashCode => Object.hash(runtimeType, + const DeepCollectionEquality().hash(_customers), totalCustomer); + + /// Create a copy of CustomerLoaderState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List customers, int totalCustomer) + loaded, + required TResult Function(String message) error, + }) { + return loaded(customers, totalCustomer); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List customers, int totalCustomer)? loaded, + TResult? Function(String message)? error, + }) { + return loaded?.call(customers, totalCustomer); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List customers, int totalCustomer)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(customers, totalCustomer); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 CustomerLoaderState { + const factory _Loaded( + final List customers, final int totalCustomer) = _$LoadedImpl; + + List get customers; + int get totalCustomer; + + /// Create a copy of CustomerLoaderState + /// 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 _$CustomerLoaderStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of CustomerLoaderState + /// 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 'CustomerLoaderState.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 CustomerLoaderState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List customers, int totalCustomer) + loaded, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List customers, int totalCustomer)? loaded, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List customers, int totalCustomer)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 CustomerLoaderState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of CustomerLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/customer/bloc/customer_loader/customer_loader_event.dart b/lib/presentation/customer/bloc/customer_loader/customer_loader_event.dart new file mode 100644 index 0000000..e4aa318 --- /dev/null +++ b/lib/presentation/customer/bloc/customer_loader/customer_loader_event.dart @@ -0,0 +1,6 @@ +part of 'customer_loader_bloc.dart'; + +@freezed +class CustomerLoaderEvent with _$CustomerLoaderEvent { + const factory CustomerLoaderEvent.getCustomer() = _GetCustomer; +} diff --git a/lib/presentation/customer/bloc/customer_loader/customer_loader_state.dart b/lib/presentation/customer/bloc/customer_loader/customer_loader_state.dart new file mode 100644 index 0000000..ed968bf --- /dev/null +++ b/lib/presentation/customer/bloc/customer_loader/customer_loader_state.dart @@ -0,0 +1,10 @@ +part of 'customer_loader_bloc.dart'; + +@freezed +class CustomerLoaderState with _$CustomerLoaderState { + const factory CustomerLoaderState.initial() = _Initial; + const factory CustomerLoaderState.loading() = _Loading; + const factory CustomerLoaderState.loaded( + List customers, int totalCustomer) = _Loaded; + const factory CustomerLoaderState.error(String message) = _Error; +} diff --git a/lib/presentation/customer/dialog/form_customer_dialog.dart b/lib/presentation/customer/dialog/form_customer_dialog.dart new file mode 100644 index 0000000..fbbab92 --- /dev/null +++ b/lib/presentation/customer/dialog/form_customer_dialog.dart @@ -0,0 +1,121 @@ +import 'package:enaklo_pos/core/components/components.dart'; +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/constants/colors.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:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class FormCustomerDialog extends StatefulWidget { + const FormCustomerDialog({super.key}); + + @override + State createState() => _FormCustomerDialogState(); +} + +class _FormCustomerDialogState extends State { + TextEditingController nameController = TextEditingController(); + TextEditingController phoneController = TextEditingController(); + TextEditingController emailController = TextEditingController(); + TextEditingController addressController = TextEditingController(); + + final _formKey = GlobalKey(); + + void onSave() async { + if (_formKey.currentState!.validate()) { + context.read().add( + CustomerFormEvent.create( + name: nameController.text, + phoneNumber: phoneController.text, + email: emailController.text, + address: addressController.text, + isActive: true, + ), + ); + } + } + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Tambah Pelanggan', + subtitle: 'Tambahkan pelanggan baru', + contentPadding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0), + child: Form( + key: _formKey, + child: Column( + children: [ + CustomTextField( + controller: nameController, + label: 'Name *', + validator: (value) { + if (value == null || value.isEmpty) { + return 'Nama harus diisi'; + } + return null; + }, + ), + SpaceHeight(16), + CustomTextField( + controller: emailController, + label: 'Email (Opsional)', + ), + SpaceHeight(16), + CustomTextField( + controller: phoneController, + keyboardType: TextInputType.phone, + label: 'No. Handphone (Opsional)', + ), + SpaceHeight(16), + CustomTextField( + controller: addressController, + label: 'Alamat (Opsional)', + maxLines: 5, + ), + SpaceHeight(24), + BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: () { + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Pelanggan berhasil disimpan'), + backgroundColor: AppColors.green, + ), + ); + context + .read() + .add(const CustomerLoaderEvent.getCustomer()); + }, + error: (message) => + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: AppColors.red, + ), + ), + ); + }, + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Button.filled( + onPressed: onSave, + label: 'Simpan', + ), + loading: () => Center( + child: const CircularProgressIndicator(), + ), + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/customer/pages/customer_page.dart b/lib/presentation/customer/pages/customer_page.dart new file mode 100644 index 0000000..b3ab443 --- /dev/null +++ b/lib/presentation/customer/pages/customer_page.dart @@ -0,0 +1,217 @@ +import 'package:enaklo_pos/core/components/dashed_divider.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import 'package:enaklo_pos/presentation/customer/bloc/customer_loader/customer_loader_bloc.dart'; +import 'package:enaklo_pos/presentation/customer/widgets/customer_card.dart'; +import 'package:enaklo_pos/presentation/customer/widgets/customer_title.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CustomerPage extends StatefulWidget { + const CustomerPage({super.key}); + + @override + State createState() => _CustomerPageState(); +} + +class _CustomerPageState extends State { + Customer? _customer; + + @override + void initState() { + super.initState(); + context + .read() + .add(const CustomerLoaderEvent.getCustomer()); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + backgroundColor: AppColors.background, + body: Row( + children: [ + Expanded( + flex: 2, + child: Material( + color: AppColors.white, + child: Column( + children: [ + CustomerTitle( + title: 'Daftar Pelanggan', + onChanged: (value) {}, + ), + Expanded( + child: + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Center( + child: CircularProgressIndicator(), + ), + loading: () => Center( + child: CircularProgressIndicator(), + ), + error: (message) => Center( + child: Text( + message, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + loaded: (customers, totalCustomer) => + ListView.builder( + itemCount: customers.length, + itemBuilder: (context, index) { + final customer = customers[index]; + return CustomerCard( + customer: customer, + isActive: customer == _customer, + onTap: () { + setState(() { + _customer = customer; + }); + }, + ); + }, + ), + ); + }, + ), + ), + ], + ), + ), + ), + Expanded( + flex: 4, + child: _customer == null + ? Center( + child: Text( + "Belum ada pelanggan yang dipilih.", + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ) + : Center( + child: Container( + width: context.deviceHeight * 0.5, + height: context.deviceHeight * 0.6, + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 16.0, + ), + child: Column( + children: [ + CircleAvatar( + radius: 24, + backgroundColor: AppColors.primary, + child: + Icon(Icons.person, color: Colors.white), + ), + const SizedBox(height: 12), + Text( + _customer?.name ?? "", + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ) + ], + ), + ), + DashedDivider(color: AppColors.stroke), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 16.0, + ), + child: Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'No. Handphone', + style: TextStyle( + fontSize: 12.0, + ), + ), + Text( + _customer?.phone ?? "-", + style: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + SpaceHeight(8), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Email', + style: TextStyle( + fontSize: 12.0, + ), + ), + Text( + _customer?.email ?? "-", + style: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + SpaceHeight(8), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Alamat', + style: TextStyle( + fontSize: 12.0, + ), + ), + Text( + _customer?.address ?? "-", + style: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/customer/widgets/customer_card.dart b/lib/presentation/customer/widgets/customer_card.dart new file mode 100644 index 0000000..79dd7b3 --- /dev/null +++ b/lib/presentation/customer/widgets/customer_card.dart @@ -0,0 +1,72 @@ +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import 'package:flutter/material.dart'; + +class CustomerCard extends StatelessWidget { + final Customer customer; + final bool isActive; + final Function() onTap; + const CustomerCard({ + super.key, + required this.customer, + required this.isActive, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: + isActive ? AppColors.primary.withOpacity(0.1) : AppColors.white, + border: Border.all( + color: isActive ? AppColors.primary : AppColors.stroke), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CircleAvatar( + radius: 22, + backgroundColor: AppColors.primary, + child: Icon(Icons.person, color: Colors.white), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + customer.name ?? '', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Text( + customer.email ?? '-', + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/customer/widgets/customer_title.dart b/lib/presentation/customer/widgets/customer_title.dart new file mode 100644 index 0000000..a0f9009 --- /dev/null +++ b/lib/presentation/customer/widgets/customer_title.dart @@ -0,0 +1,107 @@ +import 'package:enaklo_pos/core/components/components.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/presentation/customer/dialog/form_customer_dialog.dart'; +import 'package:flutter/material.dart'; + +class CustomerTitle extends StatelessWidget { + final String title; + final Function(String) onChanged; + + const CustomerTitle( + {super.key, required this.onChanged, required this.title}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + height: context.deviceHeight * 0.1, + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.background, + width: 1.0, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + title, + style: TextStyle( + color: AppColors.black, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 12.0, + ), + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.background, + width: 1.0, + ), + ), + ), + child: Column( + children: [ + Row( + children: [ + Expanded( + child: SizedBox( + height: 40, + child: TextFormField( + onChanged: onChanged, + decoration: InputDecoration( + prefixIcon: Icon( + Icons.search, + ), + hintText: 'Cari Customer', + ), + ), + ), + ), + SpaceWidth(12), + GestureDetector( + onTap: () => showDialog( + context: context, + builder: (context) => FormCustomerDialog(), + ), + child: Container( + height: 40, + width: 40, + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: AppColors.primary, + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.add, + color: AppColors.white, + size: 20, + ), + ), + ), + ], + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/presentation/home/bloc/add_order_items/add_order_items_bloc.dart b/lib/presentation/home/bloc/add_order_items/add_order_items_bloc.dart index 0b5c9ad..7f5982e 100644 --- a/lib/presentation/home/bloc/add_order_items/add_order_items_bloc.dart +++ b/lib/presentation/home/bloc/add_order_items/add_order_items_bloc.dart @@ -3,7 +3,6 @@ import 'dart:developer'; import 'package:bloc/bloc.dart'; import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart'; import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; -import 'package:enaklo_pos/core/extensions/string_ext.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'add_order_items_event.dart'; @@ -12,24 +11,26 @@ part 'add_order_items_bloc.freezed.dart'; class AddOrderItemsBloc extends Bloc { final OrderRemoteDatasource orderRemoteDatasource; - + AddOrderItemsBloc( this.orderRemoteDatasource, ) : super(const _Initial()) { on<_AddOrderItems>((event, emit) async { emit(const _Loading()); - + try { // Convert ProductQuantity list to the format expected by the API - final orderItems = event.items.map((item) => { - 'id_product': item.product.productId, - 'quantity': item.quantity, - 'price': item.product.price!.toIntegerFromText, - 'notes': item.notes, - }).toList(); + final orderItems = event.items + .map((item) => { + 'id_product': item.product.id, + 'quantity': item.quantity, + 'price': item.product.price, + 'notes': item.notes, + }) + .toList(); log("Adding order items: ${orderItems.toString()}"); - + final result = await orderRemoteDatasource.addOrderItems( event.orderId, orderItems, @@ -45,4 +46,4 @@ class AddOrderItemsBloc extends Bloc { } }); } -} \ No newline at end of file +} diff --git a/lib/presentation/home/bloc/category_loader/category_loader_bloc.dart b/lib/presentation/home/bloc/category_loader/category_loader_bloc.dart new file mode 100644 index 0000000..d1653d1 --- /dev/null +++ b/lib/presentation/home/bloc/category_loader/category_loader_bloc.dart @@ -0,0 +1,43 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/category_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/response/category_response_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'category_loader_event.dart'; +part 'category_loader_state.dart'; +part 'category_loader_bloc.freezed.dart'; + +class CategoryLoaderBloc + extends Bloc { + final CategoryRemoteDatasource _datasource; + CategoryLoaderBloc(this._datasource) : super(CategoryLoaderState.initial()) { + on<_Get>((event, emit) async { + emit(const _Loading()); + final result = await _datasource.getCategories(limit: 50); + result.fold( + (l) => emit(_Error(l)), + (r) async { + List categories = r.data.categories; + categories.insert( + 0, + CategoryModel( + id: "", + name: 'Semua', + organizationId: '', + businessType: '', + metadata: {}, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ), + ); + emit(_Loaded(categories, null)); + }, + ); + }); + on<_SetCategoryId>((event, emit) async { + var currentState = state as _Loaded; + + emit(_Loaded(currentState.categories, event.categoryId)); + }); + } +} diff --git a/lib/presentation/home/bloc/category_loader/category_loader_bloc.freezed.dart b/lib/presentation/home/bloc/category_loader/category_loader_bloc.freezed.dart new file mode 100644 index 0000000..0bd9d16 --- /dev/null +++ b/lib/presentation/home/bloc/category_loader/category_loader_bloc.freezed.dart @@ -0,0 +1,976 @@ +// 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_loader_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$CategoryLoaderEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() get, + required TResult Function(String categoryId) setCategoryId, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? get, + TResult? Function(String categoryId)? setCategoryId, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? get, + TResult Function(String categoryId)? setCategoryId, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Get value) get, + required TResult Function(_SetCategoryId value) setCategoryId, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Get value)? get, + TResult? Function(_SetCategoryId value)? setCategoryId, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Get value)? get, + TResult Function(_SetCategoryId value)? setCategoryId, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CategoryLoaderEventCopyWith<$Res> { + factory $CategoryLoaderEventCopyWith( + CategoryLoaderEvent value, $Res Function(CategoryLoaderEvent) then) = + _$CategoryLoaderEventCopyWithImpl<$Res, CategoryLoaderEvent>; +} + +/// @nodoc +class _$CategoryLoaderEventCopyWithImpl<$Res, $Val extends CategoryLoaderEvent> + implements $CategoryLoaderEventCopyWith<$Res> { + _$CategoryLoaderEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$GetImplCopyWith<$Res> { + factory _$$GetImplCopyWith(_$GetImpl value, $Res Function(_$GetImpl) then) = + __$$GetImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$GetImplCopyWithImpl<$Res> + extends _$CategoryLoaderEventCopyWithImpl<$Res, _$GetImpl> + implements _$$GetImplCopyWith<$Res> { + __$$GetImplCopyWithImpl(_$GetImpl _value, $Res Function(_$GetImpl) _then) + : super(_value, _then); + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$GetImpl implements _Get { + const _$GetImpl(); + + @override + String toString() { + return 'CategoryLoaderEvent.get()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$GetImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() get, + required TResult Function(String categoryId) setCategoryId, + }) { + return get(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? get, + TResult? Function(String categoryId)? setCategoryId, + }) { + return get?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? get, + TResult Function(String categoryId)? setCategoryId, + required TResult orElse(), + }) { + if (get != null) { + return get(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Get value) get, + required TResult Function(_SetCategoryId value) setCategoryId, + }) { + return get(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Get value)? get, + TResult? Function(_SetCategoryId value)? setCategoryId, + }) { + return get?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Get value)? get, + TResult Function(_SetCategoryId value)? setCategoryId, + required TResult orElse(), + }) { + if (get != null) { + return get(this); + } + return orElse(); + } +} + +abstract class _Get implements CategoryLoaderEvent { + const factory _Get() = _$GetImpl; +} + +/// @nodoc +abstract class _$$SetCategoryIdImplCopyWith<$Res> { + factory _$$SetCategoryIdImplCopyWith( + _$SetCategoryIdImpl value, $Res Function(_$SetCategoryIdImpl) then) = + __$$SetCategoryIdImplCopyWithImpl<$Res>; + @useResult + $Res call({String categoryId}); +} + +/// @nodoc +class __$$SetCategoryIdImplCopyWithImpl<$Res> + extends _$CategoryLoaderEventCopyWithImpl<$Res, _$SetCategoryIdImpl> + implements _$$SetCategoryIdImplCopyWith<$Res> { + __$$SetCategoryIdImplCopyWithImpl( + _$SetCategoryIdImpl _value, $Res Function(_$SetCategoryIdImpl) _then) + : super(_value, _then); + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? categoryId = null, + }) { + return _then(_$SetCategoryIdImpl( + null == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$SetCategoryIdImpl implements _SetCategoryId { + const _$SetCategoryIdImpl(this.categoryId); + + @override + final String categoryId; + + @override + String toString() { + return 'CategoryLoaderEvent.setCategoryId(categoryId: $categoryId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SetCategoryIdImpl && + (identical(other.categoryId, categoryId) || + other.categoryId == categoryId)); + } + + @override + int get hashCode => Object.hash(runtimeType, categoryId); + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SetCategoryIdImplCopyWith<_$SetCategoryIdImpl> get copyWith => + __$$SetCategoryIdImplCopyWithImpl<_$SetCategoryIdImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() get, + required TResult Function(String categoryId) setCategoryId, + }) { + return setCategoryId(categoryId); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? get, + TResult? Function(String categoryId)? setCategoryId, + }) { + return setCategoryId?.call(categoryId); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? get, + TResult Function(String categoryId)? setCategoryId, + required TResult orElse(), + }) { + if (setCategoryId != null) { + return setCategoryId(categoryId); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Get value) get, + required TResult Function(_SetCategoryId value) setCategoryId, + }) { + return setCategoryId(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Get value)? get, + TResult? Function(_SetCategoryId value)? setCategoryId, + }) { + return setCategoryId?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Get value)? get, + TResult Function(_SetCategoryId value)? setCategoryId, + required TResult orElse(), + }) { + if (setCategoryId != null) { + return setCategoryId(this); + } + return orElse(); + } +} + +abstract class _SetCategoryId implements CategoryLoaderEvent { + const factory _SetCategoryId(final String categoryId) = _$SetCategoryIdImpl; + + String get categoryId; + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SetCategoryIdImplCopyWith<_$SetCategoryIdImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$CategoryLoaderState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function( + List categories, String? categoryId) + loaded, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List categories, String? categoryId)? + loaded, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List categories, String? categoryId)? + loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + 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? 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 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 $CategoryLoaderStateCopyWith<$Res> { + factory $CategoryLoaderStateCopyWith( + CategoryLoaderState value, $Res Function(CategoryLoaderState) then) = + _$CategoryLoaderStateCopyWithImpl<$Res, CategoryLoaderState>; +} + +/// @nodoc +class _$CategoryLoaderStateCopyWithImpl<$Res, $Val extends CategoryLoaderState> + implements $CategoryLoaderStateCopyWith<$Res> { + _$CategoryLoaderStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CategoryLoaderState + /// 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 _$CategoryLoaderStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'CategoryLoaderState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function( + List categories, String? categoryId) + loaded, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List categories, String? categoryId)? + loaded, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List categories, String? categoryId)? + loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 CategoryLoaderState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$CategoryLoaderStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'CategoryLoaderState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function( + List categories, String? categoryId) + loaded, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List categories, String? categoryId)? + loaded, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List categories, String? categoryId)? + loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 CategoryLoaderState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$LoadedImplCopyWith<$Res> { + factory _$$LoadedImplCopyWith( + _$LoadedImpl value, $Res Function(_$LoadedImpl) then) = + __$$LoadedImplCopyWithImpl<$Res>; + @useResult + $Res call({List categories, String? categoryId}); +} + +/// @nodoc +class __$$LoadedImplCopyWithImpl<$Res> + extends _$CategoryLoaderStateCopyWithImpl<$Res, _$LoadedImpl> + implements _$$LoadedImplCopyWith<$Res> { + __$$LoadedImplCopyWithImpl( + _$LoadedImpl _value, $Res Function(_$LoadedImpl) _then) + : super(_value, _then); + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? categories = null, + Object? categoryId = freezed, + }) { + return _then(_$LoadedImpl( + null == categories + ? _value._categories + : categories // ignore: cast_nullable_to_non_nullable + as List, + freezed == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$LoadedImpl implements _Loaded { + const _$LoadedImpl(final List categories, this.categoryId) + : _categories = categories; + + final List _categories; + @override + List get categories { + if (_categories is EqualUnmodifiableListView) return _categories; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_categories); + } + + @override + final String? categoryId; + + @override + String toString() { + return 'CategoryLoaderState.loaded(categories: $categories, categoryId: $categoryId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoadedImpl && + const DeepCollectionEquality() + .equals(other._categories, _categories) && + (identical(other.categoryId, categoryId) || + other.categoryId == categoryId)); + } + + @override + int get hashCode => Object.hash(runtimeType, + const DeepCollectionEquality().hash(_categories), categoryId); + + /// Create a copy of CategoryLoaderState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function( + List categories, String? categoryId) + loaded, + required TResult Function(String message) error, + }) { + return loaded(categories, categoryId); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List categories, String? categoryId)? + loaded, + TResult? Function(String message)? error, + }) { + return loaded?.call(categories, categoryId); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List categories, String? categoryId)? + loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(categories, categoryId); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 CategoryLoaderState { + const factory _Loaded( + final List categories, final String? categoryId) = + _$LoadedImpl; + + List get categories; + String? get categoryId; + + /// Create a copy of CategoryLoaderState + /// 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 _$CategoryLoaderStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of CategoryLoaderState + /// 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 'CategoryLoaderState.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 CategoryLoaderState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function( + List categories, String? categoryId) + loaded, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List categories, String? categoryId)? + loaded, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List categories, String? categoryId)? + loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 CategoryLoaderState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/home/bloc/category_loader/category_loader_event.dart b/lib/presentation/home/bloc/category_loader/category_loader_event.dart new file mode 100644 index 0000000..8cf7464 --- /dev/null +++ b/lib/presentation/home/bloc/category_loader/category_loader_event.dart @@ -0,0 +1,8 @@ +part of 'category_loader_bloc.dart'; + +@freezed +class CategoryLoaderEvent with _$CategoryLoaderEvent { + const factory CategoryLoaderEvent.get() = _Get; + const factory CategoryLoaderEvent.setCategoryId(String categoryId) = + _SetCategoryId; +} diff --git a/lib/presentation/home/bloc/category_loader/category_loader_state.dart b/lib/presentation/home/bloc/category_loader/category_loader_state.dart new file mode 100644 index 0000000..da8f22f --- /dev/null +++ b/lib/presentation/home/bloc/category_loader/category_loader_state.dart @@ -0,0 +1,10 @@ +part of 'category_loader_bloc.dart'; + +@freezed +class CategoryLoaderState with _$CategoryLoaderState { + const factory CategoryLoaderState.initial() = _Initial; + const factory CategoryLoaderState.loading() = _Loading; + const factory CategoryLoaderState.loaded( + List categories, String? categoryId) = _Loaded; + const factory CategoryLoaderState.error(String message) = _Error; +} diff --git a/lib/presentation/home/bloc/checkout/checkout_bloc.dart b/lib/presentation/home/bloc/checkout/checkout_bloc.dart index 379be00..e08bfd0 100644 --- a/lib/presentation/home/bloc/checkout/checkout_bloc.dart +++ b/lib/presentation/home/bloc/checkout/checkout_bloc.dart @@ -2,8 +2,8 @@ import 'dart:developer'; import 'package:bloc/bloc.dart'; import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; +import 'package:enaklo_pos/data/models/response/delivery_response_model.dart'; import 'package:enaklo_pos/data/models/response/discount_response_model.dart'; -import 'package:enaklo_pos/presentation/home/models/order_item.dart'; import 'package:enaklo_pos/presentation/table/models/draft_order_item.dart'; import 'package:enaklo_pos/presentation/table/models/draft_order_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -20,20 +20,35 @@ part 'checkout_bloc.freezed.dart'; class CheckoutBloc extends Bloc { final SettingsLocalDatasource settingsLocalDatasource; - - CheckoutBloc({required this.settingsLocalDatasource}) : super(const CheckoutState.initial()) { + + CheckoutBloc({required this.settingsLocalDatasource}) + : super(const CheckoutState.initial()) { on<_AddItem>((event, emit) { var currentState = state as _Loaded; List items = [...currentState.items]; - var index = - items.indexWhere((element) => element.product.id == event.product.id); + var index = items.indexWhere( + (element) => + element.product.id == event.product.id && + element.variant?.id == event.variant?.id, + ); emit(const _Loading()); if (index != -1) { items[index] = ProductQuantity( - product: event.product, quantity: items[index].quantity + 1); + product: event.product, + quantity: items[index].quantity + 1, + variant: event.variant, + ); } else { - items.add(ProductQuantity(product: event.product, quantity: 1)); + items.add( + ProductQuantity( + product: event.product, + quantity: 1, + variant: event.variant, + ), + ); } + + log("Items: $items"); emit(_Loaded( items, currentState.discountModel, @@ -44,19 +59,26 @@ class CheckoutBloc extends Bloc { currentState.totalQuantity, currentState.totalPrice, currentState.draftName, - currentState.orderType)); + currentState.orderType, + currentState.deliveryType)); }); on<_RemoveItem>((event, emit) { var currentState = state as _Loaded; List items = [...currentState.items]; - var index = - items.indexWhere((element) => element.product.id == event.product.id); + var index = items.indexWhere( + (element) => + element.product.id == event.product.id && + element.variant?.id == event.variant?.id, + ); emit(const _Loading()); if (index != -1) { if (items[index].quantity > 1) { items[index] = ProductQuantity( - product: event.product, quantity: items[index].quantity - 1); + product: event.product, + quantity: items[index].quantity - 1, + variant: event.variant); + ; } else { items.removeAt(index); } @@ -71,7 +93,34 @@ class CheckoutBloc extends Bloc { currentState.totalQuantity, currentState.totalPrice, currentState.draftName, - currentState.orderType)); + currentState.orderType, + currentState.deliveryType)); + }); + + on<_DeleteItem>((event, emit) { + var currentState = state as _Loaded; + List items = [...currentState.items]; + var index = items.indexWhere( + (element) => + element.product.id == event.product.id && + element.variant?.id == event.variant?.id, + ); + emit(const _Loading()); + + items.removeAt(index); + + emit(_Loaded( + items, + currentState.discountModel, + currentState.discount, + currentState.discountAmount, + currentState.tax, + currentState.serviceCharge, + currentState.totalQuantity, + currentState.totalPrice, + currentState.draftName, + currentState.orderType, + currentState.deliveryType)); }); on<_Started>((event, emit) async { @@ -80,29 +129,42 @@ class CheckoutBloc extends Bloc { // Load tax and service charge from settings final tax = await settingsLocalDatasource.getTax(); final serviceCharge = await settingsLocalDatasource.getServiceCharge(); - - emit(_Loaded([], null, 0, 0, tax.value, serviceCharge, 0, 0, '', OrderType.dineIn)); + + emit(_Loaded( + [], + null, + 0, + 0, + tax.value, + serviceCharge, + 0, + 0, + '', + OrderType.dineIn, + null, + )); } catch (e) { // If loading fails, use default values log('Failed to load settings: $e'); - emit(const _Loaded([], null, 0, 0, 10, 5, 0, 0, '', OrderType.dineIn)); + emit(const _Loaded( + [], null, 0, 0, 10, 5, 0, 0, '', OrderType.dineIn, null)); } }); on<_AddDiscount>((event, emit) { var currentState = state as _Loaded; emit(_Loaded( - currentState.items, - event.discount, - currentState.discount, - currentState.discountAmount, - currentState.tax, - currentState.serviceCharge, - currentState.totalQuantity, - currentState.totalPrice, - currentState.draftName, - currentState.orderType, - )); + currentState.items, + event.discount, + currentState.discount, + currentState.discountAmount, + currentState.tax, + currentState.serviceCharge, + currentState.totalQuantity, + currentState.totalPrice, + currentState.draftName, + currentState.orderType, + currentState.deliveryType)); }); on<_RemoveDiscount>((event, emit) { @@ -117,7 +179,8 @@ class CheckoutBloc extends Bloc { currentState.totalQuantity, currentState.totalPrice, currentState.draftName, - currentState.orderType)); + currentState.orderType, + currentState.deliveryType)); }); on<_AddTax>((event, emit) { @@ -132,7 +195,8 @@ class CheckoutBloc extends Bloc { currentState.totalQuantity, currentState.totalPrice, currentState.draftName, - currentState.orderType)); + currentState.orderType, + currentState.deliveryType)); }); on<_AddServiceCharge>((event, emit) { @@ -148,6 +212,7 @@ class CheckoutBloc extends Bloc { currentState.totalPrice, currentState.draftName, currentState.orderType, + currentState.deliveryType, )); }); @@ -163,7 +228,8 @@ class CheckoutBloc extends Bloc { currentState.totalQuantity, currentState.totalPrice, currentState.draftName, - currentState.orderType)); + currentState.orderType, + currentState.deliveryType)); }); on<_RemoveServiceCharge>((event, emit) { @@ -178,7 +244,8 @@ class CheckoutBloc extends Bloc { currentState.totalQuantity, currentState.totalPrice, currentState.draftName, - currentState.orderType)); + currentState.orderType, + currentState.deliveryType)); }); on<_UpdateOrderType>((event, emit) { @@ -193,13 +260,15 @@ class CheckoutBloc extends Bloc { currentState.totalQuantity, currentState.totalPrice, currentState.draftName, - event.orderType)); + event.orderType, + currentState.deliveryType)); }); on<_UpdateItemNotes>((event, emit) { var currentState = state as _Loaded; List items = [...currentState.items]; - var index = items.indexWhere((element) => element.product.id == event.product.id); + var index = + items.indexWhere((element) => element.product.id == event.product.id); if (index != -1) { items[index] = items[index].copyWith(notes: event.notes); } @@ -213,7 +282,8 @@ class CheckoutBloc extends Bloc { currentState.totalQuantity, currentState.totalPrice, currentState.draftName, - currentState.orderType)); + currentState.orderType, + currentState.deliveryType)); }); on<_SaveDraftOrder>((event, emit) async { @@ -251,19 +321,38 @@ class CheckoutBloc extends Bloc { final draftOrder = event.data; log("draftOrder: ${draftOrder.toMap()}"); emit(_Loaded( - draftOrder.orders - .map((e) => - ProductQuantity(product: e.product, quantity: e.quantity)) - .toList(), - null, - draftOrder.discount, - draftOrder.discountAmount, - draftOrder.tax, - draftOrder.serviceCharge, - draftOrder.totalQuantity, - draftOrder.totalPrice, - draftOrder.draftName, - OrderType.dineIn)); + draftOrder.orders + .map((e) => + ProductQuantity(product: e.product, quantity: e.quantity)) + .toList(), + null, + draftOrder.discount, + draftOrder.discountAmount, + draftOrder.tax, + draftOrder.serviceCharge, + draftOrder.totalQuantity, + draftOrder.totalPrice, + draftOrder.draftName, + OrderType.dineIn, + null, + )); + }); + + on<_UpdateDeliveryType>((event, emit) { + var currentState = state as _Loaded; + emit(_Loaded( + currentState.items, + currentState.discountModel, + currentState.discount, + currentState.discountAmount, + currentState.tax, + currentState.serviceCharge, + currentState.totalQuantity, + currentState.totalPrice, + currentState.draftName, + currentState.orderType, + event.delivery, + )); }); } } diff --git a/lib/presentation/home/bloc/checkout/checkout_bloc.freezed.dart b/lib/presentation/home/bloc/checkout/checkout_bloc.freezed.dart index 30980f3..45934a1 100644 --- a/lib/presentation/home/bloc/checkout/checkout_bloc.freezed.dart +++ b/lib/presentation/home/bloc/checkout/checkout_bloc.freezed.dart @@ -19,8 +19,11 @@ mixin _$CheckoutEvent { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product) addItem, - required TResult Function(Product product) removeItem, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(Product product, ProductVariant? variant) + deleteItem, required TResult Function(Discount discount) addDiscount, required TResult Function() removeDiscount, required TResult Function(int tax) addTax, @@ -33,13 +36,15 @@ mixin _$CheckoutEvent { int tableNumber, String draftName, int discountAmount) saveDraftOrder, required TResult Function(DraftOrderModel data) loadDraftOrder, + required TResult Function(DeliveryModel delivery) updateDeliveryType, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product)? addItem, - TResult? Function(Product product)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Discount discount)? addDiscount, TResult? Function()? removeDiscount, TResult? Function(int tax)? addTax, @@ -51,13 +56,15 @@ mixin _$CheckoutEvent { TResult? Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult? Function(DraftOrderModel data)? loadDraftOrder, + TResult? Function(DeliveryModel delivery)? updateDeliveryType, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product)? addItem, - TResult Function(Product product)? removeItem, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Discount discount)? addDiscount, TResult Function()? removeDiscount, TResult Function(int tax)? addTax, @@ -69,6 +76,7 @@ mixin _$CheckoutEvent { TResult Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult Function(DraftOrderModel data)? loadDraftOrder, + TResult Function(DeliveryModel delivery)? updateDeliveryType, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -77,6 +85,7 @@ mixin _$CheckoutEvent { required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_DeleteItem value) deleteItem, required TResult Function(_AddDiscount value) addDiscount, required TResult Function(_RemoveDiscount value) removeDiscount, required TResult Function(_AddTax value) addTax, @@ -87,6 +96,7 @@ mixin _$CheckoutEvent { required TResult Function(_UpdateItemNotes value) updateItemNotes, required TResult Function(_SaveDraftOrder value) saveDraftOrder, required TResult Function(_LoadDraftOrder value) loadDraftOrder, + required TResult Function(_UpdateDeliveryType value) updateDeliveryType, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -94,6 +104,7 @@ mixin _$CheckoutEvent { TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_DeleteItem value)? deleteItem, TResult? Function(_AddDiscount value)? addDiscount, TResult? Function(_RemoveDiscount value)? removeDiscount, TResult? Function(_AddTax value)? addTax, @@ -104,6 +115,7 @@ mixin _$CheckoutEvent { TResult? Function(_UpdateItemNotes value)? updateItemNotes, TResult? Function(_SaveDraftOrder value)? saveDraftOrder, TResult? Function(_LoadDraftOrder value)? loadDraftOrder, + TResult? Function(_UpdateDeliveryType value)? updateDeliveryType, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -111,6 +123,7 @@ mixin _$CheckoutEvent { TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, TResult Function(_RemoveItem value)? removeItem, + TResult Function(_DeleteItem value)? deleteItem, TResult Function(_AddDiscount value)? addDiscount, TResult Function(_RemoveDiscount value)? removeDiscount, TResult Function(_AddTax value)? addTax, @@ -121,6 +134,7 @@ mixin _$CheckoutEvent { TResult Function(_UpdateItemNotes value)? updateItemNotes, TResult Function(_SaveDraftOrder value)? saveDraftOrder, TResult Function(_LoadDraftOrder value)? loadDraftOrder, + TResult Function(_UpdateDeliveryType value)? updateDeliveryType, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -189,8 +203,11 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product) addItem, - required TResult Function(Product product) removeItem, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(Product product, ProductVariant? variant) + deleteItem, required TResult Function(Discount discount) addDiscount, required TResult Function() removeDiscount, required TResult Function(int tax) addTax, @@ -203,6 +220,7 @@ class _$StartedImpl implements _Started { int tableNumber, String draftName, int discountAmount) saveDraftOrder, required TResult Function(DraftOrderModel data) loadDraftOrder, + required TResult Function(DeliveryModel delivery) updateDeliveryType, }) { return started(); } @@ -211,8 +229,9 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product)? addItem, - TResult? Function(Product product)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Discount discount)? addDiscount, TResult? Function()? removeDiscount, TResult? Function(int tax)? addTax, @@ -224,6 +243,7 @@ class _$StartedImpl implements _Started { TResult? Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult? Function(DraftOrderModel data)? loadDraftOrder, + TResult? Function(DeliveryModel delivery)? updateDeliveryType, }) { return started?.call(); } @@ -232,8 +252,9 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product)? addItem, - TResult Function(Product product)? removeItem, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Discount discount)? addDiscount, TResult Function()? removeDiscount, TResult Function(int tax)? addTax, @@ -245,6 +266,7 @@ class _$StartedImpl implements _Started { TResult Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult Function(DraftOrderModel data)? loadDraftOrder, + TResult Function(DeliveryModel delivery)? updateDeliveryType, required TResult orElse(), }) { if (started != null) { @@ -259,6 +281,7 @@ class _$StartedImpl implements _Started { required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_DeleteItem value) deleteItem, required TResult Function(_AddDiscount value) addDiscount, required TResult Function(_RemoveDiscount value) removeDiscount, required TResult Function(_AddTax value) addTax, @@ -269,6 +292,7 @@ class _$StartedImpl implements _Started { required TResult Function(_UpdateItemNotes value) updateItemNotes, required TResult Function(_SaveDraftOrder value) saveDraftOrder, required TResult Function(_LoadDraftOrder value) loadDraftOrder, + required TResult Function(_UpdateDeliveryType value) updateDeliveryType, }) { return started(this); } @@ -279,6 +303,7 @@ class _$StartedImpl implements _Started { TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_DeleteItem value)? deleteItem, TResult? Function(_AddDiscount value)? addDiscount, TResult? Function(_RemoveDiscount value)? removeDiscount, TResult? Function(_AddTax value)? addTax, @@ -289,6 +314,7 @@ class _$StartedImpl implements _Started { TResult? Function(_UpdateItemNotes value)? updateItemNotes, TResult? Function(_SaveDraftOrder value)? saveDraftOrder, TResult? Function(_LoadDraftOrder value)? loadDraftOrder, + TResult? Function(_UpdateDeliveryType value)? updateDeliveryType, }) { return started?.call(this); } @@ -299,6 +325,7 @@ class _$StartedImpl implements _Started { TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, TResult Function(_RemoveItem value)? removeItem, + TResult Function(_DeleteItem value)? deleteItem, TResult Function(_AddDiscount value)? addDiscount, TResult Function(_RemoveDiscount value)? removeDiscount, TResult Function(_AddTax value)? addTax, @@ -309,6 +336,7 @@ class _$StartedImpl implements _Started { TResult Function(_UpdateItemNotes value)? updateItemNotes, TResult Function(_SaveDraftOrder value)? saveDraftOrder, TResult Function(_LoadDraftOrder value)? loadDraftOrder, + TResult Function(_UpdateDeliveryType value)? updateDeliveryType, required TResult orElse(), }) { if (started != null) { @@ -328,7 +356,7 @@ abstract class _$$AddItemImplCopyWith<$Res> { _$AddItemImpl value, $Res Function(_$AddItemImpl) then) = __$$AddItemImplCopyWithImpl<$Res>; @useResult - $Res call({Product product}); + $Res call({Product product, ProductVariant? variant}); } /// @nodoc @@ -345,12 +373,17 @@ class __$$AddItemImplCopyWithImpl<$Res> @override $Res call({ Object? product = null, + Object? variant = freezed, }) { return _then(_$AddItemImpl( null == product ? _value.product : product // ignore: cast_nullable_to_non_nullable as Product, + freezed == variant + ? _value.variant + : variant // ignore: cast_nullable_to_non_nullable + as ProductVariant?, )); } } @@ -358,14 +391,16 @@ class __$$AddItemImplCopyWithImpl<$Res> /// @nodoc class _$AddItemImpl implements _AddItem { - const _$AddItemImpl(this.product); + const _$AddItemImpl(this.product, this.variant); @override final Product product; + @override + final ProductVariant? variant; @override String toString() { - return 'CheckoutEvent.addItem(product: $product)'; + return 'CheckoutEvent.addItem(product: $product, variant: $variant)'; } @override @@ -373,11 +408,12 @@ class _$AddItemImpl implements _AddItem { return identical(this, other) || (other.runtimeType == runtimeType && other is _$AddItemImpl && - (identical(other.product, product) || other.product == product)); + (identical(other.product, product) || other.product == product) && + (identical(other.variant, variant) || other.variant == variant)); } @override - int get hashCode => Object.hash(runtimeType, product); + int get hashCode => Object.hash(runtimeType, product, variant); /// Create a copy of CheckoutEvent /// with the given fields replaced by the non-null parameter values. @@ -391,8 +427,11 @@ class _$AddItemImpl implements _AddItem { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product) addItem, - required TResult Function(Product product) removeItem, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(Product product, ProductVariant? variant) + deleteItem, required TResult Function(Discount discount) addDiscount, required TResult Function() removeDiscount, required TResult Function(int tax) addTax, @@ -405,16 +444,18 @@ class _$AddItemImpl implements _AddItem { int tableNumber, String draftName, int discountAmount) saveDraftOrder, required TResult Function(DraftOrderModel data) loadDraftOrder, + required TResult Function(DeliveryModel delivery) updateDeliveryType, }) { - return addItem(product); + return addItem(product, variant); } @override @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product)? addItem, - TResult? Function(Product product)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Discount discount)? addDiscount, TResult? Function()? removeDiscount, TResult? Function(int tax)? addTax, @@ -426,16 +467,18 @@ class _$AddItemImpl implements _AddItem { TResult? Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult? Function(DraftOrderModel data)? loadDraftOrder, + TResult? Function(DeliveryModel delivery)? updateDeliveryType, }) { - return addItem?.call(product); + return addItem?.call(product, variant); } @override @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product)? addItem, - TResult Function(Product product)? removeItem, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Discount discount)? addDiscount, TResult Function()? removeDiscount, TResult Function(int tax)? addTax, @@ -447,10 +490,11 @@ class _$AddItemImpl implements _AddItem { TResult Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult Function(DraftOrderModel data)? loadDraftOrder, + TResult Function(DeliveryModel delivery)? updateDeliveryType, required TResult orElse(), }) { if (addItem != null) { - return addItem(product); + return addItem(product, variant); } return orElse(); } @@ -461,6 +505,7 @@ class _$AddItemImpl implements _AddItem { required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_DeleteItem value) deleteItem, required TResult Function(_AddDiscount value) addDiscount, required TResult Function(_RemoveDiscount value) removeDiscount, required TResult Function(_AddTax value) addTax, @@ -471,6 +516,7 @@ class _$AddItemImpl implements _AddItem { required TResult Function(_UpdateItemNotes value) updateItemNotes, required TResult Function(_SaveDraftOrder value) saveDraftOrder, required TResult Function(_LoadDraftOrder value) loadDraftOrder, + required TResult Function(_UpdateDeliveryType value) updateDeliveryType, }) { return addItem(this); } @@ -481,6 +527,7 @@ class _$AddItemImpl implements _AddItem { TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_DeleteItem value)? deleteItem, TResult? Function(_AddDiscount value)? addDiscount, TResult? Function(_RemoveDiscount value)? removeDiscount, TResult? Function(_AddTax value)? addTax, @@ -491,6 +538,7 @@ class _$AddItemImpl implements _AddItem { TResult? Function(_UpdateItemNotes value)? updateItemNotes, TResult? Function(_SaveDraftOrder value)? saveDraftOrder, TResult? Function(_LoadDraftOrder value)? loadDraftOrder, + TResult? Function(_UpdateDeliveryType value)? updateDeliveryType, }) { return addItem?.call(this); } @@ -501,6 +549,7 @@ class _$AddItemImpl implements _AddItem { TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, TResult Function(_RemoveItem value)? removeItem, + TResult Function(_DeleteItem value)? deleteItem, TResult Function(_AddDiscount value)? addDiscount, TResult Function(_RemoveDiscount value)? removeDiscount, TResult Function(_AddTax value)? addTax, @@ -511,6 +560,7 @@ class _$AddItemImpl implements _AddItem { TResult Function(_UpdateItemNotes value)? updateItemNotes, TResult Function(_SaveDraftOrder value)? saveDraftOrder, TResult Function(_LoadDraftOrder value)? loadDraftOrder, + TResult Function(_UpdateDeliveryType value)? updateDeliveryType, required TResult orElse(), }) { if (addItem != null) { @@ -521,9 +571,11 @@ class _$AddItemImpl implements _AddItem { } abstract class _AddItem implements CheckoutEvent { - const factory _AddItem(final Product product) = _$AddItemImpl; + const factory _AddItem(final Product product, final ProductVariant? variant) = + _$AddItemImpl; Product get product; + ProductVariant? get variant; /// Create a copy of CheckoutEvent /// with the given fields replaced by the non-null parameter values. @@ -538,7 +590,7 @@ abstract class _$$RemoveItemImplCopyWith<$Res> { _$RemoveItemImpl value, $Res Function(_$RemoveItemImpl) then) = __$$RemoveItemImplCopyWithImpl<$Res>; @useResult - $Res call({Product product}); + $Res call({Product product, ProductVariant? variant}); } /// @nodoc @@ -555,12 +607,17 @@ class __$$RemoveItemImplCopyWithImpl<$Res> @override $Res call({ Object? product = null, + Object? variant = freezed, }) { return _then(_$RemoveItemImpl( null == product ? _value.product : product // ignore: cast_nullable_to_non_nullable as Product, + freezed == variant + ? _value.variant + : variant // ignore: cast_nullable_to_non_nullable + as ProductVariant?, )); } } @@ -568,14 +625,16 @@ class __$$RemoveItemImplCopyWithImpl<$Res> /// @nodoc class _$RemoveItemImpl implements _RemoveItem { - const _$RemoveItemImpl(this.product); + const _$RemoveItemImpl(this.product, this.variant); @override final Product product; + @override + final ProductVariant? variant; @override String toString() { - return 'CheckoutEvent.removeItem(product: $product)'; + return 'CheckoutEvent.removeItem(product: $product, variant: $variant)'; } @override @@ -583,11 +642,12 @@ class _$RemoveItemImpl implements _RemoveItem { return identical(this, other) || (other.runtimeType == runtimeType && other is _$RemoveItemImpl && - (identical(other.product, product) || other.product == product)); + (identical(other.product, product) || other.product == product) && + (identical(other.variant, variant) || other.variant == variant)); } @override - int get hashCode => Object.hash(runtimeType, product); + int get hashCode => Object.hash(runtimeType, product, variant); /// Create a copy of CheckoutEvent /// with the given fields replaced by the non-null parameter values. @@ -601,8 +661,11 @@ class _$RemoveItemImpl implements _RemoveItem { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product) addItem, - required TResult Function(Product product) removeItem, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(Product product, ProductVariant? variant) + deleteItem, required TResult Function(Discount discount) addDiscount, required TResult Function() removeDiscount, required TResult Function(int tax) addTax, @@ -615,16 +678,18 @@ class _$RemoveItemImpl implements _RemoveItem { int tableNumber, String draftName, int discountAmount) saveDraftOrder, required TResult Function(DraftOrderModel data) loadDraftOrder, + required TResult Function(DeliveryModel delivery) updateDeliveryType, }) { - return removeItem(product); + return removeItem(product, variant); } @override @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product)? addItem, - TResult? Function(Product product)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Discount discount)? addDiscount, TResult? Function()? removeDiscount, TResult? Function(int tax)? addTax, @@ -636,16 +701,18 @@ class _$RemoveItemImpl implements _RemoveItem { TResult? Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult? Function(DraftOrderModel data)? loadDraftOrder, + TResult? Function(DeliveryModel delivery)? updateDeliveryType, }) { - return removeItem?.call(product); + return removeItem?.call(product, variant); } @override @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product)? addItem, - TResult Function(Product product)? removeItem, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Discount discount)? addDiscount, TResult Function()? removeDiscount, TResult Function(int tax)? addTax, @@ -657,10 +724,11 @@ class _$RemoveItemImpl implements _RemoveItem { TResult Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult Function(DraftOrderModel data)? loadDraftOrder, + TResult Function(DeliveryModel delivery)? updateDeliveryType, required TResult orElse(), }) { if (removeItem != null) { - return removeItem(product); + return removeItem(product, variant); } return orElse(); } @@ -671,6 +739,7 @@ class _$RemoveItemImpl implements _RemoveItem { required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_DeleteItem value) deleteItem, required TResult Function(_AddDiscount value) addDiscount, required TResult Function(_RemoveDiscount value) removeDiscount, required TResult Function(_AddTax value) addTax, @@ -681,6 +750,7 @@ class _$RemoveItemImpl implements _RemoveItem { required TResult Function(_UpdateItemNotes value) updateItemNotes, required TResult Function(_SaveDraftOrder value) saveDraftOrder, required TResult Function(_LoadDraftOrder value) loadDraftOrder, + required TResult Function(_UpdateDeliveryType value) updateDeliveryType, }) { return removeItem(this); } @@ -691,6 +761,7 @@ class _$RemoveItemImpl implements _RemoveItem { TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_DeleteItem value)? deleteItem, TResult? Function(_AddDiscount value)? addDiscount, TResult? Function(_RemoveDiscount value)? removeDiscount, TResult? Function(_AddTax value)? addTax, @@ -701,6 +772,7 @@ class _$RemoveItemImpl implements _RemoveItem { TResult? Function(_UpdateItemNotes value)? updateItemNotes, TResult? Function(_SaveDraftOrder value)? saveDraftOrder, TResult? Function(_LoadDraftOrder value)? loadDraftOrder, + TResult? Function(_UpdateDeliveryType value)? updateDeliveryType, }) { return removeItem?.call(this); } @@ -711,6 +783,7 @@ class _$RemoveItemImpl implements _RemoveItem { TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, TResult Function(_RemoveItem value)? removeItem, + TResult Function(_DeleteItem value)? deleteItem, TResult Function(_AddDiscount value)? addDiscount, TResult Function(_RemoveDiscount value)? removeDiscount, TResult Function(_AddTax value)? addTax, @@ -721,6 +794,7 @@ class _$RemoveItemImpl implements _RemoveItem { TResult Function(_UpdateItemNotes value)? updateItemNotes, TResult Function(_SaveDraftOrder value)? saveDraftOrder, TResult Function(_LoadDraftOrder value)? loadDraftOrder, + TResult Function(_UpdateDeliveryType value)? updateDeliveryType, required TResult orElse(), }) { if (removeItem != null) { @@ -731,9 +805,11 @@ class _$RemoveItemImpl implements _RemoveItem { } abstract class _RemoveItem implements CheckoutEvent { - const factory _RemoveItem(final Product product) = _$RemoveItemImpl; + const factory _RemoveItem( + final Product product, final ProductVariant? variant) = _$RemoveItemImpl; Product get product; + ProductVariant? get variant; /// Create a copy of CheckoutEvent /// with the given fields replaced by the non-null parameter values. @@ -742,6 +818,240 @@ abstract class _RemoveItem implements CheckoutEvent { throw _privateConstructorUsedError; } +/// @nodoc +abstract class _$$DeleteItemImplCopyWith<$Res> { + factory _$$DeleteItemImplCopyWith( + _$DeleteItemImpl value, $Res Function(_$DeleteItemImpl) then) = + __$$DeleteItemImplCopyWithImpl<$Res>; + @useResult + $Res call({Product product, ProductVariant? variant}); +} + +/// @nodoc +class __$$DeleteItemImplCopyWithImpl<$Res> + extends _$CheckoutEventCopyWithImpl<$Res, _$DeleteItemImpl> + implements _$$DeleteItemImplCopyWith<$Res> { + __$$DeleteItemImplCopyWithImpl( + _$DeleteItemImpl _value, $Res Function(_$DeleteItemImpl) _then) + : super(_value, _then); + + /// Create a copy of CheckoutEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? product = null, + Object? variant = freezed, + }) { + return _then(_$DeleteItemImpl( + null == product + ? _value.product + : product // ignore: cast_nullable_to_non_nullable + as Product, + freezed == variant + ? _value.variant + : variant // ignore: cast_nullable_to_non_nullable + as ProductVariant?, + )); + } +} + +/// @nodoc + +class _$DeleteItemImpl implements _DeleteItem { + const _$DeleteItemImpl(this.product, this.variant); + + @override + final Product product; + @override + final ProductVariant? variant; + + @override + String toString() { + return 'CheckoutEvent.deleteItem(product: $product, variant: $variant)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DeleteItemImpl && + (identical(other.product, product) || other.product == product) && + (identical(other.variant, variant) || other.variant == variant)); + } + + @override + int get hashCode => Object.hash(runtimeType, product, variant); + + /// 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') + _$$DeleteItemImplCopyWith<_$DeleteItemImpl> get copyWith => + __$$DeleteItemImplCopyWithImpl<_$DeleteItemImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() started, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(Product product, ProductVariant? variant) + deleteItem, + required TResult Function(Discount discount) addDiscount, + required TResult Function() removeDiscount, + required TResult Function(int tax) addTax, + required TResult Function(int serviceCharge) addServiceCharge, + required TResult Function() removeTax, + required TResult Function() removeServiceCharge, + required TResult Function(OrderType orderType) updateOrderType, + required TResult Function(Product product, String notes) updateItemNotes, + required TResult Function( + int tableNumber, String draftName, int discountAmount) + saveDraftOrder, + required TResult Function(DraftOrderModel data) loadDraftOrder, + required TResult Function(DeliveryModel delivery) updateDeliveryType, + }) { + return deleteItem(product, variant); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? started, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? deleteItem, + TResult? Function(Discount discount)? addDiscount, + TResult? Function()? removeDiscount, + TResult? Function(int tax)? addTax, + TResult? Function(int serviceCharge)? addServiceCharge, + TResult? Function()? removeTax, + TResult? Function()? removeServiceCharge, + TResult? Function(OrderType orderType)? updateOrderType, + TResult? Function(Product product, String notes)? updateItemNotes, + TResult? Function(int tableNumber, String draftName, int discountAmount)? + saveDraftOrder, + TResult? Function(DraftOrderModel data)? loadDraftOrder, + TResult? Function(DeliveryModel delivery)? updateDeliveryType, + }) { + return deleteItem?.call(product, variant); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? started, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(Product product, ProductVariant? variant)? deleteItem, + TResult Function(Discount discount)? addDiscount, + TResult Function()? removeDiscount, + TResult Function(int tax)? addTax, + TResult Function(int serviceCharge)? addServiceCharge, + TResult Function()? removeTax, + TResult Function()? removeServiceCharge, + TResult Function(OrderType orderType)? updateOrderType, + TResult Function(Product product, String notes)? updateItemNotes, + TResult Function(int tableNumber, String draftName, int discountAmount)? + saveDraftOrder, + TResult Function(DraftOrderModel data)? loadDraftOrder, + TResult Function(DeliveryModel delivery)? updateDeliveryType, + required TResult orElse(), + }) { + if (deleteItem != null) { + return deleteItem(product, variant); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Started value) started, + required TResult Function(_AddItem value) addItem, + required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_DeleteItem value) deleteItem, + required TResult Function(_AddDiscount value) addDiscount, + required TResult Function(_RemoveDiscount value) removeDiscount, + required TResult Function(_AddTax value) addTax, + required TResult Function(_AddServiceCharge value) addServiceCharge, + required TResult Function(_RemoveTax value) removeTax, + required TResult Function(_RemoveServiceCharge value) removeServiceCharge, + required TResult Function(_UpdateOrderType value) updateOrderType, + required TResult Function(_UpdateItemNotes value) updateItemNotes, + required TResult Function(_SaveDraftOrder value) saveDraftOrder, + required TResult Function(_LoadDraftOrder value) loadDraftOrder, + required TResult Function(_UpdateDeliveryType value) updateDeliveryType, + }) { + return deleteItem(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Started value)? started, + TResult? Function(_AddItem value)? addItem, + TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_DeleteItem value)? deleteItem, + TResult? Function(_AddDiscount value)? addDiscount, + TResult? Function(_RemoveDiscount value)? removeDiscount, + TResult? Function(_AddTax value)? addTax, + TResult? Function(_AddServiceCharge value)? addServiceCharge, + TResult? Function(_RemoveTax value)? removeTax, + TResult? Function(_RemoveServiceCharge value)? removeServiceCharge, + TResult? Function(_UpdateOrderType value)? updateOrderType, + TResult? Function(_UpdateItemNotes value)? updateItemNotes, + TResult? Function(_SaveDraftOrder value)? saveDraftOrder, + TResult? Function(_LoadDraftOrder value)? loadDraftOrder, + TResult? Function(_UpdateDeliveryType value)? updateDeliveryType, + }) { + return deleteItem?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Started value)? started, + TResult Function(_AddItem value)? addItem, + TResult Function(_RemoveItem value)? removeItem, + TResult Function(_DeleteItem value)? deleteItem, + TResult Function(_AddDiscount value)? addDiscount, + TResult Function(_RemoveDiscount value)? removeDiscount, + TResult Function(_AddTax value)? addTax, + TResult Function(_AddServiceCharge value)? addServiceCharge, + TResult Function(_RemoveTax value)? removeTax, + TResult Function(_RemoveServiceCharge value)? removeServiceCharge, + TResult Function(_UpdateOrderType value)? updateOrderType, + TResult Function(_UpdateItemNotes value)? updateItemNotes, + TResult Function(_SaveDraftOrder value)? saveDraftOrder, + TResult Function(_LoadDraftOrder value)? loadDraftOrder, + TResult Function(_UpdateDeliveryType value)? updateDeliveryType, + required TResult orElse(), + }) { + if (deleteItem != null) { + return deleteItem(this); + } + return orElse(); + } +} + +abstract class _DeleteItem implements CheckoutEvent { + const factory _DeleteItem( + final Product product, final ProductVariant? variant) = _$DeleteItemImpl; + + Product get product; + ProductVariant? get variant; + + /// Create a copy of CheckoutEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$DeleteItemImplCopyWith<_$DeleteItemImpl> get copyWith => + throw _privateConstructorUsedError; +} + /// @nodoc abstract class _$$AddDiscountImplCopyWith<$Res> { factory _$$AddDiscountImplCopyWith( @@ -812,8 +1122,11 @@ class _$AddDiscountImpl implements _AddDiscount { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product) addItem, - required TResult Function(Product product) removeItem, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(Product product, ProductVariant? variant) + deleteItem, required TResult Function(Discount discount) addDiscount, required TResult Function() removeDiscount, required TResult Function(int tax) addTax, @@ -826,6 +1139,7 @@ class _$AddDiscountImpl implements _AddDiscount { int tableNumber, String draftName, int discountAmount) saveDraftOrder, required TResult Function(DraftOrderModel data) loadDraftOrder, + required TResult Function(DeliveryModel delivery) updateDeliveryType, }) { return addDiscount(discount); } @@ -834,8 +1148,9 @@ class _$AddDiscountImpl implements _AddDiscount { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product)? addItem, - TResult? Function(Product product)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Discount discount)? addDiscount, TResult? Function()? removeDiscount, TResult? Function(int tax)? addTax, @@ -847,6 +1162,7 @@ class _$AddDiscountImpl implements _AddDiscount { TResult? Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult? Function(DraftOrderModel data)? loadDraftOrder, + TResult? Function(DeliveryModel delivery)? updateDeliveryType, }) { return addDiscount?.call(discount); } @@ -855,8 +1171,9 @@ class _$AddDiscountImpl implements _AddDiscount { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product)? addItem, - TResult Function(Product product)? removeItem, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Discount discount)? addDiscount, TResult Function()? removeDiscount, TResult Function(int tax)? addTax, @@ -868,6 +1185,7 @@ class _$AddDiscountImpl implements _AddDiscount { TResult Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult Function(DraftOrderModel data)? loadDraftOrder, + TResult Function(DeliveryModel delivery)? updateDeliveryType, required TResult orElse(), }) { if (addDiscount != null) { @@ -882,6 +1200,7 @@ class _$AddDiscountImpl implements _AddDiscount { required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_DeleteItem value) deleteItem, required TResult Function(_AddDiscount value) addDiscount, required TResult Function(_RemoveDiscount value) removeDiscount, required TResult Function(_AddTax value) addTax, @@ -892,6 +1211,7 @@ class _$AddDiscountImpl implements _AddDiscount { required TResult Function(_UpdateItemNotes value) updateItemNotes, required TResult Function(_SaveDraftOrder value) saveDraftOrder, required TResult Function(_LoadDraftOrder value) loadDraftOrder, + required TResult Function(_UpdateDeliveryType value) updateDeliveryType, }) { return addDiscount(this); } @@ -902,6 +1222,7 @@ class _$AddDiscountImpl implements _AddDiscount { TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_DeleteItem value)? deleteItem, TResult? Function(_AddDiscount value)? addDiscount, TResult? Function(_RemoveDiscount value)? removeDiscount, TResult? Function(_AddTax value)? addTax, @@ -912,6 +1233,7 @@ class _$AddDiscountImpl implements _AddDiscount { TResult? Function(_UpdateItemNotes value)? updateItemNotes, TResult? Function(_SaveDraftOrder value)? saveDraftOrder, TResult? Function(_LoadDraftOrder value)? loadDraftOrder, + TResult? Function(_UpdateDeliveryType value)? updateDeliveryType, }) { return addDiscount?.call(this); } @@ -922,6 +1244,7 @@ class _$AddDiscountImpl implements _AddDiscount { TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, TResult Function(_RemoveItem value)? removeItem, + TResult Function(_DeleteItem value)? deleteItem, TResult Function(_AddDiscount value)? addDiscount, TResult Function(_RemoveDiscount value)? removeDiscount, TResult Function(_AddTax value)? addTax, @@ -932,6 +1255,7 @@ class _$AddDiscountImpl implements _AddDiscount { TResult Function(_UpdateItemNotes value)? updateItemNotes, TResult Function(_SaveDraftOrder value)? saveDraftOrder, TResult Function(_LoadDraftOrder value)? loadDraftOrder, + TResult Function(_UpdateDeliveryType value)? updateDeliveryType, required TResult orElse(), }) { if (addDiscount != null) { @@ -995,8 +1319,11 @@ class _$RemoveDiscountImpl implements _RemoveDiscount { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product) addItem, - required TResult Function(Product product) removeItem, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(Product product, ProductVariant? variant) + deleteItem, required TResult Function(Discount discount) addDiscount, required TResult Function() removeDiscount, required TResult Function(int tax) addTax, @@ -1009,6 +1336,7 @@ class _$RemoveDiscountImpl implements _RemoveDiscount { int tableNumber, String draftName, int discountAmount) saveDraftOrder, required TResult Function(DraftOrderModel data) loadDraftOrder, + required TResult Function(DeliveryModel delivery) updateDeliveryType, }) { return removeDiscount(); } @@ -1017,8 +1345,9 @@ class _$RemoveDiscountImpl implements _RemoveDiscount { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product)? addItem, - TResult? Function(Product product)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Discount discount)? addDiscount, TResult? Function()? removeDiscount, TResult? Function(int tax)? addTax, @@ -1030,6 +1359,7 @@ class _$RemoveDiscountImpl implements _RemoveDiscount { TResult? Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult? Function(DraftOrderModel data)? loadDraftOrder, + TResult? Function(DeliveryModel delivery)? updateDeliveryType, }) { return removeDiscount?.call(); } @@ -1038,8 +1368,9 @@ class _$RemoveDiscountImpl implements _RemoveDiscount { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product)? addItem, - TResult Function(Product product)? removeItem, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Discount discount)? addDiscount, TResult Function()? removeDiscount, TResult Function(int tax)? addTax, @@ -1051,6 +1382,7 @@ class _$RemoveDiscountImpl implements _RemoveDiscount { TResult Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult Function(DraftOrderModel data)? loadDraftOrder, + TResult Function(DeliveryModel delivery)? updateDeliveryType, required TResult orElse(), }) { if (removeDiscount != null) { @@ -1065,6 +1397,7 @@ class _$RemoveDiscountImpl implements _RemoveDiscount { required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_DeleteItem value) deleteItem, required TResult Function(_AddDiscount value) addDiscount, required TResult Function(_RemoveDiscount value) removeDiscount, required TResult Function(_AddTax value) addTax, @@ -1075,6 +1408,7 @@ class _$RemoveDiscountImpl implements _RemoveDiscount { required TResult Function(_UpdateItemNotes value) updateItemNotes, required TResult Function(_SaveDraftOrder value) saveDraftOrder, required TResult Function(_LoadDraftOrder value) loadDraftOrder, + required TResult Function(_UpdateDeliveryType value) updateDeliveryType, }) { return removeDiscount(this); } @@ -1085,6 +1419,7 @@ class _$RemoveDiscountImpl implements _RemoveDiscount { TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_DeleteItem value)? deleteItem, TResult? Function(_AddDiscount value)? addDiscount, TResult? Function(_RemoveDiscount value)? removeDiscount, TResult? Function(_AddTax value)? addTax, @@ -1095,6 +1430,7 @@ class _$RemoveDiscountImpl implements _RemoveDiscount { TResult? Function(_UpdateItemNotes value)? updateItemNotes, TResult? Function(_SaveDraftOrder value)? saveDraftOrder, TResult? Function(_LoadDraftOrder value)? loadDraftOrder, + TResult? Function(_UpdateDeliveryType value)? updateDeliveryType, }) { return removeDiscount?.call(this); } @@ -1105,6 +1441,7 @@ class _$RemoveDiscountImpl implements _RemoveDiscount { TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, TResult Function(_RemoveItem value)? removeItem, + TResult Function(_DeleteItem value)? deleteItem, TResult Function(_AddDiscount value)? addDiscount, TResult Function(_RemoveDiscount value)? removeDiscount, TResult Function(_AddTax value)? addTax, @@ -1115,6 +1452,7 @@ class _$RemoveDiscountImpl implements _RemoveDiscount { TResult Function(_UpdateItemNotes value)? updateItemNotes, TResult Function(_SaveDraftOrder value)? saveDraftOrder, TResult Function(_LoadDraftOrder value)? loadDraftOrder, + TResult Function(_UpdateDeliveryType value)? updateDeliveryType, required TResult orElse(), }) { if (removeDiscount != null) { @@ -1197,8 +1535,11 @@ class _$AddTaxImpl implements _AddTax { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product) addItem, - required TResult Function(Product product) removeItem, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(Product product, ProductVariant? variant) + deleteItem, required TResult Function(Discount discount) addDiscount, required TResult Function() removeDiscount, required TResult Function(int tax) addTax, @@ -1211,6 +1552,7 @@ class _$AddTaxImpl implements _AddTax { int tableNumber, String draftName, int discountAmount) saveDraftOrder, required TResult Function(DraftOrderModel data) loadDraftOrder, + required TResult Function(DeliveryModel delivery) updateDeliveryType, }) { return addTax(tax); } @@ -1219,8 +1561,9 @@ class _$AddTaxImpl implements _AddTax { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product)? addItem, - TResult? Function(Product product)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Discount discount)? addDiscount, TResult? Function()? removeDiscount, TResult? Function(int tax)? addTax, @@ -1232,6 +1575,7 @@ class _$AddTaxImpl implements _AddTax { TResult? Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult? Function(DraftOrderModel data)? loadDraftOrder, + TResult? Function(DeliveryModel delivery)? updateDeliveryType, }) { return addTax?.call(tax); } @@ -1240,8 +1584,9 @@ class _$AddTaxImpl implements _AddTax { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product)? addItem, - TResult Function(Product product)? removeItem, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Discount discount)? addDiscount, TResult Function()? removeDiscount, TResult Function(int tax)? addTax, @@ -1253,6 +1598,7 @@ class _$AddTaxImpl implements _AddTax { TResult Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult Function(DraftOrderModel data)? loadDraftOrder, + TResult Function(DeliveryModel delivery)? updateDeliveryType, required TResult orElse(), }) { if (addTax != null) { @@ -1267,6 +1613,7 @@ class _$AddTaxImpl implements _AddTax { required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_DeleteItem value) deleteItem, required TResult Function(_AddDiscount value) addDiscount, required TResult Function(_RemoveDiscount value) removeDiscount, required TResult Function(_AddTax value) addTax, @@ -1277,6 +1624,7 @@ class _$AddTaxImpl implements _AddTax { required TResult Function(_UpdateItemNotes value) updateItemNotes, required TResult Function(_SaveDraftOrder value) saveDraftOrder, required TResult Function(_LoadDraftOrder value) loadDraftOrder, + required TResult Function(_UpdateDeliveryType value) updateDeliveryType, }) { return addTax(this); } @@ -1287,6 +1635,7 @@ class _$AddTaxImpl implements _AddTax { TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_DeleteItem value)? deleteItem, TResult? Function(_AddDiscount value)? addDiscount, TResult? Function(_RemoveDiscount value)? removeDiscount, TResult? Function(_AddTax value)? addTax, @@ -1297,6 +1646,7 @@ class _$AddTaxImpl implements _AddTax { TResult? Function(_UpdateItemNotes value)? updateItemNotes, TResult? Function(_SaveDraftOrder value)? saveDraftOrder, TResult? Function(_LoadDraftOrder value)? loadDraftOrder, + TResult? Function(_UpdateDeliveryType value)? updateDeliveryType, }) { return addTax?.call(this); } @@ -1307,6 +1657,7 @@ class _$AddTaxImpl implements _AddTax { TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, TResult Function(_RemoveItem value)? removeItem, + TResult Function(_DeleteItem value)? deleteItem, TResult Function(_AddDiscount value)? addDiscount, TResult Function(_RemoveDiscount value)? removeDiscount, TResult Function(_AddTax value)? addTax, @@ -1317,6 +1668,7 @@ class _$AddTaxImpl implements _AddTax { TResult Function(_UpdateItemNotes value)? updateItemNotes, TResult Function(_SaveDraftOrder value)? saveDraftOrder, TResult Function(_LoadDraftOrder value)? loadDraftOrder, + TResult Function(_UpdateDeliveryType value)? updateDeliveryType, required TResult orElse(), }) { if (addTax != null) { @@ -1409,8 +1761,11 @@ class _$AddServiceChargeImpl implements _AddServiceCharge { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product) addItem, - required TResult Function(Product product) removeItem, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(Product product, ProductVariant? variant) + deleteItem, required TResult Function(Discount discount) addDiscount, required TResult Function() removeDiscount, required TResult Function(int tax) addTax, @@ -1423,6 +1778,7 @@ class _$AddServiceChargeImpl implements _AddServiceCharge { int tableNumber, String draftName, int discountAmount) saveDraftOrder, required TResult Function(DraftOrderModel data) loadDraftOrder, + required TResult Function(DeliveryModel delivery) updateDeliveryType, }) { return addServiceCharge(serviceCharge); } @@ -1431,8 +1787,9 @@ class _$AddServiceChargeImpl implements _AddServiceCharge { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product)? addItem, - TResult? Function(Product product)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Discount discount)? addDiscount, TResult? Function()? removeDiscount, TResult? Function(int tax)? addTax, @@ -1444,6 +1801,7 @@ class _$AddServiceChargeImpl implements _AddServiceCharge { TResult? Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult? Function(DraftOrderModel data)? loadDraftOrder, + TResult? Function(DeliveryModel delivery)? updateDeliveryType, }) { return addServiceCharge?.call(serviceCharge); } @@ -1452,8 +1810,9 @@ class _$AddServiceChargeImpl implements _AddServiceCharge { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product)? addItem, - TResult Function(Product product)? removeItem, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Discount discount)? addDiscount, TResult Function()? removeDiscount, TResult Function(int tax)? addTax, @@ -1465,6 +1824,7 @@ class _$AddServiceChargeImpl implements _AddServiceCharge { TResult Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult Function(DraftOrderModel data)? loadDraftOrder, + TResult Function(DeliveryModel delivery)? updateDeliveryType, required TResult orElse(), }) { if (addServiceCharge != null) { @@ -1479,6 +1839,7 @@ class _$AddServiceChargeImpl implements _AddServiceCharge { required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_DeleteItem value) deleteItem, required TResult Function(_AddDiscount value) addDiscount, required TResult Function(_RemoveDiscount value) removeDiscount, required TResult Function(_AddTax value) addTax, @@ -1489,6 +1850,7 @@ class _$AddServiceChargeImpl implements _AddServiceCharge { required TResult Function(_UpdateItemNotes value) updateItemNotes, required TResult Function(_SaveDraftOrder value) saveDraftOrder, required TResult Function(_LoadDraftOrder value) loadDraftOrder, + required TResult Function(_UpdateDeliveryType value) updateDeliveryType, }) { return addServiceCharge(this); } @@ -1499,6 +1861,7 @@ class _$AddServiceChargeImpl implements _AddServiceCharge { TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_DeleteItem value)? deleteItem, TResult? Function(_AddDiscount value)? addDiscount, TResult? Function(_RemoveDiscount value)? removeDiscount, TResult? Function(_AddTax value)? addTax, @@ -1509,6 +1872,7 @@ class _$AddServiceChargeImpl implements _AddServiceCharge { TResult? Function(_UpdateItemNotes value)? updateItemNotes, TResult? Function(_SaveDraftOrder value)? saveDraftOrder, TResult? Function(_LoadDraftOrder value)? loadDraftOrder, + TResult? Function(_UpdateDeliveryType value)? updateDeliveryType, }) { return addServiceCharge?.call(this); } @@ -1519,6 +1883,7 @@ class _$AddServiceChargeImpl implements _AddServiceCharge { TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, TResult Function(_RemoveItem value)? removeItem, + TResult Function(_DeleteItem value)? deleteItem, TResult Function(_AddDiscount value)? addDiscount, TResult Function(_RemoveDiscount value)? removeDiscount, TResult Function(_AddTax value)? addTax, @@ -1529,6 +1894,7 @@ class _$AddServiceChargeImpl implements _AddServiceCharge { TResult Function(_UpdateItemNotes value)? updateItemNotes, TResult Function(_SaveDraftOrder value)? saveDraftOrder, TResult Function(_LoadDraftOrder value)? loadDraftOrder, + TResult Function(_UpdateDeliveryType value)? updateDeliveryType, required TResult orElse(), }) { if (addServiceCharge != null) { @@ -1593,8 +1959,11 @@ class _$RemoveTaxImpl implements _RemoveTax { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product) addItem, - required TResult Function(Product product) removeItem, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(Product product, ProductVariant? variant) + deleteItem, required TResult Function(Discount discount) addDiscount, required TResult Function() removeDiscount, required TResult Function(int tax) addTax, @@ -1607,6 +1976,7 @@ class _$RemoveTaxImpl implements _RemoveTax { int tableNumber, String draftName, int discountAmount) saveDraftOrder, required TResult Function(DraftOrderModel data) loadDraftOrder, + required TResult Function(DeliveryModel delivery) updateDeliveryType, }) { return removeTax(); } @@ -1615,8 +1985,9 @@ class _$RemoveTaxImpl implements _RemoveTax { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product)? addItem, - TResult? Function(Product product)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Discount discount)? addDiscount, TResult? Function()? removeDiscount, TResult? Function(int tax)? addTax, @@ -1628,6 +1999,7 @@ class _$RemoveTaxImpl implements _RemoveTax { TResult? Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult? Function(DraftOrderModel data)? loadDraftOrder, + TResult? Function(DeliveryModel delivery)? updateDeliveryType, }) { return removeTax?.call(); } @@ -1636,8 +2008,9 @@ class _$RemoveTaxImpl implements _RemoveTax { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product)? addItem, - TResult Function(Product product)? removeItem, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Discount discount)? addDiscount, TResult Function()? removeDiscount, TResult Function(int tax)? addTax, @@ -1649,6 +2022,7 @@ class _$RemoveTaxImpl implements _RemoveTax { TResult Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult Function(DraftOrderModel data)? loadDraftOrder, + TResult Function(DeliveryModel delivery)? updateDeliveryType, required TResult orElse(), }) { if (removeTax != null) { @@ -1663,6 +2037,7 @@ class _$RemoveTaxImpl implements _RemoveTax { required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_DeleteItem value) deleteItem, required TResult Function(_AddDiscount value) addDiscount, required TResult Function(_RemoveDiscount value) removeDiscount, required TResult Function(_AddTax value) addTax, @@ -1673,6 +2048,7 @@ class _$RemoveTaxImpl implements _RemoveTax { required TResult Function(_UpdateItemNotes value) updateItemNotes, required TResult Function(_SaveDraftOrder value) saveDraftOrder, required TResult Function(_LoadDraftOrder value) loadDraftOrder, + required TResult Function(_UpdateDeliveryType value) updateDeliveryType, }) { return removeTax(this); } @@ -1683,6 +2059,7 @@ class _$RemoveTaxImpl implements _RemoveTax { TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_DeleteItem value)? deleteItem, TResult? Function(_AddDiscount value)? addDiscount, TResult? Function(_RemoveDiscount value)? removeDiscount, TResult? Function(_AddTax value)? addTax, @@ -1693,6 +2070,7 @@ class _$RemoveTaxImpl implements _RemoveTax { TResult? Function(_UpdateItemNotes value)? updateItemNotes, TResult? Function(_SaveDraftOrder value)? saveDraftOrder, TResult? Function(_LoadDraftOrder value)? loadDraftOrder, + TResult? Function(_UpdateDeliveryType value)? updateDeliveryType, }) { return removeTax?.call(this); } @@ -1703,6 +2081,7 @@ class _$RemoveTaxImpl implements _RemoveTax { TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, TResult Function(_RemoveItem value)? removeItem, + TResult Function(_DeleteItem value)? deleteItem, TResult Function(_AddDiscount value)? addDiscount, TResult Function(_RemoveDiscount value)? removeDiscount, TResult Function(_AddTax value)? addTax, @@ -1713,6 +2092,7 @@ class _$RemoveTaxImpl implements _RemoveTax { TResult Function(_UpdateItemNotes value)? updateItemNotes, TResult Function(_SaveDraftOrder value)? saveDraftOrder, TResult Function(_LoadDraftOrder value)? loadDraftOrder, + TResult Function(_UpdateDeliveryType value)? updateDeliveryType, required TResult orElse(), }) { if (removeTax != null) { @@ -1769,8 +2149,11 @@ class _$RemoveServiceChargeImpl implements _RemoveServiceCharge { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product) addItem, - required TResult Function(Product product) removeItem, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(Product product, ProductVariant? variant) + deleteItem, required TResult Function(Discount discount) addDiscount, required TResult Function() removeDiscount, required TResult Function(int tax) addTax, @@ -1783,6 +2166,7 @@ class _$RemoveServiceChargeImpl implements _RemoveServiceCharge { int tableNumber, String draftName, int discountAmount) saveDraftOrder, required TResult Function(DraftOrderModel data) loadDraftOrder, + required TResult Function(DeliveryModel delivery) updateDeliveryType, }) { return removeServiceCharge(); } @@ -1791,8 +2175,9 @@ class _$RemoveServiceChargeImpl implements _RemoveServiceCharge { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product)? addItem, - TResult? Function(Product product)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Discount discount)? addDiscount, TResult? Function()? removeDiscount, TResult? Function(int tax)? addTax, @@ -1804,6 +2189,7 @@ class _$RemoveServiceChargeImpl implements _RemoveServiceCharge { TResult? Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult? Function(DraftOrderModel data)? loadDraftOrder, + TResult? Function(DeliveryModel delivery)? updateDeliveryType, }) { return removeServiceCharge?.call(); } @@ -1812,8 +2198,9 @@ class _$RemoveServiceChargeImpl implements _RemoveServiceCharge { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product)? addItem, - TResult Function(Product product)? removeItem, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Discount discount)? addDiscount, TResult Function()? removeDiscount, TResult Function(int tax)? addTax, @@ -1825,6 +2212,7 @@ class _$RemoveServiceChargeImpl implements _RemoveServiceCharge { TResult Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult Function(DraftOrderModel data)? loadDraftOrder, + TResult Function(DeliveryModel delivery)? updateDeliveryType, required TResult orElse(), }) { if (removeServiceCharge != null) { @@ -1839,6 +2227,7 @@ class _$RemoveServiceChargeImpl implements _RemoveServiceCharge { required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_DeleteItem value) deleteItem, required TResult Function(_AddDiscount value) addDiscount, required TResult Function(_RemoveDiscount value) removeDiscount, required TResult Function(_AddTax value) addTax, @@ -1849,6 +2238,7 @@ class _$RemoveServiceChargeImpl implements _RemoveServiceCharge { required TResult Function(_UpdateItemNotes value) updateItemNotes, required TResult Function(_SaveDraftOrder value) saveDraftOrder, required TResult Function(_LoadDraftOrder value) loadDraftOrder, + required TResult Function(_UpdateDeliveryType value) updateDeliveryType, }) { return removeServiceCharge(this); } @@ -1859,6 +2249,7 @@ class _$RemoveServiceChargeImpl implements _RemoveServiceCharge { TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_DeleteItem value)? deleteItem, TResult? Function(_AddDiscount value)? addDiscount, TResult? Function(_RemoveDiscount value)? removeDiscount, TResult? Function(_AddTax value)? addTax, @@ -1869,6 +2260,7 @@ class _$RemoveServiceChargeImpl implements _RemoveServiceCharge { TResult? Function(_UpdateItemNotes value)? updateItemNotes, TResult? Function(_SaveDraftOrder value)? saveDraftOrder, TResult? Function(_LoadDraftOrder value)? loadDraftOrder, + TResult? Function(_UpdateDeliveryType value)? updateDeliveryType, }) { return removeServiceCharge?.call(this); } @@ -1879,6 +2271,7 @@ class _$RemoveServiceChargeImpl implements _RemoveServiceCharge { TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, TResult Function(_RemoveItem value)? removeItem, + TResult Function(_DeleteItem value)? deleteItem, TResult Function(_AddDiscount value)? addDiscount, TResult Function(_RemoveDiscount value)? removeDiscount, TResult Function(_AddTax value)? addTax, @@ -1889,6 +2282,7 @@ class _$RemoveServiceChargeImpl implements _RemoveServiceCharge { TResult Function(_UpdateItemNotes value)? updateItemNotes, TResult Function(_SaveDraftOrder value)? saveDraftOrder, TResult Function(_LoadDraftOrder value)? loadDraftOrder, + TResult Function(_UpdateDeliveryType value)? updateDeliveryType, required TResult orElse(), }) { if (removeServiceCharge != null) { @@ -1973,8 +2367,11 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product) addItem, - required TResult Function(Product product) removeItem, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(Product product, ProductVariant? variant) + deleteItem, required TResult Function(Discount discount) addDiscount, required TResult Function() removeDiscount, required TResult Function(int tax) addTax, @@ -1987,6 +2384,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { int tableNumber, String draftName, int discountAmount) saveDraftOrder, required TResult Function(DraftOrderModel data) loadDraftOrder, + required TResult Function(DeliveryModel delivery) updateDeliveryType, }) { return updateOrderType(orderType); } @@ -1995,8 +2393,9 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product)? addItem, - TResult? Function(Product product)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Discount discount)? addDiscount, TResult? Function()? removeDiscount, TResult? Function(int tax)? addTax, @@ -2008,6 +2407,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { TResult? Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult? Function(DraftOrderModel data)? loadDraftOrder, + TResult? Function(DeliveryModel delivery)? updateDeliveryType, }) { return updateOrderType?.call(orderType); } @@ -2016,8 +2416,9 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product)? addItem, - TResult Function(Product product)? removeItem, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Discount discount)? addDiscount, TResult Function()? removeDiscount, TResult Function(int tax)? addTax, @@ -2029,6 +2430,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { TResult Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult Function(DraftOrderModel data)? loadDraftOrder, + TResult Function(DeliveryModel delivery)? updateDeliveryType, required TResult orElse(), }) { if (updateOrderType != null) { @@ -2043,6 +2445,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_DeleteItem value) deleteItem, required TResult Function(_AddDiscount value) addDiscount, required TResult Function(_RemoveDiscount value) removeDiscount, required TResult Function(_AddTax value) addTax, @@ -2053,6 +2456,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { required TResult Function(_UpdateItemNotes value) updateItemNotes, required TResult Function(_SaveDraftOrder value) saveDraftOrder, required TResult Function(_LoadDraftOrder value) loadDraftOrder, + required TResult Function(_UpdateDeliveryType value) updateDeliveryType, }) { return updateOrderType(this); } @@ -2063,6 +2467,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_DeleteItem value)? deleteItem, TResult? Function(_AddDiscount value)? addDiscount, TResult? Function(_RemoveDiscount value)? removeDiscount, TResult? Function(_AddTax value)? addTax, @@ -2073,6 +2478,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { TResult? Function(_UpdateItemNotes value)? updateItemNotes, TResult? Function(_SaveDraftOrder value)? saveDraftOrder, TResult? Function(_LoadDraftOrder value)? loadDraftOrder, + TResult? Function(_UpdateDeliveryType value)? updateDeliveryType, }) { return updateOrderType?.call(this); } @@ -2083,6 +2489,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, TResult Function(_RemoveItem value)? removeItem, + TResult Function(_DeleteItem value)? deleteItem, TResult Function(_AddDiscount value)? addDiscount, TResult Function(_RemoveDiscount value)? removeDiscount, TResult Function(_AddTax value)? addTax, @@ -2093,6 +2500,7 @@ class _$UpdateOrderTypeImpl implements _UpdateOrderType { TResult Function(_UpdateItemNotes value)? updateItemNotes, TResult Function(_SaveDraftOrder value)? saveDraftOrder, TResult Function(_LoadDraftOrder value)? loadDraftOrder, + TResult Function(_UpdateDeliveryType value)? updateDeliveryType, required TResult orElse(), }) { if (updateOrderType != null) { @@ -2193,8 +2601,11 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product) addItem, - required TResult Function(Product product) removeItem, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(Product product, ProductVariant? variant) + deleteItem, required TResult Function(Discount discount) addDiscount, required TResult Function() removeDiscount, required TResult Function(int tax) addTax, @@ -2207,6 +2618,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { int tableNumber, String draftName, int discountAmount) saveDraftOrder, required TResult Function(DraftOrderModel data) loadDraftOrder, + required TResult Function(DeliveryModel delivery) updateDeliveryType, }) { return updateItemNotes(product, notes); } @@ -2215,8 +2627,9 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product)? addItem, - TResult? Function(Product product)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Discount discount)? addDiscount, TResult? Function()? removeDiscount, TResult? Function(int tax)? addTax, @@ -2228,6 +2641,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { TResult? Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult? Function(DraftOrderModel data)? loadDraftOrder, + TResult? Function(DeliveryModel delivery)? updateDeliveryType, }) { return updateItemNotes?.call(product, notes); } @@ -2236,8 +2650,9 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product)? addItem, - TResult Function(Product product)? removeItem, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Discount discount)? addDiscount, TResult Function()? removeDiscount, TResult Function(int tax)? addTax, @@ -2249,6 +2664,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { TResult Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult Function(DraftOrderModel data)? loadDraftOrder, + TResult Function(DeliveryModel delivery)? updateDeliveryType, required TResult orElse(), }) { if (updateItemNotes != null) { @@ -2263,6 +2679,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_DeleteItem value) deleteItem, required TResult Function(_AddDiscount value) addDiscount, required TResult Function(_RemoveDiscount value) removeDiscount, required TResult Function(_AddTax value) addTax, @@ -2273,6 +2690,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { required TResult Function(_UpdateItemNotes value) updateItemNotes, required TResult Function(_SaveDraftOrder value) saveDraftOrder, required TResult Function(_LoadDraftOrder value) loadDraftOrder, + required TResult Function(_UpdateDeliveryType value) updateDeliveryType, }) { return updateItemNotes(this); } @@ -2283,6 +2701,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_DeleteItem value)? deleteItem, TResult? Function(_AddDiscount value)? addDiscount, TResult? Function(_RemoveDiscount value)? removeDiscount, TResult? Function(_AddTax value)? addTax, @@ -2293,6 +2712,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { TResult? Function(_UpdateItemNotes value)? updateItemNotes, TResult? Function(_SaveDraftOrder value)? saveDraftOrder, TResult? Function(_LoadDraftOrder value)? loadDraftOrder, + TResult? Function(_UpdateDeliveryType value)? updateDeliveryType, }) { return updateItemNotes?.call(this); } @@ -2303,6 +2723,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, TResult Function(_RemoveItem value)? removeItem, + TResult Function(_DeleteItem value)? deleteItem, TResult Function(_AddDiscount value)? addDiscount, TResult Function(_RemoveDiscount value)? removeDiscount, TResult Function(_AddTax value)? addTax, @@ -2313,6 +2734,7 @@ class _$UpdateItemNotesImpl implements _UpdateItemNotes { TResult Function(_UpdateItemNotes value)? updateItemNotes, TResult Function(_SaveDraftOrder value)? saveDraftOrder, TResult Function(_LoadDraftOrder value)? loadDraftOrder, + TResult Function(_UpdateDeliveryType value)? updateDeliveryType, required TResult orElse(), }) { if (updateItemNotes != null) { @@ -2427,8 +2849,11 @@ class _$SaveDraftOrderImpl implements _SaveDraftOrder { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product) addItem, - required TResult Function(Product product) removeItem, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(Product product, ProductVariant? variant) + deleteItem, required TResult Function(Discount discount) addDiscount, required TResult Function() removeDiscount, required TResult Function(int tax) addTax, @@ -2441,6 +2866,7 @@ class _$SaveDraftOrderImpl implements _SaveDraftOrder { int tableNumber, String draftName, int discountAmount) saveDraftOrder, required TResult Function(DraftOrderModel data) loadDraftOrder, + required TResult Function(DeliveryModel delivery) updateDeliveryType, }) { return saveDraftOrder(tableNumber, draftName, discountAmount); } @@ -2449,8 +2875,9 @@ class _$SaveDraftOrderImpl implements _SaveDraftOrder { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product)? addItem, - TResult? Function(Product product)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Discount discount)? addDiscount, TResult? Function()? removeDiscount, TResult? Function(int tax)? addTax, @@ -2462,6 +2889,7 @@ class _$SaveDraftOrderImpl implements _SaveDraftOrder { TResult? Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult? Function(DraftOrderModel data)? loadDraftOrder, + TResult? Function(DeliveryModel delivery)? updateDeliveryType, }) { return saveDraftOrder?.call(tableNumber, draftName, discountAmount); } @@ -2470,8 +2898,9 @@ class _$SaveDraftOrderImpl implements _SaveDraftOrder { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product)? addItem, - TResult Function(Product product)? removeItem, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Discount discount)? addDiscount, TResult Function()? removeDiscount, TResult Function(int tax)? addTax, @@ -2483,6 +2912,7 @@ class _$SaveDraftOrderImpl implements _SaveDraftOrder { TResult Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult Function(DraftOrderModel data)? loadDraftOrder, + TResult Function(DeliveryModel delivery)? updateDeliveryType, required TResult orElse(), }) { if (saveDraftOrder != null) { @@ -2497,6 +2927,7 @@ class _$SaveDraftOrderImpl implements _SaveDraftOrder { required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_DeleteItem value) deleteItem, required TResult Function(_AddDiscount value) addDiscount, required TResult Function(_RemoveDiscount value) removeDiscount, required TResult Function(_AddTax value) addTax, @@ -2507,6 +2938,7 @@ class _$SaveDraftOrderImpl implements _SaveDraftOrder { required TResult Function(_UpdateItemNotes value) updateItemNotes, required TResult Function(_SaveDraftOrder value) saveDraftOrder, required TResult Function(_LoadDraftOrder value) loadDraftOrder, + required TResult Function(_UpdateDeliveryType value) updateDeliveryType, }) { return saveDraftOrder(this); } @@ -2517,6 +2949,7 @@ class _$SaveDraftOrderImpl implements _SaveDraftOrder { TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_DeleteItem value)? deleteItem, TResult? Function(_AddDiscount value)? addDiscount, TResult? Function(_RemoveDiscount value)? removeDiscount, TResult? Function(_AddTax value)? addTax, @@ -2527,6 +2960,7 @@ class _$SaveDraftOrderImpl implements _SaveDraftOrder { TResult? Function(_UpdateItemNotes value)? updateItemNotes, TResult? Function(_SaveDraftOrder value)? saveDraftOrder, TResult? Function(_LoadDraftOrder value)? loadDraftOrder, + TResult? Function(_UpdateDeliveryType value)? updateDeliveryType, }) { return saveDraftOrder?.call(this); } @@ -2537,6 +2971,7 @@ class _$SaveDraftOrderImpl implements _SaveDraftOrder { TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, TResult Function(_RemoveItem value)? removeItem, + TResult Function(_DeleteItem value)? deleteItem, TResult Function(_AddDiscount value)? addDiscount, TResult Function(_RemoveDiscount value)? removeDiscount, TResult Function(_AddTax value)? addTax, @@ -2547,6 +2982,7 @@ class _$SaveDraftOrderImpl implements _SaveDraftOrder { TResult Function(_UpdateItemNotes value)? updateItemNotes, TResult Function(_SaveDraftOrder value)? saveDraftOrder, TResult Function(_LoadDraftOrder value)? loadDraftOrder, + TResult Function(_UpdateDeliveryType value)? updateDeliveryType, required TResult orElse(), }) { if (saveDraftOrder != null) { @@ -2641,8 +3077,11 @@ class _$LoadDraftOrderImpl implements _LoadDraftOrder { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product) addItem, - required TResult Function(Product product) removeItem, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(Product product, ProductVariant? variant) + deleteItem, required TResult Function(Discount discount) addDiscount, required TResult Function() removeDiscount, required TResult Function(int tax) addTax, @@ -2655,6 +3094,7 @@ class _$LoadDraftOrderImpl implements _LoadDraftOrder { int tableNumber, String draftName, int discountAmount) saveDraftOrder, required TResult Function(DraftOrderModel data) loadDraftOrder, + required TResult Function(DeliveryModel delivery) updateDeliveryType, }) { return loadDraftOrder(data); } @@ -2663,8 +3103,9 @@ class _$LoadDraftOrderImpl implements _LoadDraftOrder { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product)? addItem, - TResult? Function(Product product)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? deleteItem, TResult? Function(Discount discount)? addDiscount, TResult? Function()? removeDiscount, TResult? Function(int tax)? addTax, @@ -2676,6 +3117,7 @@ class _$LoadDraftOrderImpl implements _LoadDraftOrder { TResult? Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult? Function(DraftOrderModel data)? loadDraftOrder, + TResult? Function(DeliveryModel delivery)? updateDeliveryType, }) { return loadDraftOrder?.call(data); } @@ -2684,8 +3126,9 @@ class _$LoadDraftOrderImpl implements _LoadDraftOrder { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product)? addItem, - TResult Function(Product product)? removeItem, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(Product product, ProductVariant? variant)? deleteItem, TResult Function(Discount discount)? addDiscount, TResult Function()? removeDiscount, TResult Function(int tax)? addTax, @@ -2697,6 +3140,7 @@ class _$LoadDraftOrderImpl implements _LoadDraftOrder { TResult Function(int tableNumber, String draftName, int discountAmount)? saveDraftOrder, TResult Function(DraftOrderModel data)? loadDraftOrder, + TResult Function(DeliveryModel delivery)? updateDeliveryType, required TResult orElse(), }) { if (loadDraftOrder != null) { @@ -2711,6 +3155,7 @@ class _$LoadDraftOrderImpl implements _LoadDraftOrder { required TResult Function(_Started value) started, required TResult Function(_AddItem value) addItem, required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_DeleteItem value) deleteItem, required TResult Function(_AddDiscount value) addDiscount, required TResult Function(_RemoveDiscount value) removeDiscount, required TResult Function(_AddTax value) addTax, @@ -2721,6 +3166,7 @@ class _$LoadDraftOrderImpl implements _LoadDraftOrder { required TResult Function(_UpdateItemNotes value) updateItemNotes, required TResult Function(_SaveDraftOrder value) saveDraftOrder, required TResult Function(_LoadDraftOrder value) loadDraftOrder, + required TResult Function(_UpdateDeliveryType value) updateDeliveryType, }) { return loadDraftOrder(this); } @@ -2731,6 +3177,7 @@ class _$LoadDraftOrderImpl implements _LoadDraftOrder { TResult? Function(_Started value)? started, TResult? Function(_AddItem value)? addItem, TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_DeleteItem value)? deleteItem, TResult? Function(_AddDiscount value)? addDiscount, TResult? Function(_RemoveDiscount value)? removeDiscount, TResult? Function(_AddTax value)? addTax, @@ -2741,6 +3188,7 @@ class _$LoadDraftOrderImpl implements _LoadDraftOrder { TResult? Function(_UpdateItemNotes value)? updateItemNotes, TResult? Function(_SaveDraftOrder value)? saveDraftOrder, TResult? Function(_LoadDraftOrder value)? loadDraftOrder, + TResult? Function(_UpdateDeliveryType value)? updateDeliveryType, }) { return loadDraftOrder?.call(this); } @@ -2751,6 +3199,7 @@ class _$LoadDraftOrderImpl implements _LoadDraftOrder { TResult Function(_Started value)? started, TResult Function(_AddItem value)? addItem, TResult Function(_RemoveItem value)? removeItem, + TResult Function(_DeleteItem value)? deleteItem, TResult Function(_AddDiscount value)? addDiscount, TResult Function(_RemoveDiscount value)? removeDiscount, TResult Function(_AddTax value)? addTax, @@ -2761,6 +3210,7 @@ class _$LoadDraftOrderImpl implements _LoadDraftOrder { TResult Function(_UpdateItemNotes value)? updateItemNotes, TResult Function(_SaveDraftOrder value)? saveDraftOrder, TResult Function(_LoadDraftOrder value)? loadDraftOrder, + TResult Function(_UpdateDeliveryType value)? updateDeliveryType, required TResult orElse(), }) { if (loadDraftOrder != null) { @@ -2783,6 +3233,233 @@ abstract class _LoadDraftOrder implements CheckoutEvent { throw _privateConstructorUsedError; } +/// @nodoc +abstract class _$$UpdateDeliveryTypeImplCopyWith<$Res> { + factory _$$UpdateDeliveryTypeImplCopyWith(_$UpdateDeliveryTypeImpl value, + $Res Function(_$UpdateDeliveryTypeImpl) then) = + __$$UpdateDeliveryTypeImplCopyWithImpl<$Res>; + @useResult + $Res call({DeliveryModel delivery}); +} + +/// @nodoc +class __$$UpdateDeliveryTypeImplCopyWithImpl<$Res> + extends _$CheckoutEventCopyWithImpl<$Res, _$UpdateDeliveryTypeImpl> + implements _$$UpdateDeliveryTypeImplCopyWith<$Res> { + __$$UpdateDeliveryTypeImplCopyWithImpl(_$UpdateDeliveryTypeImpl _value, + $Res Function(_$UpdateDeliveryTypeImpl) _then) + : super(_value, _then); + + /// Create a copy of CheckoutEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? delivery = null, + }) { + return _then(_$UpdateDeliveryTypeImpl( + null == delivery + ? _value.delivery + : delivery // ignore: cast_nullable_to_non_nullable + as DeliveryModel, + )); + } +} + +/// @nodoc + +class _$UpdateDeliveryTypeImpl implements _UpdateDeliveryType { + const _$UpdateDeliveryTypeImpl(this.delivery); + + @override + final DeliveryModel delivery; + + @override + String toString() { + return 'CheckoutEvent.updateDeliveryType(delivery: $delivery)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UpdateDeliveryTypeImpl && + (identical(other.delivery, delivery) || + other.delivery == delivery)); + } + + @override + int get hashCode => Object.hash(runtimeType, delivery); + + /// 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') + _$$UpdateDeliveryTypeImplCopyWith<_$UpdateDeliveryTypeImpl> get copyWith => + __$$UpdateDeliveryTypeImplCopyWithImpl<_$UpdateDeliveryTypeImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() started, + required TResult Function(Product product, ProductVariant? variant) addItem, + required TResult Function(Product product, ProductVariant? variant) + removeItem, + required TResult Function(Product product, ProductVariant? variant) + deleteItem, + required TResult Function(Discount discount) addDiscount, + required TResult Function() removeDiscount, + required TResult Function(int tax) addTax, + required TResult Function(int serviceCharge) addServiceCharge, + required TResult Function() removeTax, + required TResult Function() removeServiceCharge, + required TResult Function(OrderType orderType) updateOrderType, + required TResult Function(Product product, String notes) updateItemNotes, + required TResult Function( + int tableNumber, String draftName, int discountAmount) + saveDraftOrder, + required TResult Function(DraftOrderModel data) loadDraftOrder, + required TResult Function(DeliveryModel delivery) updateDeliveryType, + }) { + return updateDeliveryType(delivery); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? started, + TResult? Function(Product product, ProductVariant? variant)? addItem, + TResult? Function(Product product, ProductVariant? variant)? removeItem, + TResult? Function(Product product, ProductVariant? variant)? deleteItem, + TResult? Function(Discount discount)? addDiscount, + TResult? Function()? removeDiscount, + TResult? Function(int tax)? addTax, + TResult? Function(int serviceCharge)? addServiceCharge, + TResult? Function()? removeTax, + TResult? Function()? removeServiceCharge, + TResult? Function(OrderType orderType)? updateOrderType, + TResult? Function(Product product, String notes)? updateItemNotes, + TResult? Function(int tableNumber, String draftName, int discountAmount)? + saveDraftOrder, + TResult? Function(DraftOrderModel data)? loadDraftOrder, + TResult? Function(DeliveryModel delivery)? updateDeliveryType, + }) { + return updateDeliveryType?.call(delivery); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? started, + TResult Function(Product product, ProductVariant? variant)? addItem, + TResult Function(Product product, ProductVariant? variant)? removeItem, + TResult Function(Product product, ProductVariant? variant)? deleteItem, + TResult Function(Discount discount)? addDiscount, + TResult Function()? removeDiscount, + TResult Function(int tax)? addTax, + TResult Function(int serviceCharge)? addServiceCharge, + TResult Function()? removeTax, + TResult Function()? removeServiceCharge, + TResult Function(OrderType orderType)? updateOrderType, + TResult Function(Product product, String notes)? updateItemNotes, + TResult Function(int tableNumber, String draftName, int discountAmount)? + saveDraftOrder, + TResult Function(DraftOrderModel data)? loadDraftOrder, + TResult Function(DeliveryModel delivery)? updateDeliveryType, + required TResult orElse(), + }) { + if (updateDeliveryType != null) { + return updateDeliveryType(delivery); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Started value) started, + required TResult Function(_AddItem value) addItem, + required TResult Function(_RemoveItem value) removeItem, + required TResult Function(_DeleteItem value) deleteItem, + required TResult Function(_AddDiscount value) addDiscount, + required TResult Function(_RemoveDiscount value) removeDiscount, + required TResult Function(_AddTax value) addTax, + required TResult Function(_AddServiceCharge value) addServiceCharge, + required TResult Function(_RemoveTax value) removeTax, + required TResult Function(_RemoveServiceCharge value) removeServiceCharge, + required TResult Function(_UpdateOrderType value) updateOrderType, + required TResult Function(_UpdateItemNotes value) updateItemNotes, + required TResult Function(_SaveDraftOrder value) saveDraftOrder, + required TResult Function(_LoadDraftOrder value) loadDraftOrder, + required TResult Function(_UpdateDeliveryType value) updateDeliveryType, + }) { + return updateDeliveryType(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Started value)? started, + TResult? Function(_AddItem value)? addItem, + TResult? Function(_RemoveItem value)? removeItem, + TResult? Function(_DeleteItem value)? deleteItem, + TResult? Function(_AddDiscount value)? addDiscount, + TResult? Function(_RemoveDiscount value)? removeDiscount, + TResult? Function(_AddTax value)? addTax, + TResult? Function(_AddServiceCharge value)? addServiceCharge, + TResult? Function(_RemoveTax value)? removeTax, + TResult? Function(_RemoveServiceCharge value)? removeServiceCharge, + TResult? Function(_UpdateOrderType value)? updateOrderType, + TResult? Function(_UpdateItemNotes value)? updateItemNotes, + TResult? Function(_SaveDraftOrder value)? saveDraftOrder, + TResult? Function(_LoadDraftOrder value)? loadDraftOrder, + TResult? Function(_UpdateDeliveryType value)? updateDeliveryType, + }) { + return updateDeliveryType?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Started value)? started, + TResult Function(_AddItem value)? addItem, + TResult Function(_RemoveItem value)? removeItem, + TResult Function(_DeleteItem value)? deleteItem, + TResult Function(_AddDiscount value)? addDiscount, + TResult Function(_RemoveDiscount value)? removeDiscount, + TResult Function(_AddTax value)? addTax, + TResult Function(_AddServiceCharge value)? addServiceCharge, + TResult Function(_RemoveTax value)? removeTax, + TResult Function(_RemoveServiceCharge value)? removeServiceCharge, + TResult Function(_UpdateOrderType value)? updateOrderType, + TResult Function(_UpdateItemNotes value)? updateItemNotes, + TResult Function(_SaveDraftOrder value)? saveDraftOrder, + TResult Function(_LoadDraftOrder value)? loadDraftOrder, + TResult Function(_UpdateDeliveryType value)? updateDeliveryType, + required TResult orElse(), + }) { + if (updateDeliveryType != null) { + return updateDeliveryType(this); + } + return orElse(); + } +} + +abstract class _UpdateDeliveryType implements CheckoutEvent { + const factory _UpdateDeliveryType(final DeliveryModel delivery) = + _$UpdateDeliveryTypeImpl; + + DeliveryModel get delivery; + + /// Create a copy of CheckoutEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$UpdateDeliveryTypeImplCopyWith<_$UpdateDeliveryTypeImpl> get copyWith => + throw _privateConstructorUsedError; +} + /// @nodoc mixin _$CheckoutState { @optionalTypeArgs @@ -2799,7 +3476,8 @@ mixin _$CheckoutState { int totalQuantity, int totalPrice, String draftName, - OrderType orderType) + OrderType orderType, + DeliveryModel? deliveryType) loaded, required TResult Function(String message) error, required TResult Function(int orderId) savedDraftOrder, @@ -2819,7 +3497,8 @@ mixin _$CheckoutState { int totalQuantity, int totalPrice, String draftName, - OrderType orderType)? + OrderType orderType, + DeliveryModel? deliveryType)? loaded, TResult? Function(String message)? error, TResult? Function(int orderId)? savedDraftOrder, @@ -2839,7 +3518,8 @@ mixin _$CheckoutState { int totalQuantity, int totalPrice, String draftName, - OrderType orderType)? + OrderType orderType, + DeliveryModel? deliveryType)? loaded, TResult Function(String message)? error, TResult Function(int orderId)? savedDraftOrder, @@ -2950,7 +3630,8 @@ class _$InitialImpl implements _Initial { int totalQuantity, int totalPrice, String draftName, - OrderType orderType) + OrderType orderType, + DeliveryModel? deliveryType) loaded, required TResult Function(String message) error, required TResult Function(int orderId) savedDraftOrder, @@ -2973,7 +3654,8 @@ class _$InitialImpl implements _Initial { int totalQuantity, int totalPrice, String draftName, - OrderType orderType)? + OrderType orderType, + DeliveryModel? deliveryType)? loaded, TResult? Function(String message)? error, TResult? Function(int orderId)? savedDraftOrder, @@ -2996,7 +3678,8 @@ class _$InitialImpl implements _Initial { int totalQuantity, int totalPrice, String draftName, - OrderType orderType)? + OrderType orderType, + DeliveryModel? deliveryType)? loaded, TResult Function(String message)? error, TResult Function(int orderId)? savedDraftOrder, @@ -3106,7 +3789,8 @@ class _$LoadingImpl implements _Loading { int totalQuantity, int totalPrice, String draftName, - OrderType orderType) + OrderType orderType, + DeliveryModel? deliveryType) loaded, required TResult Function(String message) error, required TResult Function(int orderId) savedDraftOrder, @@ -3129,7 +3813,8 @@ class _$LoadingImpl implements _Loading { int totalQuantity, int totalPrice, String draftName, - OrderType orderType)? + OrderType orderType, + DeliveryModel? deliveryType)? loaded, TResult? Function(String message)? error, TResult? Function(int orderId)? savedDraftOrder, @@ -3152,7 +3837,8 @@ class _$LoadingImpl implements _Loading { int totalQuantity, int totalPrice, String draftName, - OrderType orderType)? + OrderType orderType, + DeliveryModel? deliveryType)? loaded, TResult Function(String message)? error, TResult Function(int orderId)? savedDraftOrder, @@ -3225,7 +3911,8 @@ abstract class _$$LoadedImplCopyWith<$Res> { int totalQuantity, int totalPrice, String draftName, - OrderType orderType}); + OrderType orderType, + DeliveryModel? deliveryType}); } /// @nodoc @@ -3251,6 +3938,7 @@ class __$$LoadedImplCopyWithImpl<$Res> Object? totalPrice = null, Object? draftName = null, Object? orderType = null, + Object? deliveryType = freezed, }) { return _then(_$LoadedImpl( null == items @@ -3293,6 +3981,10 @@ class __$$LoadedImplCopyWithImpl<$Res> ? _value.orderType : orderType // ignore: cast_nullable_to_non_nullable as OrderType, + freezed == deliveryType + ? _value.deliveryType + : deliveryType // ignore: cast_nullable_to_non_nullable + as DeliveryModel?, )); } } @@ -3310,7 +4002,8 @@ class _$LoadedImpl implements _Loaded { this.totalQuantity, this.totalPrice, this.draftName, - this.orderType) + this.orderType, + this.deliveryType) : _items = items; final List _items; @@ -3339,10 +4032,12 @@ class _$LoadedImpl implements _Loaded { final String draftName; @override final OrderType orderType; + @override + final DeliveryModel? deliveryType; @override String toString() { - return 'CheckoutState.loaded(items: $items, discountModel: $discountModel, discount: $discount, discountAmount: $discountAmount, tax: $tax, serviceCharge: $serviceCharge, totalQuantity: $totalQuantity, totalPrice: $totalPrice, draftName: $draftName, orderType: $orderType)'; + return 'CheckoutState.loaded(items: $items, discountModel: $discountModel, discount: $discount, discountAmount: $discountAmount, tax: $tax, serviceCharge: $serviceCharge, totalQuantity: $totalQuantity, totalPrice: $totalPrice, draftName: $draftName, orderType: $orderType, deliveryType: $deliveryType)'; } @override @@ -3367,7 +4062,9 @@ class _$LoadedImpl implements _Loaded { (identical(other.draftName, draftName) || other.draftName == draftName) && (identical(other.orderType, orderType) || - other.orderType == orderType)); + other.orderType == orderType) && + (identical(other.deliveryType, deliveryType) || + other.deliveryType == deliveryType)); } @override @@ -3382,7 +4079,8 @@ class _$LoadedImpl implements _Loaded { totalQuantity, totalPrice, draftName, - orderType); + orderType, + deliveryType); /// Create a copy of CheckoutState /// with the given fields replaced by the non-null parameter values. @@ -3407,13 +4105,24 @@ class _$LoadedImpl implements _Loaded { int totalQuantity, int totalPrice, String draftName, - OrderType orderType) + OrderType orderType, + DeliveryModel? deliveryType) loaded, required TResult Function(String message) error, required TResult Function(int orderId) savedDraftOrder, }) { - return loaded(items, discountModel, discount, discountAmount, tax, - serviceCharge, totalQuantity, totalPrice, draftName, orderType); + return loaded( + items, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType); } @override @@ -3431,13 +4140,24 @@ class _$LoadedImpl implements _Loaded { int totalQuantity, int totalPrice, String draftName, - OrderType orderType)? + OrderType orderType, + DeliveryModel? deliveryType)? loaded, TResult? Function(String message)? error, TResult? Function(int orderId)? savedDraftOrder, }) { - return loaded?.call(items, discountModel, discount, discountAmount, tax, - serviceCharge, totalQuantity, totalPrice, draftName, orderType); + return loaded?.call( + items, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType); } @override @@ -3455,15 +4175,26 @@ class _$LoadedImpl implements _Loaded { int totalQuantity, int totalPrice, String draftName, - OrderType orderType)? + OrderType orderType, + DeliveryModel? deliveryType)? loaded, TResult Function(String message)? error, TResult Function(int orderId)? savedDraftOrder, required TResult orElse(), }) { if (loaded != null) { - return loaded(items, discountModel, discount, discountAmount, tax, - serviceCharge, totalQuantity, totalPrice, draftName, orderType); + return loaded( + items, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType); } return orElse(); } @@ -3520,7 +4251,8 @@ abstract class _Loaded implements CheckoutState { final int totalQuantity, final int totalPrice, final String draftName, - final OrderType orderType) = _$LoadedImpl; + final OrderType orderType, + final DeliveryModel? deliveryType) = _$LoadedImpl; List get items; Discount? get discountModel; @@ -3532,6 +4264,7 @@ abstract class _Loaded implements CheckoutState { int get totalPrice; String get draftName; OrderType get orderType; + DeliveryModel? get deliveryType; /// Create a copy of CheckoutState /// with the given fields replaced by the non-null parameter values. @@ -3620,7 +4353,8 @@ class _$ErrorImpl implements _Error { int totalQuantity, int totalPrice, String draftName, - OrderType orderType) + OrderType orderType, + DeliveryModel? deliveryType) loaded, required TResult Function(String message) error, required TResult Function(int orderId) savedDraftOrder, @@ -3643,7 +4377,8 @@ class _$ErrorImpl implements _Error { int totalQuantity, int totalPrice, String draftName, - OrderType orderType)? + OrderType orderType, + DeliveryModel? deliveryType)? loaded, TResult? Function(String message)? error, TResult? Function(int orderId)? savedDraftOrder, @@ -3666,7 +4401,8 @@ class _$ErrorImpl implements _Error { int totalQuantity, int totalPrice, String draftName, - OrderType orderType)? + OrderType orderType, + DeliveryModel? deliveryType)? loaded, TResult Function(String message)? error, TResult Function(int orderId)? savedDraftOrder, @@ -3812,7 +4548,8 @@ class _$SavedDraftOrderImpl implements _SavedDraftOrder { int totalQuantity, int totalPrice, String draftName, - OrderType orderType) + OrderType orderType, + DeliveryModel? deliveryType) loaded, required TResult Function(String message) error, required TResult Function(int orderId) savedDraftOrder, @@ -3835,7 +4572,8 @@ class _$SavedDraftOrderImpl implements _SavedDraftOrder { int totalQuantity, int totalPrice, String draftName, - OrderType orderType)? + OrderType orderType, + DeliveryModel? deliveryType)? loaded, TResult? Function(String message)? error, TResult? Function(int orderId)? savedDraftOrder, @@ -3858,7 +4596,8 @@ class _$SavedDraftOrderImpl implements _SavedDraftOrder { int totalQuantity, int totalPrice, String draftName, - OrderType orderType)? + OrderType orderType, + DeliveryModel? deliveryType)? loaded, TResult Function(String message)? error, TResult Function(int orderId)? savedDraftOrder, diff --git a/lib/presentation/home/bloc/checkout/checkout_event.dart b/lib/presentation/home/bloc/checkout/checkout_event.dart index e276431..25e6edf 100644 --- a/lib/presentation/home/bloc/checkout/checkout_event.dart +++ b/lib/presentation/home/bloc/checkout/checkout_event.dart @@ -4,9 +4,14 @@ part of 'checkout_bloc.dart'; class CheckoutEvent with _$CheckoutEvent { const factory CheckoutEvent.started() = _Started; //add item - const factory CheckoutEvent.addItem(Product product) = _AddItem; + const factory CheckoutEvent.addItem( + Product product, ProductVariant? variant) = _AddItem; //remove item - const factory CheckoutEvent.removeItem(Product product) = _RemoveItem; + const factory CheckoutEvent.removeItem( + Product product, ProductVariant? variant) = _RemoveItem; + // Delete Item + const factory CheckoutEvent.deleteItem( + Product product, ProductVariant? variant) = _DeleteItem; //add discount const factory CheckoutEvent.addDiscount(Discount discount) = _AddDiscount; @@ -20,13 +25,15 @@ class CheckoutEvent with _$CheckoutEvent { //remove tax const factory CheckoutEvent.removeTax() = _RemoveTax; //remove service charge - const factory CheckoutEvent.removeServiceCharge() = _RemoveServiceCharge; + const factory CheckoutEvent.removeServiceCharge() = _RemoveServiceCharge; //update order type - const factory CheckoutEvent.updateOrderType(OrderType orderType) = _UpdateOrderType; + const factory CheckoutEvent.updateOrderType(OrderType orderType) = + _UpdateOrderType; //update item notes - const factory CheckoutEvent.updateItemNotes(Product product, String notes) = _UpdateItemNotes; + const factory CheckoutEvent.updateItemNotes(Product product, String notes) = + _UpdateItemNotes; //save draft order const factory CheckoutEvent.saveDraftOrder( @@ -35,4 +42,8 @@ class CheckoutEvent with _$CheckoutEvent { //load draft order const factory CheckoutEvent.loadDraftOrder(DraftOrderModel data) = _LoadDraftOrder; + +// Update delivery type + const factory CheckoutEvent.updateDeliveryType(DeliveryModel delivery) = + _UpdateDeliveryType; } diff --git a/lib/presentation/home/bloc/checkout/checkout_state.dart b/lib/presentation/home/bloc/checkout/checkout_state.dart index 738d63c..691236d 100644 --- a/lib/presentation/home/bloc/checkout/checkout_state.dart +++ b/lib/presentation/home/bloc/checkout/checkout_state.dart @@ -5,16 +5,18 @@ class CheckoutState with _$CheckoutState { const factory CheckoutState.initial() = _Initial; const factory CheckoutState.loading() = _Loading; const factory CheckoutState.loaded( - List items, - Discount? discountModel, - int discount, - int discountAmount, - int tax, - int serviceCharge, - int totalQuantity, - int totalPrice, - String draftName, - OrderType orderType) = _Loaded; + List items, + Discount? discountModel, + int discount, + int discountAmount, + int tax, + int serviceCharge, + int totalQuantity, + int totalPrice, + String draftName, + OrderType orderType, + DeliveryModel? deliveryType, + ) = _Loaded; const factory CheckoutState.error(String message) = _Error; //save draft order diff --git a/lib/presentation/home/bloc/current_outlet/current_outlet_bloc.dart b/lib/presentation/home/bloc/current_outlet/current_outlet_bloc.dart new file mode 100644 index 0000000..b60ec94 --- /dev/null +++ b/lib/presentation/home/bloc/current_outlet/current_outlet_bloc.dart @@ -0,0 +1,23 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/outlet_remote_data_source.dart'; +import 'package:enaklo_pos/presentation/home/models/outlet_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'current_outlet_event.dart'; +part 'current_outlet_state.dart'; +part 'current_outlet_bloc.freezed.dart'; + +class CurrentOutletBloc extends Bloc { + final OutletRemoteDataSource _outletRemoteDataSource; + CurrentOutletBloc(this._outletRemoteDataSource) + : super(CurrentOutletState.initial()) { + on<_CurrentOutlet>((event, emit) async { + emit(const _Loading()); + final result = await _outletRemoteDataSource.currentOutlet(); + result.fold( + (l) => emit(_Error(l)), + (r) => emit(_Loaded(r.data!)), + ); + }); + } +} diff --git a/lib/presentation/home/bloc/current_outlet/current_outlet_bloc.freezed.dart b/lib/presentation/home/bloc/current_outlet/current_outlet_bloc.freezed.dart new file mode 100644 index 0000000..7229d76 --- /dev/null +++ b/lib/presentation/home/bloc/current_outlet/current_outlet_bloc.freezed.dart @@ -0,0 +1,784 @@ +// 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 'current_outlet_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$CurrentOutletEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() currentOutlet, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? currentOutlet, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? currentOutlet, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_CurrentOutlet value) currentOutlet, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_CurrentOutlet value)? currentOutlet, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_CurrentOutlet value)? currentOutlet, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CurrentOutletEventCopyWith<$Res> { + factory $CurrentOutletEventCopyWith( + CurrentOutletEvent value, $Res Function(CurrentOutletEvent) then) = + _$CurrentOutletEventCopyWithImpl<$Res, CurrentOutletEvent>; +} + +/// @nodoc +class _$CurrentOutletEventCopyWithImpl<$Res, $Val extends CurrentOutletEvent> + implements $CurrentOutletEventCopyWith<$Res> { + _$CurrentOutletEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CurrentOutletEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$CurrentOutletImplCopyWith<$Res> { + factory _$$CurrentOutletImplCopyWith( + _$CurrentOutletImpl value, $Res Function(_$CurrentOutletImpl) then) = + __$$CurrentOutletImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$CurrentOutletImplCopyWithImpl<$Res> + extends _$CurrentOutletEventCopyWithImpl<$Res, _$CurrentOutletImpl> + implements _$$CurrentOutletImplCopyWith<$Res> { + __$$CurrentOutletImplCopyWithImpl( + _$CurrentOutletImpl _value, $Res Function(_$CurrentOutletImpl) _then) + : super(_value, _then); + + /// Create a copy of CurrentOutletEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$CurrentOutletImpl implements _CurrentOutlet { + const _$CurrentOutletImpl(); + + @override + String toString() { + return 'CurrentOutletEvent.currentOutlet()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$CurrentOutletImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() currentOutlet, + }) { + return currentOutlet(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? currentOutlet, + }) { + return currentOutlet?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? currentOutlet, + required TResult orElse(), + }) { + if (currentOutlet != null) { + return currentOutlet(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_CurrentOutlet value) currentOutlet, + }) { + return currentOutlet(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_CurrentOutlet value)? currentOutlet, + }) { + return currentOutlet?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_CurrentOutlet value)? currentOutlet, + required TResult orElse(), + }) { + if (currentOutlet != null) { + return currentOutlet(this); + } + return orElse(); + } +} + +abstract class _CurrentOutlet implements CurrentOutletEvent { + const factory _CurrentOutlet() = _$CurrentOutletImpl; +} + +/// @nodoc +mixin _$CurrentOutletState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(Outlet outlet) loaded, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(Outlet outlet)? loaded, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Outlet outlet)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + 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? 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 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 $CurrentOutletStateCopyWith<$Res> { + factory $CurrentOutletStateCopyWith( + CurrentOutletState value, $Res Function(CurrentOutletState) then) = + _$CurrentOutletStateCopyWithImpl<$Res, CurrentOutletState>; +} + +/// @nodoc +class _$CurrentOutletStateCopyWithImpl<$Res, $Val extends CurrentOutletState> + implements $CurrentOutletStateCopyWith<$Res> { + _$CurrentOutletStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CurrentOutletState + /// 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 _$CurrentOutletStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of CurrentOutletState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'CurrentOutletState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(Outlet outlet) loaded, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(Outlet outlet)? loaded, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Outlet outlet)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 CurrentOutletState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$CurrentOutletStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of CurrentOutletState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'CurrentOutletState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(Outlet outlet) loaded, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(Outlet outlet)? loaded, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Outlet outlet)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 CurrentOutletState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$LoadedImplCopyWith<$Res> { + factory _$$LoadedImplCopyWith( + _$LoadedImpl value, $Res Function(_$LoadedImpl) then) = + __$$LoadedImplCopyWithImpl<$Res>; + @useResult + $Res call({Outlet outlet}); +} + +/// @nodoc +class __$$LoadedImplCopyWithImpl<$Res> + extends _$CurrentOutletStateCopyWithImpl<$Res, _$LoadedImpl> + implements _$$LoadedImplCopyWith<$Res> { + __$$LoadedImplCopyWithImpl( + _$LoadedImpl _value, $Res Function(_$LoadedImpl) _then) + : super(_value, _then); + + /// Create a copy of CurrentOutletState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? outlet = null, + }) { + return _then(_$LoadedImpl( + null == outlet + ? _value.outlet + : outlet // ignore: cast_nullable_to_non_nullable + as Outlet, + )); + } +} + +/// @nodoc + +class _$LoadedImpl implements _Loaded { + const _$LoadedImpl(this.outlet); + + @override + final Outlet outlet; + + @override + String toString() { + return 'CurrentOutletState.loaded(outlet: $outlet)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoadedImpl && + (identical(other.outlet, outlet) || other.outlet == outlet)); + } + + @override + int get hashCode => Object.hash(runtimeType, outlet); + + /// Create a copy of CurrentOutletState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(Outlet outlet) loaded, + required TResult Function(String message) error, + }) { + return loaded(outlet); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(Outlet outlet)? loaded, + TResult? Function(String message)? error, + }) { + return loaded?.call(outlet); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Outlet outlet)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(outlet); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 CurrentOutletState { + const factory _Loaded(final Outlet outlet) = _$LoadedImpl; + + Outlet get outlet; + + /// Create a copy of CurrentOutletState + /// 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 _$CurrentOutletStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of CurrentOutletState + /// 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 'CurrentOutletState.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 CurrentOutletState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(Outlet outlet) loaded, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(Outlet outlet)? loaded, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Outlet outlet)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 CurrentOutletState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of CurrentOutletState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/home/bloc/current_outlet/current_outlet_event.dart b/lib/presentation/home/bloc/current_outlet/current_outlet_event.dart new file mode 100644 index 0000000..edf3abd --- /dev/null +++ b/lib/presentation/home/bloc/current_outlet/current_outlet_event.dart @@ -0,0 +1,6 @@ +part of 'current_outlet_bloc.dart'; + +@freezed +class CurrentOutletEvent with _$CurrentOutletEvent { + const factory CurrentOutletEvent.currentOutlet() = _CurrentOutlet; +} diff --git a/lib/presentation/home/bloc/current_outlet/current_outlet_state.dart b/lib/presentation/home/bloc/current_outlet/current_outlet_state.dart new file mode 100644 index 0000000..1fd936a --- /dev/null +++ b/lib/presentation/home/bloc/current_outlet/current_outlet_state.dart @@ -0,0 +1,9 @@ +part of 'current_outlet_bloc.dart'; + +@freezed +class CurrentOutletState with _$CurrentOutletState { + const factory CurrentOutletState.initial() = _Initial; + const factory CurrentOutletState.loading() = _Loading; + const factory CurrentOutletState.loaded(Outlet outlet) = _Loaded; + const factory CurrentOutletState.error(String message) = _Error; +} diff --git a/lib/presentation/home/bloc/get_table_status/get_table_status_bloc.dart b/lib/presentation/home/bloc/get_table_status/get_table_status_bloc.dart index 884c4fa..03f0f32 100644 --- a/lib/presentation/home/bloc/get_table_status/get_table_status_bloc.dart +++ b/lib/presentation/home/bloc/get_table_status/get_table_status_bloc.dart @@ -1,5 +1,5 @@ +import 'package:enaklo_pos/data/datasources/table_remote_datasource.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; import 'package:enaklo_pos/data/models/response/table_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -9,12 +9,17 @@ part 'get_table_status_bloc.freezed.dart'; class GetTableStatusBloc extends Bloc { - GetTableStatusBloc() : super(_Initial()) { + final TableRemoteDataSource _tableRemoteDataSource; + GetTableStatusBloc(this._tableRemoteDataSource) : super(_Initial()) { on<_GetTablesStatus>((event, emit) async { emit(_Loading()); final tables = - await ProductLocalDatasource.instance.getTableByStatus(event.status); - emit(_Success(tables)); + await _tableRemoteDataSource.getTable(status: event.status); + + tables.fold( + (failure) => emit(_Error(failure)), + (tableResponse) => emit(_Success(tableResponse.data!.tables!)), + ); }); } } diff --git a/lib/presentation/home/bloc/get_table_status/get_table_status_bloc.freezed.dart b/lib/presentation/home/bloc/get_table_status/get_table_status_bloc.freezed.dart index db73954..1cec23b 100644 --- a/lib/presentation/home/bloc/get_table_status/get_table_status_bloc.freezed.dart +++ b/lib/presentation/home/bloc/get_table_status/get_table_status_bloc.freezed.dart @@ -330,6 +330,7 @@ mixin _$GetTableStatusState { required TResult Function() initial, required TResult Function() loading, required TResult Function(List tables) success, + required TResult Function(String message) error, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -337,6 +338,7 @@ mixin _$GetTableStatusState { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(List tables)? success, + TResult? Function(String message)? error, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -344,6 +346,7 @@ mixin _$GetTableStatusState { TResult Function()? initial, TResult Function()? loading, TResult Function(List tables)? success, + TResult Function(String message)? error, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -352,6 +355,7 @@ mixin _$GetTableStatusState { required TResult Function(_Initial value) initial, required TResult Function(_Loading value) loading, required TResult Function(_Success value) success, + required TResult Function(_Error value) error, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -359,6 +363,7 @@ mixin _$GetTableStatusState { TResult? Function(_Initial value)? initial, TResult? Function(_Loading value)? loading, TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -366,6 +371,7 @@ mixin _$GetTableStatusState { TResult Function(_Initial value)? initial, TResult Function(_Loading value)? loading, TResult Function(_Success value)? success, + TResult Function(_Error value)? error, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -436,6 +442,7 @@ class _$InitialImpl implements _Initial { required TResult Function() initial, required TResult Function() loading, required TResult Function(List tables) success, + required TResult Function(String message) error, }) { return initial(); } @@ -446,6 +453,7 @@ class _$InitialImpl implements _Initial { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(List tables)? success, + TResult? Function(String message)? error, }) { return initial?.call(); } @@ -456,6 +464,7 @@ class _$InitialImpl implements _Initial { TResult Function()? initial, TResult Function()? loading, TResult Function(List tables)? success, + TResult Function(String message)? error, required TResult orElse(), }) { if (initial != null) { @@ -470,6 +479,7 @@ class _$InitialImpl implements _Initial { required TResult Function(_Initial value) initial, required TResult Function(_Loading value) loading, required TResult Function(_Success value) success, + required TResult Function(_Error value) error, }) { return initial(this); } @@ -480,6 +490,7 @@ class _$InitialImpl implements _Initial { TResult? Function(_Initial value)? initial, TResult? Function(_Loading value)? loading, TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, }) { return initial?.call(this); } @@ -490,6 +501,7 @@ class _$InitialImpl implements _Initial { TResult Function(_Initial value)? initial, TResult Function(_Loading value)? loading, TResult Function(_Success value)? success, + TResult Function(_Error value)? error, required TResult orElse(), }) { if (initial != null) { @@ -547,6 +559,7 @@ class _$LoadingImpl implements _Loading { required TResult Function() initial, required TResult Function() loading, required TResult Function(List tables) success, + required TResult Function(String message) error, }) { return loading(); } @@ -557,6 +570,7 @@ class _$LoadingImpl implements _Loading { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(List tables)? success, + TResult? Function(String message)? error, }) { return loading?.call(); } @@ -567,6 +581,7 @@ class _$LoadingImpl implements _Loading { TResult Function()? initial, TResult Function()? loading, TResult Function(List tables)? success, + TResult Function(String message)? error, required TResult orElse(), }) { if (loading != null) { @@ -581,6 +596,7 @@ class _$LoadingImpl implements _Loading { required TResult Function(_Initial value) initial, required TResult Function(_Loading value) loading, required TResult Function(_Success value) success, + required TResult Function(_Error value) error, }) { return loading(this); } @@ -591,6 +607,7 @@ class _$LoadingImpl implements _Loading { TResult? Function(_Initial value)? initial, TResult? Function(_Loading value)? loading, TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, }) { return loading?.call(this); } @@ -601,6 +618,7 @@ class _$LoadingImpl implements _Loading { TResult Function(_Initial value)? initial, TResult Function(_Loading value)? loading, TResult Function(_Success value)? success, + TResult Function(_Error value)? error, required TResult orElse(), }) { if (loading != null) { @@ -691,6 +709,7 @@ class _$SuccessImpl implements _Success { required TResult Function() initial, required TResult Function() loading, required TResult Function(List tables) success, + required TResult Function(String message) error, }) { return success(tables); } @@ -701,6 +720,7 @@ class _$SuccessImpl implements _Success { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(List tables)? success, + TResult? Function(String message)? error, }) { return success?.call(tables); } @@ -711,6 +731,7 @@ class _$SuccessImpl implements _Success { TResult Function()? initial, TResult Function()? loading, TResult Function(List tables)? success, + TResult Function(String message)? error, required TResult orElse(), }) { if (success != null) { @@ -725,6 +746,7 @@ class _$SuccessImpl implements _Success { required TResult Function(_Initial value) initial, required TResult Function(_Loading value) loading, required TResult Function(_Success value) success, + required TResult Function(_Error value) error, }) { return success(this); } @@ -735,6 +757,7 @@ class _$SuccessImpl implements _Success { TResult? Function(_Initial value)? initial, TResult? Function(_Loading value)? loading, TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, }) { return success?.call(this); } @@ -745,6 +768,7 @@ class _$SuccessImpl implements _Success { TResult Function(_Initial value)? initial, TResult Function(_Loading value)? loading, TResult Function(_Success value)? success, + TResult Function(_Error value)? error, required TResult orElse(), }) { if (success != null) { @@ -765,3 +789,155 @@ abstract class _Success implements GetTableStatusState { _$$SuccessImplCopyWith<_$SuccessImpl> 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 _$GetTableStatusStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of GetTableStatusState + /// 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 'GetTableStatusState.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 GetTableStatusState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List tables) success, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List tables)? success, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List tables)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements GetTableStatusState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of GetTableStatusState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/home/bloc/get_table_status/get_table_status_state.dart b/lib/presentation/home/bloc/get_table_status/get_table_status_state.dart index 397566a..d42c5e5 100644 --- a/lib/presentation/home/bloc/get_table_status/get_table_status_state.dart +++ b/lib/presentation/home/bloc/get_table_status/get_table_status_state.dart @@ -5,4 +5,5 @@ class GetTableStatusState with _$GetTableStatusState { const factory GetTableStatusState.initial() = _Initial; const factory GetTableStatusState.loading() = _Loading; const factory GetTableStatusState.success(List tables) = _Success; + const factory GetTableStatusState.error(String message) = _Error; } diff --git a/lib/presentation/home/bloc/order/order_bloc.dart b/lib/presentation/home/bloc/order/order_bloc.dart index 9a303bc..9aaa63f 100644 --- a/lib/presentation/home/bloc/order/order_bloc.dart +++ b/lib/presentation/home/bloc/order/order_bloc.dart @@ -3,7 +3,6 @@ import 'dart:developer'; import 'package:bloc/bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:enaklo_pos/core/extensions/string_ext.dart'; import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart'; import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; @@ -33,8 +32,7 @@ class OrderBloc extends Bloc { final subTotal = event.items.fold( 0, (previousValue, element) => - previousValue + - (element.product.price!.toIntegerFromText * element.quantity)); + previousValue + (element.product.price! * element.quantity)); // final total = subTotal + event.tax + event.serviceCharge - event.discount; final totalItem = event.items.fold( @@ -52,7 +50,7 @@ class OrderBloc extends Bloc { total: event.totalPriceFinal, paymentMethod: event.paymentMethod, totalItem: totalItem, - idKasir: userData.user?.id ?? 1, + idKasir: int.parse(userData.user!.id ?? "0"), namaKasir: userData.user?.name ?? 'Kasir A', transactionTime: DateTime.now().toIso8601String(), customerName: event.customerName, @@ -66,11 +64,11 @@ class OrderBloc extends Bloc { log("Start 2"); //check state online or offline - + log("🔄 About to call API to save order..."); log("📤 Order data being sent to API: ${dataInput.toServerMap()}"); log("🌐 OrderRemoteDatasource instance: $orderRemoteDatasource"); - + bool value = false; try { value = await orderRemoteDatasource.saveOrder(dataInput); diff --git a/lib/presentation/home/bloc/order_form/order_form_bloc.dart b/lib/presentation/home/bloc/order_form/order_form_bloc.dart new file mode 100644 index 0000000..85236c5 --- /dev/null +++ b/lib/presentation/home/bloc/order_form/order_form_bloc.dart @@ -0,0 +1,213 @@ +import 'dart:developer'; + +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; +import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:enaklo_pos/data/models/response/payment_methods_response_model.dart'; +import 'package:enaklo_pos/data/models/response/table_model.dart'; +import 'package:enaklo_pos/presentation/home/models/order_request.dart'; +import 'package:enaklo_pos/presentation/home/models/order_type.dart'; +import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'order_form_event.dart'; +part 'order_form_state.dart'; +part 'order_form_bloc.freezed.dart'; + +class OrderFormBloc extends Bloc { + final OrderRemoteDatasource _orderRemoteDatasource; + OrderFormBloc(this._orderRemoteDatasource) : super(OrderFormState.initial()) { + on<_Started>((event, emit) { + emit(OrderFormState.loaded(order: event.order)); + }); + on<_Create>( + (event, emit) async { + emit(const _Loading()); + + try { + // Convert ProductQuantity list to the format expected by the API + + final userData = await AuthLocalDataSource().getAuthData(); + + final orderItems = OrderRequestModel( + customerName: event.customerName, + notes: '', + orderType: event.orderType.name, + tableId: event.table?.id, + tableNumber: event.table?.tableName, + outletId: userData.user?.outletId, + customerId: event.customer?.id ?? '', + orderItems: event.items + .map( + (productQuantity) => OrderItemRequest( + productId: productQuantity.product.id, + quantity: productQuantity.quantity, + notes: productQuantity.notes, + unitPrice: productQuantity.product.price, + productVariantId: productQuantity.variant?.id, + ), + ) + .toList(), + ); + + final result = await _orderRemoteDatasource.createOrder(orderItems); + + result.fold( + (error) => emit(_Error(error)), + (success) => emit(_Success(success.data!)), + ); + } catch (e) { + log("Error in AddOrderItemsBloc: $e"); + emit(_Error("Failed to add order items: $e")); + } + }, + ); + on<_CreateWithPaymentMethod>( + (event, emit) async { + emit(const _Loading()); + + try { + // Convert ProductQuantity list to the format expected by the API + + final userData = await AuthLocalDataSource().getAuthData(); + + final orderItems = OrderRequestModel( + customerName: event.customerName, + notes: '', + orderType: event.orderType.name, + tableId: event.table?.id ?? "", + tableNumber: event.table?.tableName ?? "", + outletId: userData.user?.outletId, + customerId: event.customer?.id ?? '', + orderItems: event.items + .map( + (productQuantity) => OrderItemRequest( + productId: productQuantity.product.id, + quantity: productQuantity.quantity, + notes: productQuantity.notes, + unitPrice: productQuantity.product.price, + productVariantId: productQuantity.variant?.id, + ), + ) + .toList(), + ); + + final result = await _orderRemoteDatasource.createOrderWithPayment( + orderItems, + event.paymentMethod, + ); + + result.fold( + (error) => emit(_Error(error)), + (success) => emit(_Success(success.data!)), + ); + } catch (e) { + log("Error in AddOrderItemsBloc: $e"); + emit(_Error("Failed to add order items: $e")); + } + }, + ); + on<_AddToOrder>( + (event, emit) async { + emit(const _Loading()); + + try { + final result = await _orderRemoteDatasource.addToOrder( + orderId: event.orderId, + orderItems: event.items + .map( + (item) => OrderItemRequest( + productId: item.product.id, + quantity: item.quantity, + unitPrice: item.product.price, + notes: item.notes, + productVariantId: item.variant?.id, + ), + ) + .toList(), + ); + + result.fold( + (error) => emit(_Error(error)), + (success) => emit(_SuccessMsg()), + ); + } catch (e) { + log("Error in AddOrderItemsBloc: $e"); + emit(_Error("Ada kesalahan. Coba lagi nanti")); + } + }, + ); + on<_ToggleItem>((event, emit) { + state.maybeWhen( + loaded: (order, selectedItems, totalVoidOrRefund, _) { + final newList = [...selectedItems]; + final exists = newList.any((e) => e.id == event.item.id); + + if (exists) { + newList.removeWhere((e) => e.id == event.item.id); + } else { + newList.add(event.item); + } + + final isAll = newList.length == (order.orderItems?.length ?? 0); + + emit(OrderFormState.loaded( + order: order, + selectedItems: newList, + isAllSelected: isAll, + )); + }, + orElse: () {}, + ); + }); + + on<_ToggleSelectAll>((event, emit) { + state.maybeWhen( + loaded: (order, _, totalVoidOrRefund, __) { + final items = event.selectAll + ? (order.orderItems ?? []) + : []; + + emit(OrderFormState.loaded( + order: order, + selectedItems: items, + isAllSelected: event.selectAll, + )); + }, + orElse: () {}, + ); + }); + + on<_Refund>((event, emit) async { + emit(const OrderFormState.loading()); + + final result = await _orderRemoteDatasource.refund( + orderId: event.orderId, + reason: event.reason, + orderItems: event.items, + ); + + result.fold( + (error) => emit(_Error(error)), + (success) => emit(_SuccessMsg()), + ); + }); + + on<_VoidOrder>((event, emit) async { + emit(const OrderFormState.loading()); + + final result = await _orderRemoteDatasource.voidOrder( + orderId: event.orderId, + reason: event.reason, + orderItems: event.items, + ); + + result.fold( + (error) => emit(_Error(error)), + (success) => emit(_SuccessMsg()), + ); + }); + } +} diff --git a/lib/presentation/home/bloc/order_form/order_form_bloc.freezed.dart b/lib/presentation/home/bloc/order_form/order_form_bloc.freezed.dart new file mode 100644 index 0000000..d0b2688 --- /dev/null +++ b/lib/presentation/home/bloc/order_form/order_form_bloc.freezed.dart @@ -0,0 +1,3152 @@ +// 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 'order_form_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$OrderFormEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(Order order) started, + required TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table) + create, + required TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod) + createWithPayment, + required TResult Function(List items, String orderId) + addToOrder, + required TResult Function( + List items, String orderId, String reason) + refund, + required TResult Function( + List items, String orderId, String reason) + voidOrder, + required TResult Function(OrderItem item) toggleItem, + required TResult Function(bool selectAll) toggleSelectAll, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(Order order)? started, + TResult? Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult? Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult? Function(List items, String orderId)? addToOrder, + TResult? Function(List items, String orderId, String reason)? + refund, + TResult? Function(List items, String orderId, String reason)? + voidOrder, + TResult? Function(OrderItem item)? toggleItem, + TResult? Function(bool selectAll)? toggleSelectAll, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(Order order)? started, + TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult Function(List items, String orderId)? addToOrder, + TResult Function(List items, String orderId, String reason)? + refund, + TResult Function(List items, String orderId, String reason)? + voidOrder, + TResult Function(OrderItem item)? toggleItem, + TResult Function(bool selectAll)? toggleSelectAll, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Started value) started, + required TResult Function(_Create value) create, + required TResult Function(_CreateWithPaymentMethod value) createWithPayment, + required TResult Function(_AddToOrder value) addToOrder, + required TResult Function(_Refund value) refund, + required TResult Function(_VoidOrder value) voidOrder, + required TResult Function(_ToggleItem value) toggleItem, + required TResult Function(_ToggleSelectAll value) toggleSelectAll, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Started value)? started, + TResult? Function(_Create value)? create, + TResult? Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult? Function(_AddToOrder value)? addToOrder, + TResult? Function(_Refund value)? refund, + TResult? Function(_VoidOrder value)? voidOrder, + TResult? Function(_ToggleItem value)? toggleItem, + TResult? Function(_ToggleSelectAll value)? toggleSelectAll, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Started value)? started, + TResult Function(_Create value)? create, + TResult Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult Function(_AddToOrder value)? addToOrder, + TResult Function(_Refund value)? refund, + TResult Function(_VoidOrder value)? voidOrder, + TResult Function(_ToggleItem value)? toggleItem, + TResult Function(_ToggleSelectAll value)? toggleSelectAll, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $OrderFormEventCopyWith<$Res> { + factory $OrderFormEventCopyWith( + OrderFormEvent value, $Res Function(OrderFormEvent) then) = + _$OrderFormEventCopyWithImpl<$Res, OrderFormEvent>; +} + +/// @nodoc +class _$OrderFormEventCopyWithImpl<$Res, $Val extends OrderFormEvent> + implements $OrderFormEventCopyWith<$Res> { + _$OrderFormEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$StartedImplCopyWith<$Res> { + factory _$$StartedImplCopyWith( + _$StartedImpl value, $Res Function(_$StartedImpl) then) = + __$$StartedImplCopyWithImpl<$Res>; + @useResult + $Res call({Order order}); +} + +/// @nodoc +class __$$StartedImplCopyWithImpl<$Res> + extends _$OrderFormEventCopyWithImpl<$Res, _$StartedImpl> + implements _$$StartedImplCopyWith<$Res> { + __$$StartedImplCopyWithImpl( + _$StartedImpl _value, $Res Function(_$StartedImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? order = null, + }) { + return _then(_$StartedImpl( + null == order + ? _value.order + : order // ignore: cast_nullable_to_non_nullable + as Order, + )); + } +} + +/// @nodoc + +class _$StartedImpl implements _Started { + const _$StartedImpl(this.order); + + @override + final Order order; + + @override + String toString() { + return 'OrderFormEvent.started(order: $order)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$StartedImpl && + (identical(other.order, order) || other.order == order)); + } + + @override + int get hashCode => Object.hash(runtimeType, order); + + /// Create a copy of OrderFormEvent + /// 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 + @optionalTypeArgs + TResult when({ + required TResult Function(Order order) started, + required TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table) + create, + required TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod) + createWithPayment, + required TResult Function(List items, String orderId) + addToOrder, + required TResult Function( + List items, String orderId, String reason) + refund, + required TResult Function( + List items, String orderId, String reason) + voidOrder, + required TResult Function(OrderItem item) toggleItem, + required TResult Function(bool selectAll) toggleSelectAll, + }) { + return started(order); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(Order order)? started, + TResult? Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult? Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult? Function(List items, String orderId)? addToOrder, + TResult? Function(List items, String orderId, String reason)? + refund, + TResult? Function(List items, String orderId, String reason)? + voidOrder, + TResult? Function(OrderItem item)? toggleItem, + TResult? Function(bool selectAll)? toggleSelectAll, + }) { + return started?.call(order); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(Order order)? started, + TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult Function(List items, String orderId)? addToOrder, + TResult Function(List items, String orderId, String reason)? + refund, + TResult Function(List items, String orderId, String reason)? + voidOrder, + TResult Function(OrderItem item)? toggleItem, + TResult Function(bool selectAll)? toggleSelectAll, + required TResult orElse(), + }) { + if (started != null) { + return started(order); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Started value) started, + required TResult Function(_Create value) create, + required TResult Function(_CreateWithPaymentMethod value) createWithPayment, + required TResult Function(_AddToOrder value) addToOrder, + required TResult Function(_Refund value) refund, + required TResult Function(_VoidOrder value) voidOrder, + required TResult Function(_ToggleItem value) toggleItem, + required TResult Function(_ToggleSelectAll value) toggleSelectAll, + }) { + return started(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Started value)? started, + TResult? Function(_Create value)? create, + TResult? Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult? Function(_AddToOrder value)? addToOrder, + TResult? Function(_Refund value)? refund, + TResult? Function(_VoidOrder value)? voidOrder, + TResult? Function(_ToggleItem value)? toggleItem, + TResult? Function(_ToggleSelectAll value)? toggleSelectAll, + }) { + return started?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Started value)? started, + TResult Function(_Create value)? create, + TResult Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult Function(_AddToOrder value)? addToOrder, + TResult Function(_Refund value)? refund, + TResult Function(_VoidOrder value)? voidOrder, + TResult Function(_ToggleItem value)? toggleItem, + TResult Function(_ToggleSelectAll value)? toggleSelectAll, + required TResult orElse(), + }) { + if (started != null) { + return started(this); + } + return orElse(); + } +} + +abstract class _Started implements OrderFormEvent { + const factory _Started(final Order order) = _$StartedImpl; + + Order get order; + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$StartedImplCopyWith<_$StartedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$CreateImplCopyWith<$Res> { + factory _$$CreateImplCopyWith( + _$CreateImpl value, $Res Function(_$CreateImpl) then) = + __$$CreateImplCopyWithImpl<$Res>; + @useResult + $Res call( + {List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table}); +} + +/// @nodoc +class __$$CreateImplCopyWithImpl<$Res> + extends _$OrderFormEventCopyWithImpl<$Res, _$CreateImpl> + implements _$$CreateImplCopyWith<$Res> { + __$$CreateImplCopyWithImpl( + _$CreateImpl _value, $Res Function(_$CreateImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? items = null, + Object? customerName = null, + Object? customer = freezed, + Object? orderType = null, + Object? table = freezed, + }) { + return _then(_$CreateImpl( + items: null == items + ? _value._items + : items // ignore: cast_nullable_to_non_nullable + as List, + customerName: null == customerName + ? _value.customerName + : customerName // ignore: cast_nullable_to_non_nullable + as String, + customer: freezed == customer + ? _value.customer + : customer // ignore: cast_nullable_to_non_nullable + as Customer?, + orderType: null == orderType + ? _value.orderType + : orderType // ignore: cast_nullable_to_non_nullable + as OrderType, + table: freezed == table + ? _value.table + : table // ignore: cast_nullable_to_non_nullable + as TableModel?, + )); + } +} + +/// @nodoc + +class _$CreateImpl implements _Create { + const _$CreateImpl( + {required final List items, + required this.customerName, + required this.customer, + required this.orderType, + required this.table}) + : _items = items; + + final List _items; + @override + List get items { + if (_items is EqualUnmodifiableListView) return _items; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_items); + } + + @override + final String customerName; + @override + final Customer? customer; + @override + final OrderType orderType; + @override + final TableModel? table; + + @override + String toString() { + return 'OrderFormEvent.create(items: $items, customerName: $customerName, customer: $customer, orderType: $orderType, table: $table)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CreateImpl && + const DeepCollectionEquality().equals(other._items, _items) && + (identical(other.customerName, customerName) || + other.customerName == customerName) && + (identical(other.customer, customer) || + other.customer == customer) && + (identical(other.orderType, orderType) || + other.orderType == orderType) && + (identical(other.table, table) || other.table == table)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_items), + customerName, + customer, + orderType, + table); + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CreateImplCopyWith<_$CreateImpl> get copyWith => + __$$CreateImplCopyWithImpl<_$CreateImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(Order order) started, + required TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table) + create, + required TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod) + createWithPayment, + required TResult Function(List items, String orderId) + addToOrder, + required TResult Function( + List items, String orderId, String reason) + refund, + required TResult Function( + List items, String orderId, String reason) + voidOrder, + required TResult Function(OrderItem item) toggleItem, + required TResult Function(bool selectAll) toggleSelectAll, + }) { + return create(items, customerName, customer, orderType, table); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(Order order)? started, + TResult? Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult? Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult? Function(List items, String orderId)? addToOrder, + TResult? Function(List items, String orderId, String reason)? + refund, + TResult? Function(List items, String orderId, String reason)? + voidOrder, + TResult? Function(OrderItem item)? toggleItem, + TResult? Function(bool selectAll)? toggleSelectAll, + }) { + return create?.call(items, customerName, customer, orderType, table); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(Order order)? started, + TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult Function(List items, String orderId)? addToOrder, + TResult Function(List items, String orderId, String reason)? + refund, + TResult Function(List items, String orderId, String reason)? + voidOrder, + TResult Function(OrderItem item)? toggleItem, + TResult Function(bool selectAll)? toggleSelectAll, + required TResult orElse(), + }) { + if (create != null) { + return create(items, customerName, customer, orderType, table); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Started value) started, + required TResult Function(_Create value) create, + required TResult Function(_CreateWithPaymentMethod value) createWithPayment, + required TResult Function(_AddToOrder value) addToOrder, + required TResult Function(_Refund value) refund, + required TResult Function(_VoidOrder value) voidOrder, + required TResult Function(_ToggleItem value) toggleItem, + required TResult Function(_ToggleSelectAll value) toggleSelectAll, + }) { + return create(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Started value)? started, + TResult? Function(_Create value)? create, + TResult? Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult? Function(_AddToOrder value)? addToOrder, + TResult? Function(_Refund value)? refund, + TResult? Function(_VoidOrder value)? voidOrder, + TResult? Function(_ToggleItem value)? toggleItem, + TResult? Function(_ToggleSelectAll value)? toggleSelectAll, + }) { + return create?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Started value)? started, + TResult Function(_Create value)? create, + TResult Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult Function(_AddToOrder value)? addToOrder, + TResult Function(_Refund value)? refund, + TResult Function(_VoidOrder value)? voidOrder, + TResult Function(_ToggleItem value)? toggleItem, + TResult Function(_ToggleSelectAll value)? toggleSelectAll, + required TResult orElse(), + }) { + if (create != null) { + return create(this); + } + return orElse(); + } +} + +abstract class _Create implements OrderFormEvent { + const factory _Create( + {required final List items, + required final String customerName, + required final Customer? customer, + required final OrderType orderType, + required final TableModel? table}) = _$CreateImpl; + + List get items; + String get customerName; + Customer? get customer; + OrderType get orderType; + TableModel? get table; + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CreateImplCopyWith<_$CreateImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$CreateWithPaymentMethodImplCopyWith<$Res> { + factory _$$CreateWithPaymentMethodImplCopyWith( + _$CreateWithPaymentMethodImpl value, + $Res Function(_$CreateWithPaymentMethodImpl) then) = + __$$CreateWithPaymentMethodImplCopyWithImpl<$Res>; + @useResult + $Res call( + {List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod}); +} + +/// @nodoc +class __$$CreateWithPaymentMethodImplCopyWithImpl<$Res> + extends _$OrderFormEventCopyWithImpl<$Res, _$CreateWithPaymentMethodImpl> + implements _$$CreateWithPaymentMethodImplCopyWith<$Res> { + __$$CreateWithPaymentMethodImplCopyWithImpl( + _$CreateWithPaymentMethodImpl _value, + $Res Function(_$CreateWithPaymentMethodImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? items = null, + Object? customerName = null, + Object? customer = freezed, + Object? orderType = null, + Object? table = freezed, + Object? paymentMethod = null, + }) { + return _then(_$CreateWithPaymentMethodImpl( + items: null == items + ? _value._items + : items // ignore: cast_nullable_to_non_nullable + as List, + customerName: null == customerName + ? _value.customerName + : customerName // ignore: cast_nullable_to_non_nullable + as String, + customer: freezed == customer + ? _value.customer + : customer // ignore: cast_nullable_to_non_nullable + as Customer?, + orderType: null == orderType + ? _value.orderType + : orderType // ignore: cast_nullable_to_non_nullable + as OrderType, + table: freezed == table + ? _value.table + : table // ignore: cast_nullable_to_non_nullable + as TableModel?, + paymentMethod: null == paymentMethod + ? _value.paymentMethod + : paymentMethod // ignore: cast_nullable_to_non_nullable + as PaymentMethod, + )); + } +} + +/// @nodoc + +class _$CreateWithPaymentMethodImpl implements _CreateWithPaymentMethod { + const _$CreateWithPaymentMethodImpl( + {required final List items, + required this.customerName, + required this.customer, + required this.orderType, + required this.table, + required this.paymentMethod}) + : _items = items; + + final List _items; + @override + List get items { + if (_items is EqualUnmodifiableListView) return _items; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_items); + } + + @override + final String customerName; + @override + final Customer? customer; + @override + final OrderType orderType; + @override + final TableModel? table; + @override + final PaymentMethod paymentMethod; + + @override + String toString() { + return 'OrderFormEvent.createWithPayment(items: $items, customerName: $customerName, customer: $customer, orderType: $orderType, table: $table, paymentMethod: $paymentMethod)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CreateWithPaymentMethodImpl && + const DeepCollectionEquality().equals(other._items, _items) && + (identical(other.customerName, customerName) || + other.customerName == customerName) && + (identical(other.customer, customer) || + other.customer == customer) && + (identical(other.orderType, orderType) || + other.orderType == orderType) && + (identical(other.table, table) || other.table == table) && + (identical(other.paymentMethod, paymentMethod) || + other.paymentMethod == paymentMethod)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_items), + customerName, + customer, + orderType, + table, + paymentMethod); + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CreateWithPaymentMethodImplCopyWith<_$CreateWithPaymentMethodImpl> + get copyWith => __$$CreateWithPaymentMethodImplCopyWithImpl< + _$CreateWithPaymentMethodImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(Order order) started, + required TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table) + create, + required TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod) + createWithPayment, + required TResult Function(List items, String orderId) + addToOrder, + required TResult Function( + List items, String orderId, String reason) + refund, + required TResult Function( + List items, String orderId, String reason) + voidOrder, + required TResult Function(OrderItem item) toggleItem, + required TResult Function(bool selectAll) toggleSelectAll, + }) { + return createWithPayment( + items, customerName, customer, orderType, table, paymentMethod); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(Order order)? started, + TResult? Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult? Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult? Function(List items, String orderId)? addToOrder, + TResult? Function(List items, String orderId, String reason)? + refund, + TResult? Function(List items, String orderId, String reason)? + voidOrder, + TResult? Function(OrderItem item)? toggleItem, + TResult? Function(bool selectAll)? toggleSelectAll, + }) { + return createWithPayment?.call( + items, customerName, customer, orderType, table, paymentMethod); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(Order order)? started, + TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult Function(List items, String orderId)? addToOrder, + TResult Function(List items, String orderId, String reason)? + refund, + TResult Function(List items, String orderId, String reason)? + voidOrder, + TResult Function(OrderItem item)? toggleItem, + TResult Function(bool selectAll)? toggleSelectAll, + required TResult orElse(), + }) { + if (createWithPayment != null) { + return createWithPayment( + items, customerName, customer, orderType, table, paymentMethod); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Started value) started, + required TResult Function(_Create value) create, + required TResult Function(_CreateWithPaymentMethod value) createWithPayment, + required TResult Function(_AddToOrder value) addToOrder, + required TResult Function(_Refund value) refund, + required TResult Function(_VoidOrder value) voidOrder, + required TResult Function(_ToggleItem value) toggleItem, + required TResult Function(_ToggleSelectAll value) toggleSelectAll, + }) { + return createWithPayment(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Started value)? started, + TResult? Function(_Create value)? create, + TResult? Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult? Function(_AddToOrder value)? addToOrder, + TResult? Function(_Refund value)? refund, + TResult? Function(_VoidOrder value)? voidOrder, + TResult? Function(_ToggleItem value)? toggleItem, + TResult? Function(_ToggleSelectAll value)? toggleSelectAll, + }) { + return createWithPayment?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Started value)? started, + TResult Function(_Create value)? create, + TResult Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult Function(_AddToOrder value)? addToOrder, + TResult Function(_Refund value)? refund, + TResult Function(_VoidOrder value)? voidOrder, + TResult Function(_ToggleItem value)? toggleItem, + TResult Function(_ToggleSelectAll value)? toggleSelectAll, + required TResult orElse(), + }) { + if (createWithPayment != null) { + return createWithPayment(this); + } + return orElse(); + } +} + +abstract class _CreateWithPaymentMethod implements OrderFormEvent { + const factory _CreateWithPaymentMethod( + {required final List items, + required final String customerName, + required final Customer? customer, + required final OrderType orderType, + required final TableModel? table, + required final PaymentMethod paymentMethod}) = + _$CreateWithPaymentMethodImpl; + + List get items; + String get customerName; + Customer? get customer; + OrderType get orderType; + TableModel? get table; + PaymentMethod get paymentMethod; + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CreateWithPaymentMethodImplCopyWith<_$CreateWithPaymentMethodImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$AddToOrderImplCopyWith<$Res> { + factory _$$AddToOrderImplCopyWith( + _$AddToOrderImpl value, $Res Function(_$AddToOrderImpl) then) = + __$$AddToOrderImplCopyWithImpl<$Res>; + @useResult + $Res call({List items, String orderId}); +} + +/// @nodoc +class __$$AddToOrderImplCopyWithImpl<$Res> + extends _$OrderFormEventCopyWithImpl<$Res, _$AddToOrderImpl> + implements _$$AddToOrderImplCopyWith<$Res> { + __$$AddToOrderImplCopyWithImpl( + _$AddToOrderImpl _value, $Res Function(_$AddToOrderImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? items = null, + Object? orderId = null, + }) { + return _then(_$AddToOrderImpl( + items: null == items + ? _value._items + : items // ignore: cast_nullable_to_non_nullable + as List, + orderId: null == orderId + ? _value.orderId + : orderId // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$AddToOrderImpl implements _AddToOrder { + const _$AddToOrderImpl( + {required final List items, required this.orderId}) + : _items = items; + + final List _items; + @override + List get items { + if (_items is EqualUnmodifiableListView) return _items; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_items); + } + + @override + final String orderId; + + @override + String toString() { + return 'OrderFormEvent.addToOrder(items: $items, orderId: $orderId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AddToOrderImpl && + const DeepCollectionEquality().equals(other._items, _items) && + (identical(other.orderId, orderId) || other.orderId == orderId)); + } + + @override + int get hashCode => Object.hash( + runtimeType, const DeepCollectionEquality().hash(_items), orderId); + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$AddToOrderImplCopyWith<_$AddToOrderImpl> get copyWith => + __$$AddToOrderImplCopyWithImpl<_$AddToOrderImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(Order order) started, + required TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table) + create, + required TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod) + createWithPayment, + required TResult Function(List items, String orderId) + addToOrder, + required TResult Function( + List items, String orderId, String reason) + refund, + required TResult Function( + List items, String orderId, String reason) + voidOrder, + required TResult Function(OrderItem item) toggleItem, + required TResult Function(bool selectAll) toggleSelectAll, + }) { + return addToOrder(items, orderId); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(Order order)? started, + TResult? Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult? Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult? Function(List items, String orderId)? addToOrder, + TResult? Function(List items, String orderId, String reason)? + refund, + TResult? Function(List items, String orderId, String reason)? + voidOrder, + TResult? Function(OrderItem item)? toggleItem, + TResult? Function(bool selectAll)? toggleSelectAll, + }) { + return addToOrder?.call(items, orderId); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(Order order)? started, + TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult Function(List items, String orderId)? addToOrder, + TResult Function(List items, String orderId, String reason)? + refund, + TResult Function(List items, String orderId, String reason)? + voidOrder, + TResult Function(OrderItem item)? toggleItem, + TResult Function(bool selectAll)? toggleSelectAll, + required TResult orElse(), + }) { + if (addToOrder != null) { + return addToOrder(items, orderId); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Started value) started, + required TResult Function(_Create value) create, + required TResult Function(_CreateWithPaymentMethod value) createWithPayment, + required TResult Function(_AddToOrder value) addToOrder, + required TResult Function(_Refund value) refund, + required TResult Function(_VoidOrder value) voidOrder, + required TResult Function(_ToggleItem value) toggleItem, + required TResult Function(_ToggleSelectAll value) toggleSelectAll, + }) { + return addToOrder(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Started value)? started, + TResult? Function(_Create value)? create, + TResult? Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult? Function(_AddToOrder value)? addToOrder, + TResult? Function(_Refund value)? refund, + TResult? Function(_VoidOrder value)? voidOrder, + TResult? Function(_ToggleItem value)? toggleItem, + TResult? Function(_ToggleSelectAll value)? toggleSelectAll, + }) { + return addToOrder?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Started value)? started, + TResult Function(_Create value)? create, + TResult Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult Function(_AddToOrder value)? addToOrder, + TResult Function(_Refund value)? refund, + TResult Function(_VoidOrder value)? voidOrder, + TResult Function(_ToggleItem value)? toggleItem, + TResult Function(_ToggleSelectAll value)? toggleSelectAll, + required TResult orElse(), + }) { + if (addToOrder != null) { + return addToOrder(this); + } + return orElse(); + } +} + +abstract class _AddToOrder implements OrderFormEvent { + const factory _AddToOrder( + {required final List items, + required final String orderId}) = _$AddToOrderImpl; + + List get items; + String get orderId; + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$AddToOrderImplCopyWith<_$AddToOrderImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$RefundImplCopyWith<$Res> { + factory _$$RefundImplCopyWith( + _$RefundImpl value, $Res Function(_$RefundImpl) then) = + __$$RefundImplCopyWithImpl<$Res>; + @useResult + $Res call({List items, String orderId, String reason}); +} + +/// @nodoc +class __$$RefundImplCopyWithImpl<$Res> + extends _$OrderFormEventCopyWithImpl<$Res, _$RefundImpl> + implements _$$RefundImplCopyWith<$Res> { + __$$RefundImplCopyWithImpl( + _$RefundImpl _value, $Res Function(_$RefundImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? items = null, + Object? orderId = null, + Object? reason = null, + }) { + return _then(_$RefundImpl( + items: null == items + ? _value._items + : items // ignore: cast_nullable_to_non_nullable + as List, + orderId: null == orderId + ? _value.orderId + : orderId // ignore: cast_nullable_to_non_nullable + as String, + reason: null == reason + ? _value.reason + : reason // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$RefundImpl implements _Refund { + const _$RefundImpl( + {required final List items, + required this.orderId, + required this.reason}) + : _items = items; + + final List _items; + @override + List get items { + if (_items is EqualUnmodifiableListView) return _items; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_items); + } + + @override + final String orderId; + @override + final String reason; + + @override + String toString() { + return 'OrderFormEvent.refund(items: $items, orderId: $orderId, reason: $reason)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RefundImpl && + const DeepCollectionEquality().equals(other._items, _items) && + (identical(other.orderId, orderId) || other.orderId == orderId) && + (identical(other.reason, reason) || other.reason == reason)); + } + + @override + int get hashCode => Object.hash(runtimeType, + const DeepCollectionEquality().hash(_items), orderId, reason); + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$RefundImplCopyWith<_$RefundImpl> get copyWith => + __$$RefundImplCopyWithImpl<_$RefundImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(Order order) started, + required TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table) + create, + required TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod) + createWithPayment, + required TResult Function(List items, String orderId) + addToOrder, + required TResult Function( + List items, String orderId, String reason) + refund, + required TResult Function( + List items, String orderId, String reason) + voidOrder, + required TResult Function(OrderItem item) toggleItem, + required TResult Function(bool selectAll) toggleSelectAll, + }) { + return refund(items, orderId, reason); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(Order order)? started, + TResult? Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult? Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult? Function(List items, String orderId)? addToOrder, + TResult? Function(List items, String orderId, String reason)? + refund, + TResult? Function(List items, String orderId, String reason)? + voidOrder, + TResult? Function(OrderItem item)? toggleItem, + TResult? Function(bool selectAll)? toggleSelectAll, + }) { + return refund?.call(items, orderId, reason); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(Order order)? started, + TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult Function(List items, String orderId)? addToOrder, + TResult Function(List items, String orderId, String reason)? + refund, + TResult Function(List items, String orderId, String reason)? + voidOrder, + TResult Function(OrderItem item)? toggleItem, + TResult Function(bool selectAll)? toggleSelectAll, + required TResult orElse(), + }) { + if (refund != null) { + return refund(items, orderId, reason); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Started value) started, + required TResult Function(_Create value) create, + required TResult Function(_CreateWithPaymentMethod value) createWithPayment, + required TResult Function(_AddToOrder value) addToOrder, + required TResult Function(_Refund value) refund, + required TResult Function(_VoidOrder value) voidOrder, + required TResult Function(_ToggleItem value) toggleItem, + required TResult Function(_ToggleSelectAll value) toggleSelectAll, + }) { + return refund(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Started value)? started, + TResult? Function(_Create value)? create, + TResult? Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult? Function(_AddToOrder value)? addToOrder, + TResult? Function(_Refund value)? refund, + TResult? Function(_VoidOrder value)? voidOrder, + TResult? Function(_ToggleItem value)? toggleItem, + TResult? Function(_ToggleSelectAll value)? toggleSelectAll, + }) { + return refund?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Started value)? started, + TResult Function(_Create value)? create, + TResult Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult Function(_AddToOrder value)? addToOrder, + TResult Function(_Refund value)? refund, + TResult Function(_VoidOrder value)? voidOrder, + TResult Function(_ToggleItem value)? toggleItem, + TResult Function(_ToggleSelectAll value)? toggleSelectAll, + required TResult orElse(), + }) { + if (refund != null) { + return refund(this); + } + return orElse(); + } +} + +abstract class _Refund implements OrderFormEvent { + const factory _Refund( + {required final List items, + required final String orderId, + required final String reason}) = _$RefundImpl; + + List get items; + String get orderId; + String get reason; + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RefundImplCopyWith<_$RefundImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$VoidOrderImplCopyWith<$Res> { + factory _$$VoidOrderImplCopyWith( + _$VoidOrderImpl value, $Res Function(_$VoidOrderImpl) then) = + __$$VoidOrderImplCopyWithImpl<$Res>; + @useResult + $Res call({List items, String orderId, String reason}); +} + +/// @nodoc +class __$$VoidOrderImplCopyWithImpl<$Res> + extends _$OrderFormEventCopyWithImpl<$Res, _$VoidOrderImpl> + implements _$$VoidOrderImplCopyWith<$Res> { + __$$VoidOrderImplCopyWithImpl( + _$VoidOrderImpl _value, $Res Function(_$VoidOrderImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? items = null, + Object? orderId = null, + Object? reason = null, + }) { + return _then(_$VoidOrderImpl( + items: null == items + ? _value._items + : items // ignore: cast_nullable_to_non_nullable + as List, + orderId: null == orderId + ? _value.orderId + : orderId // ignore: cast_nullable_to_non_nullable + as String, + reason: null == reason + ? _value.reason + : reason // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$VoidOrderImpl implements _VoidOrder { + const _$VoidOrderImpl( + {required final List items, + required this.orderId, + required this.reason}) + : _items = items; + + final List _items; + @override + List get items { + if (_items is EqualUnmodifiableListView) return _items; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_items); + } + + @override + final String orderId; + @override + final String reason; + + @override + String toString() { + return 'OrderFormEvent.voidOrder(items: $items, orderId: $orderId, reason: $reason)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$VoidOrderImpl && + const DeepCollectionEquality().equals(other._items, _items) && + (identical(other.orderId, orderId) || other.orderId == orderId) && + (identical(other.reason, reason) || other.reason == reason)); + } + + @override + int get hashCode => Object.hash(runtimeType, + const DeepCollectionEquality().hash(_items), orderId, reason); + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$VoidOrderImplCopyWith<_$VoidOrderImpl> get copyWith => + __$$VoidOrderImplCopyWithImpl<_$VoidOrderImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(Order order) started, + required TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table) + create, + required TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod) + createWithPayment, + required TResult Function(List items, String orderId) + addToOrder, + required TResult Function( + List items, String orderId, String reason) + refund, + required TResult Function( + List items, String orderId, String reason) + voidOrder, + required TResult Function(OrderItem item) toggleItem, + required TResult Function(bool selectAll) toggleSelectAll, + }) { + return voidOrder(items, orderId, reason); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(Order order)? started, + TResult? Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult? Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult? Function(List items, String orderId)? addToOrder, + TResult? Function(List items, String orderId, String reason)? + refund, + TResult? Function(List items, String orderId, String reason)? + voidOrder, + TResult? Function(OrderItem item)? toggleItem, + TResult? Function(bool selectAll)? toggleSelectAll, + }) { + return voidOrder?.call(items, orderId, reason); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(Order order)? started, + TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult Function(List items, String orderId)? addToOrder, + TResult Function(List items, String orderId, String reason)? + refund, + TResult Function(List items, String orderId, String reason)? + voidOrder, + TResult Function(OrderItem item)? toggleItem, + TResult Function(bool selectAll)? toggleSelectAll, + required TResult orElse(), + }) { + if (voidOrder != null) { + return voidOrder(items, orderId, reason); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Started value) started, + required TResult Function(_Create value) create, + required TResult Function(_CreateWithPaymentMethod value) createWithPayment, + required TResult Function(_AddToOrder value) addToOrder, + required TResult Function(_Refund value) refund, + required TResult Function(_VoidOrder value) voidOrder, + required TResult Function(_ToggleItem value) toggleItem, + required TResult Function(_ToggleSelectAll value) toggleSelectAll, + }) { + return voidOrder(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Started value)? started, + TResult? Function(_Create value)? create, + TResult? Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult? Function(_AddToOrder value)? addToOrder, + TResult? Function(_Refund value)? refund, + TResult? Function(_VoidOrder value)? voidOrder, + TResult? Function(_ToggleItem value)? toggleItem, + TResult? Function(_ToggleSelectAll value)? toggleSelectAll, + }) { + return voidOrder?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Started value)? started, + TResult Function(_Create value)? create, + TResult Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult Function(_AddToOrder value)? addToOrder, + TResult Function(_Refund value)? refund, + TResult Function(_VoidOrder value)? voidOrder, + TResult Function(_ToggleItem value)? toggleItem, + TResult Function(_ToggleSelectAll value)? toggleSelectAll, + required TResult orElse(), + }) { + if (voidOrder != null) { + return voidOrder(this); + } + return orElse(); + } +} + +abstract class _VoidOrder implements OrderFormEvent { + const factory _VoidOrder( + {required final List items, + required final String orderId, + required final String reason}) = _$VoidOrderImpl; + + List get items; + String get orderId; + String get reason; + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$VoidOrderImplCopyWith<_$VoidOrderImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ToggleItemImplCopyWith<$Res> { + factory _$$ToggleItemImplCopyWith( + _$ToggleItemImpl value, $Res Function(_$ToggleItemImpl) then) = + __$$ToggleItemImplCopyWithImpl<$Res>; + @useResult + $Res call({OrderItem item}); +} + +/// @nodoc +class __$$ToggleItemImplCopyWithImpl<$Res> + extends _$OrderFormEventCopyWithImpl<$Res, _$ToggleItemImpl> + implements _$$ToggleItemImplCopyWith<$Res> { + __$$ToggleItemImplCopyWithImpl( + _$ToggleItemImpl _value, $Res Function(_$ToggleItemImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? item = null, + }) { + return _then(_$ToggleItemImpl( + null == item + ? _value.item + : item // ignore: cast_nullable_to_non_nullable + as OrderItem, + )); + } +} + +/// @nodoc + +class _$ToggleItemImpl implements _ToggleItem { + const _$ToggleItemImpl(this.item); + + @override + final OrderItem item; + + @override + String toString() { + return 'OrderFormEvent.toggleItem(item: $item)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ToggleItemImpl && + (identical(other.item, item) || other.item == item)); + } + + @override + int get hashCode => Object.hash(runtimeType, item); + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ToggleItemImplCopyWith<_$ToggleItemImpl> get copyWith => + __$$ToggleItemImplCopyWithImpl<_$ToggleItemImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(Order order) started, + required TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table) + create, + required TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod) + createWithPayment, + required TResult Function(List items, String orderId) + addToOrder, + required TResult Function( + List items, String orderId, String reason) + refund, + required TResult Function( + List items, String orderId, String reason) + voidOrder, + required TResult Function(OrderItem item) toggleItem, + required TResult Function(bool selectAll) toggleSelectAll, + }) { + return toggleItem(item); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(Order order)? started, + TResult? Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult? Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult? Function(List items, String orderId)? addToOrder, + TResult? Function(List items, String orderId, String reason)? + refund, + TResult? Function(List items, String orderId, String reason)? + voidOrder, + TResult? Function(OrderItem item)? toggleItem, + TResult? Function(bool selectAll)? toggleSelectAll, + }) { + return toggleItem?.call(item); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(Order order)? started, + TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult Function(List items, String orderId)? addToOrder, + TResult Function(List items, String orderId, String reason)? + refund, + TResult Function(List items, String orderId, String reason)? + voidOrder, + TResult Function(OrderItem item)? toggleItem, + TResult Function(bool selectAll)? toggleSelectAll, + required TResult orElse(), + }) { + if (toggleItem != null) { + return toggleItem(item); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Started value) started, + required TResult Function(_Create value) create, + required TResult Function(_CreateWithPaymentMethod value) createWithPayment, + required TResult Function(_AddToOrder value) addToOrder, + required TResult Function(_Refund value) refund, + required TResult Function(_VoidOrder value) voidOrder, + required TResult Function(_ToggleItem value) toggleItem, + required TResult Function(_ToggleSelectAll value) toggleSelectAll, + }) { + return toggleItem(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Started value)? started, + TResult? Function(_Create value)? create, + TResult? Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult? Function(_AddToOrder value)? addToOrder, + TResult? Function(_Refund value)? refund, + TResult? Function(_VoidOrder value)? voidOrder, + TResult? Function(_ToggleItem value)? toggleItem, + TResult? Function(_ToggleSelectAll value)? toggleSelectAll, + }) { + return toggleItem?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Started value)? started, + TResult Function(_Create value)? create, + TResult Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult Function(_AddToOrder value)? addToOrder, + TResult Function(_Refund value)? refund, + TResult Function(_VoidOrder value)? voidOrder, + TResult Function(_ToggleItem value)? toggleItem, + TResult Function(_ToggleSelectAll value)? toggleSelectAll, + required TResult orElse(), + }) { + if (toggleItem != null) { + return toggleItem(this); + } + return orElse(); + } +} + +abstract class _ToggleItem implements OrderFormEvent { + const factory _ToggleItem(final OrderItem item) = _$ToggleItemImpl; + + OrderItem get item; + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ToggleItemImplCopyWith<_$ToggleItemImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ToggleSelectAllImplCopyWith<$Res> { + factory _$$ToggleSelectAllImplCopyWith(_$ToggleSelectAllImpl value, + $Res Function(_$ToggleSelectAllImpl) then) = + __$$ToggleSelectAllImplCopyWithImpl<$Res>; + @useResult + $Res call({bool selectAll}); +} + +/// @nodoc +class __$$ToggleSelectAllImplCopyWithImpl<$Res> + extends _$OrderFormEventCopyWithImpl<$Res, _$ToggleSelectAllImpl> + implements _$$ToggleSelectAllImplCopyWith<$Res> { + __$$ToggleSelectAllImplCopyWithImpl( + _$ToggleSelectAllImpl _value, $Res Function(_$ToggleSelectAllImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? selectAll = null, + }) { + return _then(_$ToggleSelectAllImpl( + null == selectAll + ? _value.selectAll + : selectAll // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$ToggleSelectAllImpl implements _ToggleSelectAll { + const _$ToggleSelectAllImpl(this.selectAll); + + @override + final bool selectAll; + + @override + String toString() { + return 'OrderFormEvent.toggleSelectAll(selectAll: $selectAll)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ToggleSelectAllImpl && + (identical(other.selectAll, selectAll) || + other.selectAll == selectAll)); + } + + @override + int get hashCode => Object.hash(runtimeType, selectAll); + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ToggleSelectAllImplCopyWith<_$ToggleSelectAllImpl> get copyWith => + __$$ToggleSelectAllImplCopyWithImpl<_$ToggleSelectAllImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(Order order) started, + required TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table) + create, + required TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod) + createWithPayment, + required TResult Function(List items, String orderId) + addToOrder, + required TResult Function( + List items, String orderId, String reason) + refund, + required TResult Function( + List items, String orderId, String reason) + voidOrder, + required TResult Function(OrderItem item) toggleItem, + required TResult Function(bool selectAll) toggleSelectAll, + }) { + return toggleSelectAll(selectAll); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(Order order)? started, + TResult? Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult? Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult? Function(List items, String orderId)? addToOrder, + TResult? Function(List items, String orderId, String reason)? + refund, + TResult? Function(List items, String orderId, String reason)? + voidOrder, + TResult? Function(OrderItem item)? toggleItem, + TResult? Function(bool selectAll)? toggleSelectAll, + }) { + return toggleSelectAll?.call(selectAll); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(Order order)? started, + TResult Function(List items, String customerName, + Customer? customer, OrderType orderType, TableModel? table)? + create, + TResult Function( + List items, + String customerName, + Customer? customer, + OrderType orderType, + TableModel? table, + PaymentMethod paymentMethod)? + createWithPayment, + TResult Function(List items, String orderId)? addToOrder, + TResult Function(List items, String orderId, String reason)? + refund, + TResult Function(List items, String orderId, String reason)? + voidOrder, + TResult Function(OrderItem item)? toggleItem, + TResult Function(bool selectAll)? toggleSelectAll, + required TResult orElse(), + }) { + if (toggleSelectAll != null) { + return toggleSelectAll(selectAll); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Started value) started, + required TResult Function(_Create value) create, + required TResult Function(_CreateWithPaymentMethod value) createWithPayment, + required TResult Function(_AddToOrder value) addToOrder, + required TResult Function(_Refund value) refund, + required TResult Function(_VoidOrder value) voidOrder, + required TResult Function(_ToggleItem value) toggleItem, + required TResult Function(_ToggleSelectAll value) toggleSelectAll, + }) { + return toggleSelectAll(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Started value)? started, + TResult? Function(_Create value)? create, + TResult? Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult? Function(_AddToOrder value)? addToOrder, + TResult? Function(_Refund value)? refund, + TResult? Function(_VoidOrder value)? voidOrder, + TResult? Function(_ToggleItem value)? toggleItem, + TResult? Function(_ToggleSelectAll value)? toggleSelectAll, + }) { + return toggleSelectAll?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Started value)? started, + TResult Function(_Create value)? create, + TResult Function(_CreateWithPaymentMethod value)? createWithPayment, + TResult Function(_AddToOrder value)? addToOrder, + TResult Function(_Refund value)? refund, + TResult Function(_VoidOrder value)? voidOrder, + TResult Function(_ToggleItem value)? toggleItem, + TResult Function(_ToggleSelectAll value)? toggleSelectAll, + required TResult orElse(), + }) { + if (toggleSelectAll != null) { + return toggleSelectAll(this); + } + return orElse(); + } +} + +abstract class _ToggleSelectAll implements OrderFormEvent { + const factory _ToggleSelectAll(final bool selectAll) = _$ToggleSelectAllImpl; + + bool get selectAll; + + /// Create a copy of OrderFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ToggleSelectAllImplCopyWith<_$ToggleSelectAllImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$OrderFormState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected) + loaded, + required TResult Function(Order order) success, + required TResult Function() successMsg, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected)? + loaded, + TResult? Function(Order order)? success, + TResult? Function()? successMsg, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected)? + loaded, + TResult Function(Order order)? success, + TResult Function()? successMsg, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Success value) success, + required TResult Function(_SuccessMsg value) successMsg, + required TResult Function(_Error value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Success value)? success, + TResult? Function(_SuccessMsg value)? successMsg, + TResult? Function(_Error value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Success value)? success, + TResult Function(_SuccessMsg value)? successMsg, + TResult Function(_Error value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $OrderFormStateCopyWith<$Res> { + factory $OrderFormStateCopyWith( + OrderFormState value, $Res Function(OrderFormState) then) = + _$OrderFormStateCopyWithImpl<$Res, OrderFormState>; +} + +/// @nodoc +class _$OrderFormStateCopyWithImpl<$Res, $Val extends OrderFormState> + implements $OrderFormStateCopyWith<$Res> { + _$OrderFormStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of OrderFormState + /// 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 _$OrderFormStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderFormState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'OrderFormState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected) + loaded, + required TResult Function(Order order) success, + required TResult Function() successMsg, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected)? + loaded, + TResult? Function(Order order)? success, + TResult? Function()? successMsg, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected)? + loaded, + TResult Function(Order order)? success, + TResult Function()? successMsg, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Success value) success, + required TResult Function(_SuccessMsg value) successMsg, + required TResult Function(_Error value) error, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Success value)? success, + TResult? Function(_SuccessMsg value)? successMsg, + TResult? Function(_Error value)? error, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Success value)? success, + TResult Function(_SuccessMsg value)? successMsg, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements OrderFormState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$OrderFormStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderFormState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'OrderFormState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected) + loaded, + required TResult Function(Order order) success, + required TResult Function() successMsg, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected)? + loaded, + TResult? Function(Order order)? success, + TResult? Function()? successMsg, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected)? + loaded, + TResult Function(Order order)? success, + TResult Function()? successMsg, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Success value) success, + required TResult Function(_SuccessMsg value) successMsg, + required TResult Function(_Error value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Success value)? success, + TResult? Function(_SuccessMsg value)? successMsg, + TResult? Function(_Error value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Success value)? success, + TResult Function(_SuccessMsg value)? successMsg, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class _Loading implements OrderFormState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$LoadedImplCopyWith<$Res> { + factory _$$LoadedImplCopyWith( + _$LoadedImpl value, $Res Function(_$LoadedImpl) then) = + __$$LoadedImplCopyWithImpl<$Res>; + @useResult + $Res call( + {Order order, + List selectedItems, + int totalVoidOrRefund, + bool isAllSelected}); +} + +/// @nodoc +class __$$LoadedImplCopyWithImpl<$Res> + extends _$OrderFormStateCopyWithImpl<$Res, _$LoadedImpl> + implements _$$LoadedImplCopyWith<$Res> { + __$$LoadedImplCopyWithImpl( + _$LoadedImpl _value, $Res Function(_$LoadedImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? order = null, + Object? selectedItems = null, + Object? totalVoidOrRefund = null, + Object? isAllSelected = null, + }) { + return _then(_$LoadedImpl( + order: null == order + ? _value.order + : order // ignore: cast_nullable_to_non_nullable + as Order, + selectedItems: null == selectedItems + ? _value._selectedItems + : selectedItems // ignore: cast_nullable_to_non_nullable + as List, + totalVoidOrRefund: null == totalVoidOrRefund + ? _value.totalVoidOrRefund + : totalVoidOrRefund // ignore: cast_nullable_to_non_nullable + as int, + isAllSelected: null == isAllSelected + ? _value.isAllSelected + : isAllSelected // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$LoadedImpl implements _Loaded { + const _$LoadedImpl( + {required this.order, + final List selectedItems = const [], + this.totalVoidOrRefund = 0, + this.isAllSelected = false}) + : _selectedItems = selectedItems; + + @override + final Order order; + final List _selectedItems; + @override + @JsonKey() + List get selectedItems { + if (_selectedItems is EqualUnmodifiableListView) return _selectedItems; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_selectedItems); + } + + @override + @JsonKey() + final int totalVoidOrRefund; + @override + @JsonKey() + final bool isAllSelected; + + @override + String toString() { + return 'OrderFormState.loaded(order: $order, selectedItems: $selectedItems, totalVoidOrRefund: $totalVoidOrRefund, isAllSelected: $isAllSelected)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoadedImpl && + (identical(other.order, order) || other.order == order) && + const DeepCollectionEquality() + .equals(other._selectedItems, _selectedItems) && + (identical(other.totalVoidOrRefund, totalVoidOrRefund) || + other.totalVoidOrRefund == totalVoidOrRefund) && + (identical(other.isAllSelected, isAllSelected) || + other.isAllSelected == isAllSelected)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + order, + const DeepCollectionEquality().hash(_selectedItems), + totalVoidOrRefund, + isAllSelected); + + /// Create a copy of OrderFormState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected) + loaded, + required TResult Function(Order order) success, + required TResult Function() successMsg, + required TResult Function(String message) error, + }) { + return loaded(order, selectedItems, totalVoidOrRefund, isAllSelected); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected)? + loaded, + TResult? Function(Order order)? success, + TResult? Function()? successMsg, + TResult? Function(String message)? error, + }) { + return loaded?.call(order, selectedItems, totalVoidOrRefund, isAllSelected); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected)? + loaded, + TResult Function(Order order)? success, + TResult Function()? successMsg, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(order, selectedItems, totalVoidOrRefund, isAllSelected); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Success value) success, + required TResult Function(_SuccessMsg value) successMsg, + required TResult Function(_Error value) error, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Success value)? success, + TResult? Function(_SuccessMsg value)? successMsg, + TResult? Function(_Error value)? error, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Success value)? success, + TResult Function(_SuccessMsg value)? successMsg, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class _Loaded implements OrderFormState { + const factory _Loaded( + {required final Order order, + final List selectedItems, + final int totalVoidOrRefund, + final bool isAllSelected}) = _$LoadedImpl; + + Order get order; + List get selectedItems; + int get totalVoidOrRefund; + bool get isAllSelected; + + /// Create a copy of OrderFormState + /// 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 _$$SuccessImplCopyWith<$Res> { + factory _$$SuccessImplCopyWith( + _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = + __$$SuccessImplCopyWithImpl<$Res>; + @useResult + $Res call({Order order}); +} + +/// @nodoc +class __$$SuccessImplCopyWithImpl<$Res> + extends _$OrderFormStateCopyWithImpl<$Res, _$SuccessImpl> + implements _$$SuccessImplCopyWith<$Res> { + __$$SuccessImplCopyWithImpl( + _$SuccessImpl _value, $Res Function(_$SuccessImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? order = null, + }) { + return _then(_$SuccessImpl( + null == order + ? _value.order + : order // ignore: cast_nullable_to_non_nullable + as Order, + )); + } +} + +/// @nodoc + +class _$SuccessImpl implements _Success { + const _$SuccessImpl(this.order); + + @override + final Order order; + + @override + String toString() { + return 'OrderFormState.success(order: $order)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SuccessImpl && + (identical(other.order, order) || other.order == order)); + } + + @override + int get hashCode => Object.hash(runtimeType, order); + + /// Create a copy of OrderFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SuccessImplCopyWith<_$SuccessImpl> get copyWith => + __$$SuccessImplCopyWithImpl<_$SuccessImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected) + loaded, + required TResult Function(Order order) success, + required TResult Function() successMsg, + required TResult Function(String message) error, + }) { + return success(order); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected)? + loaded, + TResult? Function(Order order)? success, + TResult? Function()? successMsg, + TResult? Function(String message)? error, + }) { + return success?.call(order); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected)? + loaded, + TResult Function(Order order)? success, + TResult Function()? successMsg, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(order); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Success value) success, + required TResult Function(_SuccessMsg value) successMsg, + required TResult Function(_Error value) error, + }) { + return success(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Success value)? success, + TResult? Function(_SuccessMsg value)? successMsg, + TResult? Function(_Error value)? error, + }) { + return success?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Success value)? success, + TResult Function(_SuccessMsg value)? successMsg, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(this); + } + return orElse(); + } +} + +abstract class _Success implements OrderFormState { + const factory _Success(final Order order) = _$SuccessImpl; + + Order get order; + + /// Create a copy of OrderFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SuccessImplCopyWith<_$SuccessImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$SuccessMsgImplCopyWith<$Res> { + factory _$$SuccessMsgImplCopyWith( + _$SuccessMsgImpl value, $Res Function(_$SuccessMsgImpl) then) = + __$$SuccessMsgImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SuccessMsgImplCopyWithImpl<$Res> + extends _$OrderFormStateCopyWithImpl<$Res, _$SuccessMsgImpl> + implements _$$SuccessMsgImplCopyWith<$Res> { + __$$SuccessMsgImplCopyWithImpl( + _$SuccessMsgImpl _value, $Res Function(_$SuccessMsgImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderFormState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SuccessMsgImpl implements _SuccessMsg { + const _$SuccessMsgImpl(); + + @override + String toString() { + return 'OrderFormState.successMsg()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SuccessMsgImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected) + loaded, + required TResult Function(Order order) success, + required TResult Function() successMsg, + required TResult Function(String message) error, + }) { + return successMsg(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected)? + loaded, + TResult? Function(Order order)? success, + TResult? Function()? successMsg, + TResult? Function(String message)? error, + }) { + return successMsg?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected)? + loaded, + TResult Function(Order order)? success, + TResult Function()? successMsg, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (successMsg != null) { + return successMsg(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Success value) success, + required TResult Function(_SuccessMsg value) successMsg, + required TResult Function(_Error value) error, + }) { + return successMsg(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Success value)? success, + TResult? Function(_SuccessMsg value)? successMsg, + TResult? Function(_Error value)? error, + }) { + return successMsg?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Success value)? success, + TResult Function(_SuccessMsg value)? successMsg, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (successMsg != null) { + return successMsg(this); + } + return orElse(); + } +} + +abstract class _SuccessMsg implements OrderFormState { + const factory _SuccessMsg() = _$SuccessMsgImpl; +} + +/// @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 _$OrderFormStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderFormState + /// 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 'OrderFormState.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 OrderFormState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected) + loaded, + required TResult Function(Order order) success, + required TResult Function() successMsg, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected)? + loaded, + TResult? Function(Order order)? success, + TResult? Function()? successMsg, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(Order order, List selectedItems, + int totalVoidOrRefund, bool isAllSelected)? + loaded, + TResult Function(Order order)? success, + TResult Function()? successMsg, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Success value) success, + required TResult Function(_SuccessMsg value) successMsg, + required TResult Function(_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Success value)? success, + TResult? Function(_SuccessMsg value)? successMsg, + TResult? Function(_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Success value)? success, + TResult Function(_SuccessMsg value)? successMsg, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements OrderFormState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of OrderFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/home/bloc/order_form/order_form_event.dart b/lib/presentation/home/bloc/order_form/order_form_event.dart new file mode 100644 index 0000000..b54406d --- /dev/null +++ b/lib/presentation/home/bloc/order_form/order_form_event.dart @@ -0,0 +1,38 @@ +part of 'order_form_bloc.dart'; + +@freezed +class OrderFormEvent with _$OrderFormEvent { + const factory OrderFormEvent.started(Order order) = _Started; + const factory OrderFormEvent.create({ + required List items, + required String customerName, + required Customer? customer, + required OrderType orderType, + required TableModel? table, + }) = _Create; + const factory OrderFormEvent.createWithPayment({ + required List items, + required String customerName, + required Customer? customer, + required OrderType orderType, + required TableModel? table, + required PaymentMethod paymentMethod, + }) = _CreateWithPaymentMethod; + const factory OrderFormEvent.addToOrder({ + required List items, + required String orderId, + }) = _AddToOrder; + const factory OrderFormEvent.refund({ + required List items, + required String orderId, + required String reason, + }) = _Refund; + const factory OrderFormEvent.voidOrder({ + required List items, + required String orderId, + required String reason, + }) = _VoidOrder; + const factory OrderFormEvent.toggleItem(OrderItem item) = _ToggleItem; + const factory OrderFormEvent.toggleSelectAll(bool selectAll) = + _ToggleSelectAll; +} diff --git a/lib/presentation/home/bloc/order_form/order_form_state.dart b/lib/presentation/home/bloc/order_form/order_form_state.dart new file mode 100644 index 0000000..52e23d8 --- /dev/null +++ b/lib/presentation/home/bloc/order_form/order_form_state.dart @@ -0,0 +1,16 @@ +part of 'order_form_bloc.dart'; + +@freezed +class OrderFormState with _$OrderFormState { + const factory OrderFormState.initial() = _Initial; + const factory OrderFormState.loading() = _Loading; + const factory OrderFormState.loaded({ + required Order order, + @Default([]) List selectedItems, + @Default(0) int totalVoidOrRefund, + @Default(false) bool isAllSelected, + }) = _Loaded; + const factory OrderFormState.success(Order order) = _Success; + const factory OrderFormState.successMsg() = _SuccessMsg; + const factory OrderFormState.error(String message) = _Error; +} diff --git a/lib/presentation/home/bloc/outlet_loader/outlet_loader_bloc.dart b/lib/presentation/home/bloc/outlet_loader/outlet_loader_bloc.dart new file mode 100644 index 0000000..1755140 --- /dev/null +++ b/lib/presentation/home/bloc/outlet_loader/outlet_loader_bloc.dart @@ -0,0 +1,23 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/outlet_remote_data_source.dart'; +import 'package:enaklo_pos/presentation/home/models/outlet_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'outlet_loader_event.dart'; +part 'outlet_loader_state.dart'; +part 'outlet_loader_bloc.freezed.dart'; + +class OutletLoaderBloc extends Bloc { + final OutletRemoteDataSource _outletRemoteDataSource; + OutletLoaderBloc(this._outletRemoteDataSource) + : super(OutletLoaderState.initial()) { + on<_GetOutlet>((event, emit) async { + emit(const _Loading()); + final result = await _outletRemoteDataSource.getOutlets(); + result.fold( + (l) => emit(_Error(l)), + (r) => emit(_Loaded(r.data?.outlets ?? [])), + ); + }); + } +} diff --git a/lib/presentation/home/bloc/outlet_loader/outlet_loader_bloc.freezed.dart b/lib/presentation/home/bloc/outlet_loader/outlet_loader_bloc.freezed.dart new file mode 100644 index 0000000..b1fcd7b --- /dev/null +++ b/lib/presentation/home/bloc/outlet_loader/outlet_loader_bloc.freezed.dart @@ -0,0 +1,790 @@ +// 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 'outlet_loader_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$OutletLoaderEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() getOutlet, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? getOutlet, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? getOutlet, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_GetOutlet value) getOutlet, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetOutlet value)? getOutlet, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetOutlet value)? getOutlet, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $OutletLoaderEventCopyWith<$Res> { + factory $OutletLoaderEventCopyWith( + OutletLoaderEvent value, $Res Function(OutletLoaderEvent) then) = + _$OutletLoaderEventCopyWithImpl<$Res, OutletLoaderEvent>; +} + +/// @nodoc +class _$OutletLoaderEventCopyWithImpl<$Res, $Val extends OutletLoaderEvent> + implements $OutletLoaderEventCopyWith<$Res> { + _$OutletLoaderEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of OutletLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$GetOutletImplCopyWith<$Res> { + factory _$$GetOutletImplCopyWith( + _$GetOutletImpl value, $Res Function(_$GetOutletImpl) then) = + __$$GetOutletImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$GetOutletImplCopyWithImpl<$Res> + extends _$OutletLoaderEventCopyWithImpl<$Res, _$GetOutletImpl> + implements _$$GetOutletImplCopyWith<$Res> { + __$$GetOutletImplCopyWithImpl( + _$GetOutletImpl _value, $Res Function(_$GetOutletImpl) _then) + : super(_value, _then); + + /// Create a copy of OutletLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$GetOutletImpl implements _GetOutlet { + const _$GetOutletImpl(); + + @override + String toString() { + return 'OutletLoaderEvent.getOutlet()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$GetOutletImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() getOutlet, + }) { + return getOutlet(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? getOutlet, + }) { + return getOutlet?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? getOutlet, + required TResult orElse(), + }) { + if (getOutlet != null) { + return getOutlet(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetOutlet value) getOutlet, + }) { + return getOutlet(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetOutlet value)? getOutlet, + }) { + return getOutlet?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetOutlet value)? getOutlet, + required TResult orElse(), + }) { + if (getOutlet != null) { + return getOutlet(this); + } + return orElse(); + } +} + +abstract class _GetOutlet implements OutletLoaderEvent { + const factory _GetOutlet() = _$GetOutletImpl; +} + +/// @nodoc +mixin _$OutletLoaderState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List outlets) loaded, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List outlets)? loaded, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List outlets)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + 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? 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 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 $OutletLoaderStateCopyWith<$Res> { + factory $OutletLoaderStateCopyWith( + OutletLoaderState value, $Res Function(OutletLoaderState) then) = + _$OutletLoaderStateCopyWithImpl<$Res, OutletLoaderState>; +} + +/// @nodoc +class _$OutletLoaderStateCopyWithImpl<$Res, $Val extends OutletLoaderState> + implements $OutletLoaderStateCopyWith<$Res> { + _$OutletLoaderStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of OutletLoaderState + /// 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 _$OutletLoaderStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of OutletLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'OutletLoaderState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List outlets) loaded, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List outlets)? loaded, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List outlets)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 OutletLoaderState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$OutletLoaderStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of OutletLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'OutletLoaderState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List outlets) loaded, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List outlets)? loaded, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List outlets)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 OutletLoaderState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$LoadedImplCopyWith<$Res> { + factory _$$LoadedImplCopyWith( + _$LoadedImpl value, $Res Function(_$LoadedImpl) then) = + __$$LoadedImplCopyWithImpl<$Res>; + @useResult + $Res call({List outlets}); +} + +/// @nodoc +class __$$LoadedImplCopyWithImpl<$Res> + extends _$OutletLoaderStateCopyWithImpl<$Res, _$LoadedImpl> + implements _$$LoadedImplCopyWith<$Res> { + __$$LoadedImplCopyWithImpl( + _$LoadedImpl _value, $Res Function(_$LoadedImpl) _then) + : super(_value, _then); + + /// Create a copy of OutletLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? outlets = null, + }) { + return _then(_$LoadedImpl( + null == outlets + ? _value._outlets + : outlets // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +class _$LoadedImpl implements _Loaded { + const _$LoadedImpl(final List outlets) : _outlets = outlets; + + final List _outlets; + @override + List get outlets { + if (_outlets is EqualUnmodifiableListView) return _outlets; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_outlets); + } + + @override + String toString() { + return 'OutletLoaderState.loaded(outlets: $outlets)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoadedImpl && + const DeepCollectionEquality().equals(other._outlets, _outlets)); + } + + @override + int get hashCode => + Object.hash(runtimeType, const DeepCollectionEquality().hash(_outlets)); + + /// Create a copy of OutletLoaderState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List outlets) loaded, + required TResult Function(String message) error, + }) { + return loaded(outlets); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List outlets)? loaded, + TResult? Function(String message)? error, + }) { + return loaded?.call(outlets); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List outlets)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(outlets); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 OutletLoaderState { + const factory _Loaded(final List outlets) = _$LoadedImpl; + + List get outlets; + + /// Create a copy of OutletLoaderState + /// 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 _$OutletLoaderStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of OutletLoaderState + /// 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 'OutletLoaderState.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 OutletLoaderState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List outlets) loaded, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List outlets)? loaded, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List outlets)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 OutletLoaderState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of OutletLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/home/bloc/outlet_loader/outlet_loader_event.dart b/lib/presentation/home/bloc/outlet_loader/outlet_loader_event.dart new file mode 100644 index 0000000..c94e780 --- /dev/null +++ b/lib/presentation/home/bloc/outlet_loader/outlet_loader_event.dart @@ -0,0 +1,6 @@ +part of 'outlet_loader_bloc.dart'; + +@freezed +class OutletLoaderEvent with _$OutletLoaderEvent { + const factory OutletLoaderEvent.getOutlet() = _GetOutlet; +} diff --git a/lib/presentation/home/bloc/outlet_loader/outlet_loader_state.dart b/lib/presentation/home/bloc/outlet_loader/outlet_loader_state.dart new file mode 100644 index 0000000..de64ef0 --- /dev/null +++ b/lib/presentation/home/bloc/outlet_loader/outlet_loader_state.dart @@ -0,0 +1,9 @@ +part of 'outlet_loader_bloc.dart'; + +@freezed +class OutletLoaderState with _$OutletLoaderState { + const factory OutletLoaderState.initial() = _Initial; + const factory OutletLoaderState.loading() = _Loading; + const factory OutletLoaderState.loaded(List outlets) = _Loaded; + const factory OutletLoaderState.error(String message) = _Error; +} diff --git a/lib/presentation/home/bloc/payment_methods/payment_methods_bloc.dart b/lib/presentation/home/bloc/payment_methods/payment_methods_bloc.dart index b18d945..93b746d 100644 --- a/lib/presentation/home/bloc/payment_methods/payment_methods_bloc.dart +++ b/lib/presentation/home/bloc/payment_methods/payment_methods_bloc.dart @@ -8,17 +8,18 @@ part 'payment_methods_event.dart'; part 'payment_methods_state.dart'; part 'payment_methods_bloc.freezed.dart'; -class PaymentMethodsBloc extends Bloc { +class PaymentMethodsBloc + extends Bloc { final PaymentMethodsRemoteDatasource datasource; - + PaymentMethodsBloc(this.datasource) : super(const _Initial()) { on<_FetchPaymentMethods>((event, emit) async { emit(const _Loading()); final response = await datasource.getPaymentMethods(); response.fold( (l) => emit(_Error(l)), - (r) => emit(_Loaded(r.data ?? [])), + (r) => emit(_Loaded(r.data?.paymentMethods ?? [])), ); }); } -} \ No newline at end of file +} diff --git a/lib/presentation/home/bloc/product_loader/product_loader_bloc.dart b/lib/presentation/home/bloc/product_loader/product_loader_bloc.dart new file mode 100644 index 0000000..1a18a08 --- /dev/null +++ b/lib/presentation/home/bloc/product_loader/product_loader_bloc.dart @@ -0,0 +1,148 @@ +import 'dart:async'; + +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:freezed_annotation/freezed_annotation.dart'; + +part 'product_loader_event.dart'; +part 'product_loader_state.dart'; +part 'product_loader_bloc.freezed.dart'; + +class ProductLoaderBloc extends Bloc { + final ProductRemoteDatasource _productRemoteDatasource; + + // Debouncing untuk mencegah multiple load more calls + Timer? _loadMoreDebounce; + bool _isLoadingMore = false; + + ProductLoaderBloc(this._productRemoteDatasource) + : super(ProductLoaderState.initial()) { + on<_GetProduct>(_onGetProduct); + on<_LoadMore>(_onLoadMore); + on<_Refresh>(_onRefresh); + } + + @override + Future close() { + _loadMoreDebounce?.cancel(); + return super.close(); + } + + // Debounce transformer untuk load more + // EventTransformer _debounceTransformer() { + // return (events, mapper) { + // return events + // .debounceTime(const Duration(milliseconds: 300)) + // .asyncExpand(mapper); + // }; + // } + + // Initial load + Future _onGetProduct( + _GetProduct event, + Emitter emit, + ) async { + emit(const _Loading()); + _isLoadingMore = false; // Reset loading state + + final result = await _productRemoteDatasource.getProducts( + page: 1, + limit: 10, + categoryId: event.categoryId, + ); + + await result.fold( + (failure) async => emit(_Error(failure)), + (response) async { + final products = response.data?.products ?? []; + final hasReachedMax = products.length < 10; + + emit(_Loaded( + products: products, + hasReachedMax: hasReachedMax, + currentPage: 1, + isLoadingMore: false, + )); + }, + ); + } + + // Load more with enhanced debouncing + Future _onLoadMore( + _LoadMore event, + Emitter emit, + ) async { + final currentState = state; + + // Enhanced validation + if (currentState is! _Loaded || + currentState.hasReachedMax || + _isLoadingMore || + currentState.isLoadingMore) { + return; + } + + _isLoadingMore = true; + + // Emit loading more state + emit(currentState.copyWith(isLoadingMore: true)); + + final nextPage = currentState.currentPage + 1; + + try { + final result = await _productRemoteDatasource.getProducts( + page: nextPage, + limit: 10, + categoryId: event.categoryId, + ); + + await result.fold( + (failure) async { + // On error, revert loading state but don't show error + // Just silently fail and allow retry + emit(currentState.copyWith(isLoadingMore: false)); + _isLoadingMore = false; + }, + (response) async { + final newProducts = response.data?.products ?? []; + + // Prevent duplicate products + final currentProductIds = + currentState.products.map((p) => p.id).toSet(); + final filteredNewProducts = newProducts + .where((product) => !currentProductIds.contains(product.id)) + .toList(); + + final allProducts = List.from(currentState.products) + ..addAll(filteredNewProducts); + + final hasReachedMax = newProducts.length < 10; + + emit(_Loaded( + products: allProducts, + hasReachedMax: hasReachedMax, + currentPage: nextPage, + isLoadingMore: false, + )); + + _isLoadingMore = false; + }, + ); + } catch (e) { + // Handle unexpected errors + emit(currentState.copyWith(isLoadingMore: false)); + _isLoadingMore = false; + } + } + + // Refresh data + Future _onRefresh( + _Refresh event, + Emitter emit, + ) async { + _isLoadingMore = false; + _loadMoreDebounce?.cancel(); + add(const _GetProduct()); + } +} diff --git a/lib/presentation/home/bloc/product_loader/product_loader_bloc.freezed.dart b/lib/presentation/home/bloc/product_loader/product_loader_bloc.freezed.dart new file mode 100644 index 0000000..5e2d0e5 --- /dev/null +++ b/lib/presentation/home/bloc/product_loader/product_loader_bloc.freezed.dart @@ -0,0 +1,1206 @@ +// 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 'product_loader_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$ProductLoaderEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(String? categoryId, String? search) getProduct, + required TResult Function(String? categoryId, String? search) loadMore, + required TResult Function() refresh, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String? categoryId, String? search)? getProduct, + TResult? Function(String? categoryId, String? search)? loadMore, + TResult? Function()? refresh, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String? categoryId, String? search)? getProduct, + TResult Function(String? categoryId, String? search)? loadMore, + TResult Function()? refresh, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_GetProduct value) getProduct, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetProduct value)? getProduct, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetProduct value)? getProduct, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductLoaderEventCopyWith<$Res> { + factory $ProductLoaderEventCopyWith( + ProductLoaderEvent value, $Res Function(ProductLoaderEvent) then) = + _$ProductLoaderEventCopyWithImpl<$Res, ProductLoaderEvent>; +} + +/// @nodoc +class _$ProductLoaderEventCopyWithImpl<$Res, $Val extends ProductLoaderEvent> + implements $ProductLoaderEventCopyWith<$Res> { + _$ProductLoaderEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$GetProductImplCopyWith<$Res> { + factory _$$GetProductImplCopyWith( + _$GetProductImpl value, $Res Function(_$GetProductImpl) then) = + __$$GetProductImplCopyWithImpl<$Res>; + @useResult + $Res call({String? categoryId, String? search}); +} + +/// @nodoc +class __$$GetProductImplCopyWithImpl<$Res> + extends _$ProductLoaderEventCopyWithImpl<$Res, _$GetProductImpl> + implements _$$GetProductImplCopyWith<$Res> { + __$$GetProductImplCopyWithImpl( + _$GetProductImpl _value, $Res Function(_$GetProductImpl) _then) + : super(_value, _then); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? categoryId = freezed, + Object? search = freezed, + }) { + return _then(_$GetProductImpl( + categoryId: freezed == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String?, + search: freezed == search + ? _value.search + : search // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$GetProductImpl implements _GetProduct { + const _$GetProductImpl({this.categoryId, this.search}); + + @override + final String? categoryId; + @override + final String? search; + + @override + String toString() { + return 'ProductLoaderEvent.getProduct(categoryId: $categoryId, search: $search)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$GetProductImpl && + (identical(other.categoryId, categoryId) || + other.categoryId == categoryId) && + (identical(other.search, search) || other.search == search)); + } + + @override + int get hashCode => Object.hash(runtimeType, categoryId, search); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$GetProductImplCopyWith<_$GetProductImpl> get copyWith => + __$$GetProductImplCopyWithImpl<_$GetProductImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String? categoryId, String? search) getProduct, + required TResult Function(String? categoryId, String? search) loadMore, + required TResult Function() refresh, + }) { + return getProduct(categoryId, search); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String? categoryId, String? search)? getProduct, + TResult? Function(String? categoryId, String? search)? loadMore, + TResult? Function()? refresh, + }) { + return getProduct?.call(categoryId, search); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String? categoryId, String? search)? getProduct, + TResult Function(String? categoryId, String? search)? loadMore, + TResult Function()? refresh, + required TResult orElse(), + }) { + if (getProduct != null) { + return getProduct(categoryId, search); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetProduct value) getProduct, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + }) { + return getProduct(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetProduct value)? getProduct, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + }) { + return getProduct?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetProduct value)? getProduct, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + required TResult orElse(), + }) { + if (getProduct != null) { + return getProduct(this); + } + return orElse(); + } +} + +abstract class _GetProduct implements ProductLoaderEvent { + const factory _GetProduct({final String? categoryId, final String? search}) = + _$GetProductImpl; + + String? get categoryId; + String? get search; + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GetProductImplCopyWith<_$GetProductImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$LoadMoreImplCopyWith<$Res> { + factory _$$LoadMoreImplCopyWith( + _$LoadMoreImpl value, $Res Function(_$LoadMoreImpl) then) = + __$$LoadMoreImplCopyWithImpl<$Res>; + @useResult + $Res call({String? categoryId, String? search}); +} + +/// @nodoc +class __$$LoadMoreImplCopyWithImpl<$Res> + extends _$ProductLoaderEventCopyWithImpl<$Res, _$LoadMoreImpl> + implements _$$LoadMoreImplCopyWith<$Res> { + __$$LoadMoreImplCopyWithImpl( + _$LoadMoreImpl _value, $Res Function(_$LoadMoreImpl) _then) + : super(_value, _then); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? categoryId = freezed, + Object? search = freezed, + }) { + return _then(_$LoadMoreImpl( + categoryId: freezed == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String?, + search: freezed == search + ? _value.search + : search // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$LoadMoreImpl implements _LoadMore { + const _$LoadMoreImpl({this.categoryId, this.search}); + + @override + final String? categoryId; + @override + final String? search; + + @override + String toString() { + return 'ProductLoaderEvent.loadMore(categoryId: $categoryId, search: $search)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoadMoreImpl && + (identical(other.categoryId, categoryId) || + other.categoryId == categoryId) && + (identical(other.search, search) || other.search == search)); + } + + @override + int get hashCode => Object.hash(runtimeType, categoryId, search); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LoadMoreImplCopyWith<_$LoadMoreImpl> get copyWith => + __$$LoadMoreImplCopyWithImpl<_$LoadMoreImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String? categoryId, String? search) getProduct, + required TResult Function(String? categoryId, String? search) loadMore, + required TResult Function() refresh, + }) { + return loadMore(categoryId, search); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String? categoryId, String? search)? getProduct, + TResult? Function(String? categoryId, String? search)? loadMore, + TResult? Function()? refresh, + }) { + return loadMore?.call(categoryId, search); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String? categoryId, String? search)? getProduct, + TResult Function(String? categoryId, String? search)? loadMore, + TResult Function()? refresh, + required TResult orElse(), + }) { + if (loadMore != null) { + return loadMore(categoryId, search); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetProduct value) getProduct, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + }) { + return loadMore(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetProduct value)? getProduct, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + }) { + return loadMore?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetProduct value)? getProduct, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + required TResult orElse(), + }) { + if (loadMore != null) { + return loadMore(this); + } + return orElse(); + } +} + +abstract class _LoadMore implements ProductLoaderEvent { + const factory _LoadMore({final String? categoryId, final String? search}) = + _$LoadMoreImpl; + + String? get categoryId; + String? get search; + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LoadMoreImplCopyWith<_$LoadMoreImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$RefreshImplCopyWith<$Res> { + factory _$$RefreshImplCopyWith( + _$RefreshImpl value, $Res Function(_$RefreshImpl) then) = + __$$RefreshImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$RefreshImplCopyWithImpl<$Res> + extends _$ProductLoaderEventCopyWithImpl<$Res, _$RefreshImpl> + implements _$$RefreshImplCopyWith<$Res> { + __$$RefreshImplCopyWithImpl( + _$RefreshImpl _value, $Res Function(_$RefreshImpl) _then) + : super(_value, _then); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$RefreshImpl implements _Refresh { + const _$RefreshImpl(); + + @override + String toString() { + return 'ProductLoaderEvent.refresh()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$RefreshImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String? categoryId, String? search) getProduct, + required TResult Function(String? categoryId, String? search) loadMore, + required TResult Function() refresh, + }) { + return refresh(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String? categoryId, String? search)? getProduct, + TResult? Function(String? categoryId, String? search)? loadMore, + TResult? Function()? refresh, + }) { + return refresh?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String? categoryId, String? search)? getProduct, + TResult Function(String? categoryId, String? search)? loadMore, + TResult Function()? refresh, + required TResult orElse(), + }) { + if (refresh != null) { + return refresh(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetProduct value) getProduct, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + }) { + return refresh(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetProduct value)? getProduct, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + }) { + return refresh?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetProduct value)? getProduct, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + required TResult orElse(), + }) { + if (refresh != null) { + return refresh(this); + } + return orElse(); + } +} + +abstract class _Refresh implements ProductLoaderEvent { + const factory _Refresh() = _$RefreshImpl; +} + +/// @nodoc +mixin _$ProductLoaderState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore) + loaded, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + 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? 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 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 $ProductLoaderStateCopyWith<$Res> { + factory $ProductLoaderStateCopyWith( + ProductLoaderState value, $Res Function(ProductLoaderState) then) = + _$ProductLoaderStateCopyWithImpl<$Res, ProductLoaderState>; +} + +/// @nodoc +class _$ProductLoaderStateCopyWithImpl<$Res, $Val extends ProductLoaderState> + implements $ProductLoaderStateCopyWith<$Res> { + _$ProductLoaderStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductLoaderState + /// 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 _$ProductLoaderStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of ProductLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'ProductLoaderState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore) + loaded, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 ProductLoaderState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$ProductLoaderStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of ProductLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'ProductLoaderState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore) + loaded, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 ProductLoaderState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$LoadedImplCopyWith<$Res> { + factory _$$LoadedImplCopyWith( + _$LoadedImpl value, $Res Function(_$LoadedImpl) then) = + __$$LoadedImplCopyWithImpl<$Res>; + @useResult + $Res call( + {List products, + bool hasReachedMax, + int currentPage, + bool isLoadingMore}); +} + +/// @nodoc +class __$$LoadedImplCopyWithImpl<$Res> + extends _$ProductLoaderStateCopyWithImpl<$Res, _$LoadedImpl> + implements _$$LoadedImplCopyWith<$Res> { + __$$LoadedImplCopyWithImpl( + _$LoadedImpl _value, $Res Function(_$LoadedImpl) _then) + : super(_value, _then); + + /// Create a copy of ProductLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? products = null, + Object? hasReachedMax = null, + Object? currentPage = null, + Object? isLoadingMore = null, + }) { + return _then(_$LoadedImpl( + products: null == products + ? _value._products + : products // ignore: cast_nullable_to_non_nullable + as List, + hasReachedMax: null == hasReachedMax + ? _value.hasReachedMax + : hasReachedMax // ignore: cast_nullable_to_non_nullable + as bool, + currentPage: null == currentPage + ? _value.currentPage + : currentPage // ignore: cast_nullable_to_non_nullable + as int, + isLoadingMore: null == isLoadingMore + ? _value.isLoadingMore + : isLoadingMore // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$LoadedImpl implements _Loaded { + const _$LoadedImpl( + {required final List products, + required this.hasReachedMax, + required this.currentPage, + required this.isLoadingMore}) + : _products = products; + + final List _products; + @override + List get products { + if (_products is EqualUnmodifiableListView) return _products; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_products); + } + + @override + final bool hasReachedMax; + @override + final int currentPage; + @override + final bool isLoadingMore; + + @override + String toString() { + return 'ProductLoaderState.loaded(products: $products, hasReachedMax: $hasReachedMax, currentPage: $currentPage, isLoadingMore: $isLoadingMore)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoadedImpl && + const DeepCollectionEquality().equals(other._products, _products) && + (identical(other.hasReachedMax, hasReachedMax) || + other.hasReachedMax == hasReachedMax) && + (identical(other.currentPage, currentPage) || + other.currentPage == currentPage) && + (identical(other.isLoadingMore, isLoadingMore) || + other.isLoadingMore == isLoadingMore)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_products), + hasReachedMax, + currentPage, + isLoadingMore); + + /// Create a copy of ProductLoaderState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore) + loaded, + required TResult Function(String message) error, + }) { + return loaded(products, hasReachedMax, currentPage, isLoadingMore); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult? Function(String message)? error, + }) { + return loaded?.call(products, hasReachedMax, currentPage, isLoadingMore); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(products, hasReachedMax, currentPage, isLoadingMore); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 ProductLoaderState { + const factory _Loaded( + {required final List products, + required final bool hasReachedMax, + required final int currentPage, + required final bool isLoadingMore}) = _$LoadedImpl; + + List get products; + bool get hasReachedMax; + int get currentPage; + bool get isLoadingMore; + + /// Create a copy of ProductLoaderState + /// 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 _$ProductLoaderStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of ProductLoaderState + /// 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 'ProductLoaderState.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 ProductLoaderState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore) + loaded, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + 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? 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 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 ProductLoaderState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of ProductLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/home/bloc/product_loader/product_loader_event.dart b/lib/presentation/home/bloc/product_loader/product_loader_event.dart new file mode 100644 index 0000000..3ff5bb6 --- /dev/null +++ b/lib/presentation/home/bloc/product_loader/product_loader_event.dart @@ -0,0 +1,10 @@ +part of 'product_loader_bloc.dart'; + +@freezed +class ProductLoaderEvent with _$ProductLoaderEvent { + const factory ProductLoaderEvent.getProduct( + {String? categoryId, String? search}) = _GetProduct; + const factory ProductLoaderEvent.loadMore( + {String? categoryId, String? search}) = _LoadMore; + const factory ProductLoaderEvent.refresh() = _Refresh; +} diff --git a/lib/presentation/home/bloc/product_loader/product_loader_state.dart b/lib/presentation/home/bloc/product_loader/product_loader_state.dart new file mode 100644 index 0000000..1464f78 --- /dev/null +++ b/lib/presentation/home/bloc/product_loader/product_loader_state.dart @@ -0,0 +1,14 @@ +part of 'product_loader_bloc.dart'; + +@freezed +class ProductLoaderState with _$ProductLoaderState { + const factory ProductLoaderState.initial() = _Initial; + const factory ProductLoaderState.loading() = _Loading; + const factory ProductLoaderState.loaded({ + required List products, + required bool hasReachedMax, + required int currentPage, + required bool isLoadingMore, + }) = _Loaded; + const factory ProductLoaderState.error(String message) = _Error; +} diff --git a/lib/presentation/home/bloc/qris/qris_bloc.dart b/lib/presentation/home/bloc/qris/qris_bloc.dart index d9a4ac4..42c5936 100644 --- a/lib/presentation/home/bloc/qris/qris_bloc.dart +++ b/lib/presentation/home/bloc/qris/qris_bloc.dart @@ -1,4 +1,3 @@ -import 'dart:async'; import 'dart:developer'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart b/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart new file mode 100644 index 0000000..e614e35 --- /dev/null +++ b/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart @@ -0,0 +1,30 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/user_remote_datasource.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'user_update_outlet_event.dart'; +part 'user_update_outlet_state.dart'; +part 'user_update_outlet_bloc.freezed.dart'; + +class UserUpdateOutletBloc + extends Bloc { + final UserRemoteDatasource _userRemoteDatasource; + + UserUpdateOutletBloc(this._userRemoteDatasource) + : super(UserUpdateOutletState.initial()) { + on<_Update>( + (event, emit) async { + emit(const _Loading()); + + final result = await _userRemoteDatasource.updateOutlet( + event.outlet, + ); + + result.fold( + (error) => emit(_Error(error)), + (success) => emit(_Success()), + ); + }, + ); + } +} diff --git a/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.freezed.dart b/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.freezed.dart new file mode 100644 index 0000000..75d6ad0 --- /dev/null +++ b/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.freezed.dart @@ -0,0 +1,811 @@ +// 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 'user_update_outlet_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$UserUpdateOutletEvent { + String get outlet => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(String outlet) update, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String outlet)? update, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String outlet)? update, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Update value) update, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Update value)? update, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Update value)? update, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Create a copy of UserUpdateOutletEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $UserUpdateOutletEventCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UserUpdateOutletEventCopyWith<$Res> { + factory $UserUpdateOutletEventCopyWith(UserUpdateOutletEvent value, + $Res Function(UserUpdateOutletEvent) then) = + _$UserUpdateOutletEventCopyWithImpl<$Res, UserUpdateOutletEvent>; + @useResult + $Res call({String outlet}); +} + +/// @nodoc +class _$UserUpdateOutletEventCopyWithImpl<$Res, + $Val extends UserUpdateOutletEvent> + implements $UserUpdateOutletEventCopyWith<$Res> { + _$UserUpdateOutletEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of UserUpdateOutletEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? outlet = null, + }) { + return _then(_value.copyWith( + outlet: null == outlet + ? _value.outlet + : outlet // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$UpdateImplCopyWith<$Res> + implements $UserUpdateOutletEventCopyWith<$Res> { + factory _$$UpdateImplCopyWith( + _$UpdateImpl value, $Res Function(_$UpdateImpl) then) = + __$$UpdateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String outlet}); +} + +/// @nodoc +class __$$UpdateImplCopyWithImpl<$Res> + extends _$UserUpdateOutletEventCopyWithImpl<$Res, _$UpdateImpl> + implements _$$UpdateImplCopyWith<$Res> { + __$$UpdateImplCopyWithImpl( + _$UpdateImpl _value, $Res Function(_$UpdateImpl) _then) + : super(_value, _then); + + /// Create a copy of UserUpdateOutletEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? outlet = null, + }) { + return _then(_$UpdateImpl( + null == outlet + ? _value.outlet + : outlet // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$UpdateImpl implements _Update { + const _$UpdateImpl(this.outlet); + + @override + final String outlet; + + @override + String toString() { + return 'UserUpdateOutletEvent.update(outlet: $outlet)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UpdateImpl && + (identical(other.outlet, outlet) || other.outlet == outlet)); + } + + @override + int get hashCode => Object.hash(runtimeType, outlet); + + /// Create a copy of UserUpdateOutletEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$UpdateImplCopyWith<_$UpdateImpl> get copyWith => + __$$UpdateImplCopyWithImpl<_$UpdateImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String outlet) update, + }) { + return update(outlet); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String outlet)? update, + }) { + return update?.call(outlet); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String outlet)? update, + required TResult orElse(), + }) { + if (update != null) { + return update(outlet); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Update value) update, + }) { + return update(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Update value)? update, + }) { + return update?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Update value)? update, + required TResult orElse(), + }) { + if (update != null) { + return update(this); + } + return orElse(); + } +} + +abstract class _Update implements UserUpdateOutletEvent { + const factory _Update(final String outlet) = _$UpdateImpl; + + @override + String get outlet; + + /// Create a copy of UserUpdateOutletEvent + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$UpdateImplCopyWith<_$UpdateImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$UserUpdateOutletState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UserUpdateOutletStateCopyWith<$Res> { + factory $UserUpdateOutletStateCopyWith(UserUpdateOutletState value, + $Res Function(UserUpdateOutletState) then) = + _$UserUpdateOutletStateCopyWithImpl<$Res, UserUpdateOutletState>; +} + +/// @nodoc +class _$UserUpdateOutletStateCopyWithImpl<$Res, + $Val extends UserUpdateOutletState> + implements $UserUpdateOutletStateCopyWith<$Res> { + _$UserUpdateOutletStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of UserUpdateOutletState + /// 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 _$UserUpdateOutletStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of UserUpdateOutletState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'UserUpdateOutletState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements UserUpdateOutletState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$UserUpdateOutletStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of UserUpdateOutletState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'UserUpdateOutletState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class _Loading implements UserUpdateOutletState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$SuccessImplCopyWith<$Res> { + factory _$$SuccessImplCopyWith( + _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = + __$$SuccessImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SuccessImplCopyWithImpl<$Res> + extends _$UserUpdateOutletStateCopyWithImpl<$Res, _$SuccessImpl> + implements _$$SuccessImplCopyWith<$Res> { + __$$SuccessImplCopyWithImpl( + _$SuccessImpl _value, $Res Function(_$SuccessImpl) _then) + : super(_value, _then); + + /// Create a copy of UserUpdateOutletState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SuccessImpl implements _Success { + const _$SuccessImpl(); + + @override + String toString() { + return 'UserUpdateOutletState.success()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SuccessImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return success(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return success?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return success(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return success?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(this); + } + return orElse(); + } +} + +abstract class _Success implements UserUpdateOutletState { + const factory _Success() = _$SuccessImpl; +} + +/// @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 _$UserUpdateOutletStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of UserUpdateOutletState + /// 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 'UserUpdateOutletState.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 UserUpdateOutletState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements UserUpdateOutletState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of UserUpdateOutletState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_event.dart b/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_event.dart new file mode 100644 index 0000000..afccd81 --- /dev/null +++ b/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_event.dart @@ -0,0 +1,6 @@ +part of 'user_update_outlet_bloc.dart'; + +@freezed +class UserUpdateOutletEvent with _$UserUpdateOutletEvent { + const factory UserUpdateOutletEvent.update(String outlet) = _Update; +} diff --git a/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_state.dart b/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_state.dart new file mode 100644 index 0000000..1eadb6f --- /dev/null +++ b/lib/presentation/home/bloc/user_update_outlet/user_update_outlet_state.dart @@ -0,0 +1,9 @@ +part of 'user_update_outlet_bloc.dart'; + +@freezed +class UserUpdateOutletState with _$UserUpdateOutletState { + const factory UserUpdateOutletState.initial() = _Initial; + const factory UserUpdateOutletState.loading() = _Loading; + const factory UserUpdateOutletState.success() = _Success; + const factory UserUpdateOutletState.error(String message) = _Error; +} diff --git a/lib/presentation/home/dialog/confirm_save_order_dialog.dart b/lib/presentation/home/dialog/confirm_save_order_dialog.dart new file mode 100644 index 0000000..5f6bd48 --- /dev/null +++ b/lib/presentation/home/dialog/confirm_save_order_dialog.dart @@ -0,0 +1,410 @@ +import 'package:enaklo_pos/core/components/flushbar.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/int_ext.dart'; +import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import 'package:enaklo_pos/data/models/response/delivery_response_model.dart'; +import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart'; +import 'package:enaklo_pos/presentation/home/models/order_type.dart'; +import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; +import 'package:enaklo_pos/presentation/success/pages/success_save_order_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ConfirmSaveOrderDialog extends StatelessWidget { + final String customerName; + final OrderType orderType; + final List items; + final Customer? customer; + final DeliveryModel? delivery; + + const ConfirmSaveOrderDialog({ + super.key, + required this.customerName, + required this.orderType, + required this.items, + this.customer, + this.delivery, + }); + + @override + Widget build(BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 8, + child: Container( + width: MediaQuery.of(context).size.width * 0.9, + constraints: BoxConstraints( + maxWidth: 600, + maxHeight: MediaQuery.of(context).size.height * 0.8, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Header + Container( + width: double.infinity, + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8) + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: Column( + children: [ + Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + shape: BoxShape.circle, + ), + child: Icon( + Icons.warning_rounded, + color: Colors.white, + size: 28, + ), + ), + SizedBox(height: 12), + Text( + 'Konfirmasi Pesanan', + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'Tindakan ini tidak dapat dibatalkan', + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 12, + ), + ), + ], + ), + ), + + // Scrollable Content + Flexible( + child: SingleChildScrollView( + padding: EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Main message + Container( + width: double.infinity, + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey[200]!), + ), + child: Text( + message, + style: TextStyle( + fontSize: 14, + height: 1.4, + color: Colors.grey[800], + ), + ), + ), + + if (items.isNotEmpty) ...[ + SizedBox(height: 16), + + // Items section + Container( + width: double.infinity, + decoration: BoxDecoration( + color: Colors.orange[50], + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.orange[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + Container( + padding: EdgeInsets.all(6), + decoration: BoxDecoration( + color: Colors.orange[100], + borderRadius: BorderRadius.circular(6), + ), + child: Icon( + Icons.list_alt_rounded, + color: Colors.orange[700], + size: 16, + ), + ), + SizedBox(width: 8), + Text( + 'Item yang akan dipesan:', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + color: Colors.orange[800], + ), + ), + ], + ), + ), + Container( + constraints: BoxConstraints(maxHeight: 120), + child: Scrollbar( + child: SingleChildScrollView( + padding: EdgeInsets.only( + left: 16, right: 16, bottom: 16), + child: Column( + children: items.map((item) { + return Container( + margin: EdgeInsets.only(bottom: 6), + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular(6), + boxShadow: [ + BoxShadow( + color: Colors.black + .withOpacity(0.03), + blurRadius: 2, + offset: Offset(0, 1), + ), + ], + ), + child: Row( + children: [ + Container( + width: 6, + height: 6, + decoration: BoxDecoration( + color: Colors.red[400], + shape: BoxShape.circle, + ), + ), + SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + item.product.name ?? + 'Unknown Product', + style: TextStyle( + fontWeight: + FontWeight.w600, + fontSize: 12, + ), + maxLines: 1, + overflow: + TextOverflow.ellipsis, + ), + Text( + '${item.quantity} qty', + style: TextStyle( + fontSize: 10, + color: Colors.grey[600], + ), + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.red[100], + borderRadius: + BorderRadius.circular(4), + ), + child: Text( + ((item.product.price ?? 0) * + item.quantity) + .currencyFormatRpV2, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: Colors.red[700], + ), + ), + ), + ], + ), + ); + }).toList(), + ), + ), + ), + ), + ], + ), + ), + ], + + SizedBox(height: 16), + ], + ), + ), + ), + + // Action buttons + Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(16), + bottomRight: Radius.circular(16), + ), + ), + child: Row( + children: [ + Expanded( + child: SizedBox( + height: 44, + child: ElevatedButton( + onPressed: () => Navigator.pop(context), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.grey[300], + foregroundColor: Colors.grey[700], + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.close_rounded, size: 18), + SizedBox(width: 6), + Text( + 'Batal', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ), + SizedBox(width: 12), + Expanded( + child: BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: (data) { + context.pushReplacement(SuccessSaveOrderPage( + productQuantity: items, + orderId: data.id ?? "", + )); + }, + error: (message) => + AppFlushbar.showError(context, message), + ); + }, + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => SizedBox( + height: 44, + child: ElevatedButton( + onPressed: () { + context.read().add( + OrderFormEvent.create( + items: items, + customerName: customerName, + orderType: orderType, + table: null, + customer: customer, + ), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + foregroundColor: Colors.white, + elevation: 2, + shadowColor: + AppColors.primary.withOpacity(0.3), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.delete_forever_rounded, + size: 18), + SizedBox(width: 6), + Text( + 'Konfirmasi', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + loading: () => + Center(child: CircularProgressIndicator()), + ); + }, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + String get message { + switch (orderType) { + case OrderType.dineIn: + return 'Konfirmasi untuk menyimpan pesanan Dine-In\n\n' + 'Pesanan untuk ${customerName.isNotEmpty ? customerName : "Pelanggan"} akan disimpan ke dalam sistem. ' + 'Pelanggan dapat langsung menikmati pesanan di meja yang telah disediakan. ' + 'Pastikan semua item pesanan sudah sesuai sebelum menyimpan.'; + + case OrderType.delivery: + return 'Konfirmasi untuk menyimpan pesanan Delivery\n\n' + 'Pesanan delivery untuk ${customerName.isNotEmpty ? customerName : "Pelanggan"} akan disimpan. ' + '${delivery != null ? "Pengiriman: ${delivery!.name}. " : ""}' + 'Tim delivery akan segera mempersiapkan pesanan.'; + + case OrderType.takeAway: + return 'Konfirmasi untuk menyimpan pesanan Take Away\n\n' + 'Pesanan take away untuk ${customerName.isNotEmpty ? customerName : "Pelanggan"} akan disimpan. ' + 'Dapur akan mulai mempersiapkan pesanan dan pelanggan dapat mengambil pesanan sesuai estimasi waktu yang diberikan.'; + + case OrderType.freeTable: + return 'Konfirmasi untuk menyimpan pesanan Free Table\n\n' + 'Pesanan free table untuk ${customerName.isNotEmpty ? customerName : "Pelanggan"} akan disimpan. ' + 'Meja akan direservasi dan pesanan akan dipersiapkan sesuai dengan waktu kedatangan pelanggan.'; + } + } +} diff --git a/lib/presentation/home/dialog/delivery_dialog.dart b/lib/presentation/home/dialog/delivery_dialog.dart new file mode 100644 index 0000000..5058f0f --- /dev/null +++ b/lib/presentation/home/dialog/delivery_dialog.dart @@ -0,0 +1,114 @@ +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/data/datasources/delivery_local_datasource.dart'; +import 'package:enaklo_pos/data/models/response/delivery_response_model.dart'; +import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class DeliveryDialog extends StatefulWidget { + const DeliveryDialog({super.key}); + + @override + State createState() => _DeliveryDialogState(); +} + +class _DeliveryDialogState extends State { + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Pilih Pengiriman', + subtitle: 'Silahkan pilih pengiriman yang sesuai', + contentPadding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0), + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const SizedBox.shrink(), + loaded: (items, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType) { + return Column( + children: List.generate(deliveries.length, (index) { + return _buildItem( + context, + deliveries[index], + selectedType: deliveryType, + ); + }), + ); + }); + }, + ), + ); + } + + Widget _buildItem(BuildContext context, DeliveryModel delivery, + {DeliveryModel? selectedType}) { + return GestureDetector( + onTap: () { + context.read().add( + CheckoutEvent.updateDeliveryType(delivery), + ); + Navigator.pop(context); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0), + margin: const EdgeInsets.only(bottom: 8.0), + decoration: BoxDecoration( + color: selectedType?.id == delivery.id + ? AppColors.primary + : AppColors.white, + borderRadius: BorderRadius.circular(8.0), + border: Border.all( + color: selectedType?.id == delivery.id + ? AppColors.primary + : AppColors.grey, + width: 1.0, + ), + ), + child: Row( + children: [ + Image.asset( + delivery.imageUrl, + width: 40.0, + height: 40.0, + fit: BoxFit.contain, + ), + SpaceWidth(12.0), + Expanded( + child: Text( + delivery.name, + style: TextStyle( + fontSize: 16, + color: selectedType?.id == delivery.id + ? AppColors.white + : AppColors.black, + fontWeight: FontWeight.bold, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + SpaceWidth(12.0), + Icon( + Icons.check_circle, + color: selectedType?.id == delivery.id + ? AppColors.green + : Colors.transparent, + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/home/dialog/discount_dialog.dart b/lib/presentation/home/dialog/discount_dialog.dart index 30ecb8b..f951684 100644 --- a/lib/presentation/home/dialog/discount_dialog.dart +++ b/lib/presentation/home/dialog/discount_dialog.dart @@ -1,3 +1,5 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/data/models/response/discount_response_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; @@ -25,29 +27,38 @@ class _DiscountDialogState extends State { @override Widget build(BuildContext context) { return AlertDialog( - title: Stack( - alignment: Alignment.center, + backgroundColor: AppColors.white, + title: Row( children: [ - const Text( - 'DISKON', - style: TextStyle( - color: AppColors.primary, - fontSize: 28, - fontWeight: FontWeight.w600, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Pilih Diskon', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.black, + ), + ), + const SizedBox(height: 4.0), + Text( + 'Pilih diskon yang ingin diterapkan pada pesanan', + style: TextStyle( + fontSize: 14, + color: Colors.grey, + ), + ), + ], ), ), - Align( - alignment: Alignment.centerRight, - child: IconButton( - onPressed: () { - context.pop(); - }, - icon: const Icon( - Icons.cancel, - color: AppColors.primary, - size: 30.0, - ), - ), + SpaceWidth(12), + IconButton( + icon: const Icon(Icons.close, color: AppColors.black), + onPressed: () { + context.pop(); + }, ), ], ), @@ -63,30 +74,7 @@ class _DiscountDialogState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: discounts - .map( - (discount) => ListTile( - title: Text('Nama Diskon: ${discount.name}'), - subtitle: Text('Potongan harga (${discount.value}%)'), - contentPadding: EdgeInsets.zero, - textColor: AppColors.primary, - trailing: Checkbox( - value: discount.id == discountIdSelected, - onChanged: (value) { - setState(() { - discountIdSelected = discount.id!; - context.read().add( - CheckoutEvent.addDiscount( - discount, - ), - ); - }); - }, - ), - onTap: () { - // context.pop(); - }, - ), - ) + .map((discount) => _buildDiscountItem(discount)) .toList(), ); }, @@ -95,4 +83,58 @@ class _DiscountDialogState extends State { ), ); } + + Widget _buildDiscountItem(Discount discount) { + return GestureDetector( + onTap: () { + setState(() { + discountIdSelected = discount.id!; + context.read().add( + CheckoutEvent.addDiscount( + discount, + ), + ); + }); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0), + margin: const EdgeInsets.only(bottom: 8.0), + decoration: BoxDecoration( + color: discountIdSelected == discount.id + ? AppColors.primary + : AppColors.white, + borderRadius: BorderRadius.circular(8.0), + border: Border.all( + color: discountIdSelected == discount.id + ? AppColors.primary + : AppColors.grey, + width: 1.0, + ), + ), + child: Row( + children: [ + Expanded( + child: Text( + "${discount.name} (${discount.value})", + style: TextStyle( + fontSize: 16, + color: discountIdSelected == discount.id + ? AppColors.white + : AppColors.black, + fontWeight: FontWeight.w500, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + SpaceWidth(12.0), + Icon(Icons.check_circle, + color: discountIdSelected == discount.id + ? AppColors.green + : Colors.transparent), + ], + ), + ), + ); + } } diff --git a/lib/presentation/home/dialog/outlet_dialog.dart b/lib/presentation/home/dialog/outlet_dialog.dart new file mode 100644 index 0000000..3de3e76 --- /dev/null +++ b/lib/presentation/home/dialog/outlet_dialog.dart @@ -0,0 +1,109 @@ +import 'package:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/presentation/home/bloc/outlet_loader/outlet_loader_bloc.dart'; +import 'package:enaklo_pos/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart'; +import 'package:enaklo_pos/presentation/home/models/outlet_model.dart'; +import 'package:enaklo_pos/presentation/home/widgets/outlet_card.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class OutletDialog extends StatefulWidget { + const OutletDialog({super.key}); + + @override + State createState() => _OutletDialogState(); +} + +class _OutletDialogState extends State { + Outlet? selectedOutlet; + + void selectOutlet(Outlet outlet) { + setState(() { + if (selectedOutlet == outlet) { + selectedOutlet = null; // Deselect jika outlet yang sama diklik + } else { + selectedOutlet = + outlet; // Select outlet baru (akan mengganti selection sebelumnya) + } + }); + } + + @override + void initState() { + super.initState(); + context.read().add(OutletLoaderEvent.getOutlet()); + } + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Outlet', + subtitle: 'Silahkan pilih outlet', + minWidth: context.deviceWidth * 0.4, + contentPadding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0), + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Center( + child: Text('Error has occured'), + ), + loading: () => Center(child: CircularProgressIndicator()), + error: (message) => Center( + child: Text(message), + ), + loaded: (outlets) => Column( + children: [ + ...List.generate( + outlets.length, + (index) => GestureDetector( + onTap: () { + selectOutlet(outlets[index]); + }, + child: OutletCard( + outlet: outlets[index], + isSelected: selectedOutlet == outlets[index], + ), + ), + ), + SpaceHeight(24), + BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: () { + context.pop(); + }, + ); + }, + child: + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Button.filled( + onPressed: selectedOutlet == null + ? null + : () { + context.read().add( + UserUpdateOutletEvent.update( + selectedOutlet!.id ?? "")); + }, + label: 'Terapkan', + ), + loading: () => Center( + child: CircularProgressIndicator(), + ), + ); + }, + ), + ), + ], + ), + ); + }, + ), + ); + } +} diff --git a/lib/presentation/home/dialog/payment_add_order_dialog.dart b/lib/presentation/home/dialog/payment_add_order_dialog.dart new file mode 100644 index 0000000..f2a0906 --- /dev/null +++ b/lib/presentation/home/dialog/payment_add_order_dialog.dart @@ -0,0 +1,329 @@ +import 'dart:developer'; + +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/components/flushbar.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.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/models/product_quantity.dart'; +import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart'; +import 'package:enaklo_pos/presentation/success/pages/success_save_order_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class PaymentAddOrderDialog extends StatefulWidget { + final List items; + const PaymentAddOrderDialog({super.key, required this.items}); + + @override + State createState() => _PaymentAddOrderDialogState(); +} + +class _PaymentAddOrderDialogState extends State { + Order? selectOrder; + + @override + void initState() { + super.initState(); + context.read().add( + OrderLoaderEvent.getByStatus( + 'pending', + dateFrom: DateTime.now(), + dateTo: DateTime.now(), + limit: 20, + ), + ); + } + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Bayar Nanti', + subtitle: 'Simpan pesanan dan bayar nanti', + contentPadding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0), + minWidth: context.deviceWidth * 0.4, + minHeight: context.deviceHeight * 0.4, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Pilih Meja yang sudah dipesan', + style: TextStyle( + color: AppColors.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SpaceHeight(6.0), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => + Center(child: const CircularProgressIndicator()), + loading: () => + Center(child: const CircularProgressIndicator()), + loaded: ( + orders, + totalOrder, + _, + __, + ___, + ) { + final availableOrders = orders; + + if (selectOrder == null && availableOrders.isNotEmpty) { + selectOrder = availableOrders.first; + } + + if (availableOrders.isEmpty) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.orange[50], + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Colors.orange, + width: 1, + ), + ), + child: const Text( + 'Tidak ada meja yang tersedia. Silakan pilih opsi lain.', + style: TextStyle(color: Colors.orange), + ), + ); + } + + return DropdownSearch( + items: orders, + selectedItem: selectOrder, + + // Dropdown properties + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + hintText: "Pilih Meja", + hintStyle: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + ), + prefixIcon: Icon( + Icons.category_outlined, + color: Colors.grey.shade500, + size: 20, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, + width: 1.5, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, + width: 1.5, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.blue.shade400, + width: 2, + ), + ), + filled: true, + fillColor: Colors.white, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + ), + ), + + // Popup properties + popupProps: PopupProps.menu( + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Cari meja...", + prefixIcon: const Icon(Icons.search), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + ), + menuProps: MenuProps( + backgroundColor: Colors.white, + elevation: 8, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + itemBuilder: (context, order, isSelected) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + decoration: BoxDecoration( + color: isSelected + ? Colors.blue.shade50 + : Colors.transparent, + border: Border( + bottom: BorderSide( + color: Colors.grey.shade100, + width: 0.5, + ), + ), + ), + child: Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: isSelected + ? Colors.blue.shade600 + : Colors.grey.shade400, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + "${order.tableNumber ?? ""} - ${order.metadata?['customer_name']}", + style: TextStyle( + fontSize: 14, + fontWeight: isSelected + ? FontWeight.w600 + : FontWeight.w500, + color: isSelected + ? Colors.blue.shade700 + : Colors.black87, + ), + ), + ), + if (isSelected) + Icon( + Icons.check, + color: Colors.blue.shade600, + size: 18, + ), + ], + ), + ); + }, + emptyBuilder: (context, searchEntry) { + return Container( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.search_off, + color: Colors.grey.shade400, + size: 48, + ), + const SizedBox(height: 12), + Text( + searchEntry.isEmpty + ? "Tidak ada meja tersedia" + : "Tidak ditemukan meja dengan '${searchEntry}'", + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + ], + ), + ); + }, + ), + + // Item as string (for search functionality) + itemAsString: (Order order) => order.tableNumber ?? "", + + // Comparison function + compareFn: (Order? item1, Order? item2) { + return item1?.id == item2?.id; + }, + + // On changed callback + onChanged: (Order? selectedOrder) { + if (selectedOrder != null) { + setState(() { + selectOrder = selectedOrder; + }); + log("selectOrder: ${selectOrder!.tableNumber}"); + } + }, + + // Validator (optional) + validator: (Order? value) { + if (value == null) { + return "Meja harus dipilih"; + } + return null; + }, + ); + }, + ); + }), + ], + ), + SpaceHeight(24), + BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + successMsg: () { + context.pop(); + context.pushReplacement( + SuccessSaveOrderPage( + productQuantity: widget.items, + orderId: selectOrder?.id ?? "", + ), + ); + }, + error: (message) => AppFlushbar.showError(context, message), + ); + }, + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Button.filled( + onPressed: () { + context + .read() + .add(OrderFormEvent.addToOrder( + items: widget.items, + orderId: selectOrder?.id ?? "", + )); + }, + label: "Simpan", + ), + loading: () => Center( + child: const CircularProgressIndicator(), + ), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/home/dialog/payment_qris_dialog.dart b/lib/presentation/home/dialog/payment_qris_dialog.dart index 483545b..33688f2 100644 --- a/lib/presentation/home/dialog/payment_qris_dialog.dart +++ b/lib/presentation/home/dialog/payment_qris_dialog.dart @@ -13,8 +13,6 @@ import 'package:intl/intl.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/widgets/success_payment_dialog.dart'; -import 'package:enaklo_pos/presentation/setting/bloc/sync_order/sync_order_bloc.dart'; -import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart'; import 'package:widgets_to_image/widgets_to_image.dart'; import 'package:enaklo_pos/core/utils/printer_service.dart'; @@ -230,24 +228,25 @@ class _PaymentQrisDialogState extends State { ElevatedButton( onPressed: () async { try { - final sizeReceipt = await AuthLocalDataSource().getSizeReceipt(); + final sizeReceipt = + await AuthLocalDataSource().getSizeReceipt(); final bytes = await controller.capture(); final printValue = await PrintDataoutputs.instance - .printQRIS(widget.price, bytes!, int.parse(sizeReceipt)); - + .printQRIS( + widget.price, bytes!, int.parse(sizeReceipt)); + // Get the receipt printer to print QRIS - final receiptPrinter = await ProductLocalDatasource.instance + final receiptPrinter = await ProductLocalDatasource + .instance .getPrinterByCode('receipt'); - + if (receiptPrinter != null) { await PrinterService().printWithPrinter( - receiptPrinter, - printValue, - context - ); + receiptPrinter, printValue, context); } else { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('No receipt printer configured')), + SnackBar( + content: Text('No receipt printer configured')), ); } } catch (e) { diff --git a/lib/presentation/home/dialog/payment_save_dialog.dart b/lib/presentation/home/dialog/payment_save_dialog.dart new file mode 100644 index 0000000..313847e --- /dev/null +++ b/lib/presentation/home/dialog/payment_save_dialog.dart @@ -0,0 +1,335 @@ +import 'dart:developer'; + +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/components/flushbar.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import 'package:enaklo_pos/data/models/response/table_model.dart'; +import 'package:enaklo_pos/presentation/home/bloc/get_table_status/get_table_status_bloc.dart'; +import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart'; +import 'package:enaklo_pos/presentation/home/models/order_type.dart'; +import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; +import 'package:enaklo_pos/presentation/success/pages/success_save_order_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class PaymentSaveDialog extends StatefulWidget { + final TableModel? selectedTable; + final String customerName; + final OrderType orderType; + final List items; + final Customer? customer; + const PaymentSaveDialog({ + super.key, + required this.selectedTable, + required this.customerName, + required this.orderType, + required this.items, + required this.customer, + }); + + @override + State createState() => _PaymentSaveDialogState(); +} + +class _PaymentSaveDialogState extends State { + TableModel? selectTable; + + @override + void initState() { + super.initState(); + context + .read() + .add(GetTableStatusEvent.getTablesStatus('available')); + if (widget.selectedTable != null) { + selectTable = widget.selectedTable; + } + } + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Bayar Nanti', + subtitle: 'Simpan pesanan dan bayar nanti', + contentPadding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0), + minWidth: context.deviceWidth * 0.4, + minHeight: context.deviceHeight * 0.4, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Pilih Meja', + style: TextStyle( + color: AppColors.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SpaceHeight(6.0), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => + Center(child: const CircularProgressIndicator()), + loading: () => + Center(child: const CircularProgressIndicator()), + success: (tables) { + final availableTables = tables; + + if (selectTable == null && availableTables.isNotEmpty) { + selectTable = availableTables.first; + } + + if (availableTables.isEmpty) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.orange[50], + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Colors.orange, + width: 1, + ), + ), + child: const Text( + 'Tidak ada meja yang tersedia. Silakan pilih opsi lain.', + style: TextStyle(color: Colors.orange), + ), + ); + } + + return DropdownSearch( + items: tables, + selectedItem: selectTable, + + // Dropdown properties + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + hintText: "Pilih meja", + hintStyle: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + ), + prefixIcon: Icon( + Icons.category_outlined, + color: Colors.grey.shade500, + size: 20, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, + width: 1.5, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, + width: 1.5, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.blue.shade400, + width: 2, + ), + ), + filled: true, + fillColor: Colors.white, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + ), + ), + + // Popup properties + popupProps: PopupProps.menu( + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Cari meja...", + prefixIcon: const Icon(Icons.search), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + ), + menuProps: MenuProps( + backgroundColor: Colors.white, + elevation: 8, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + itemBuilder: (context, category, isSelected) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + decoration: BoxDecoration( + color: isSelected + ? Colors.blue.shade50 + : Colors.transparent, + border: Border( + bottom: BorderSide( + color: Colors.grey.shade100, + width: 0.5, + ), + ), + ), + child: Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: isSelected + ? Colors.blue.shade600 + : Colors.grey.shade400, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + category.tableName ?? "", + style: TextStyle( + fontSize: 14, + fontWeight: isSelected + ? FontWeight.w600 + : FontWeight.w500, + color: isSelected + ? Colors.blue.shade700 + : Colors.black87, + ), + ), + ), + if (isSelected) + Icon( + Icons.check, + color: Colors.blue.shade600, + size: 18, + ), + ], + ), + ); + }, + emptyBuilder: (context, searchEntry) { + return Container( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.search_off, + color: Colors.grey.shade400, + size: 48, + ), + const SizedBox(height: 12), + Text( + searchEntry.isEmpty + ? "Tidak ada meja tersedia" + : "Tidak ditemukan meja dengan '${searchEntry}'", + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + ], + ), + ); + }, + ), + + // Item as string (for search functionality) + itemAsString: (TableModel table) => table.tableName ?? "", + + // Comparison function + compareFn: (TableModel? item1, TableModel? item2) { + return item1?.id == item2?.id; + }, + + // On changed callback + onChanged: (TableModel? selectedTable) { + if (selectedTable != null) { + setState(() { + selectTable = selectedTable; + }); + log("selectTable: ${selectTable!.tableName}"); + } + }, + + // Validator (optional) + validator: (TableModel? value) { + if (value == null) { + return "Meja harus dipilih"; + } + return null; + }, + ); + }, + ); + }), + ], + ), + SpaceHeight(24), + BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: (data) { + context.pop(); + context.pushReplacement(SuccessSaveOrderPage( + productQuantity: widget.items, + orderId: data.id ?? "", + )); + }, + error: (message) => AppFlushbar.showError(context, message), + ); + }, + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Button.filled( + onPressed: () { + context.read().add( + OrderFormEvent.create( + items: widget.items, + customerName: widget.customerName, + orderType: widget.orderType, + table: selectTable!, + customer: widget.customer, + ), + ); + }, + label: "Simpan", + ), + loading: () => Center( + child: const CircularProgressIndicator(), + ), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/home/dialog/save_dialog.dart b/lib/presentation/home/dialog/save_dialog.dart new file mode 100644 index 0000000..ce72923 --- /dev/null +++ b/lib/presentation/home/dialog/save_dialog.dart @@ -0,0 +1,134 @@ +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import 'package:enaklo_pos/data/models/response/delivery_response_model.dart'; +import 'package:enaklo_pos/data/models/response/table_model.dart'; +import 'package:enaklo_pos/presentation/home/dialog/confirm_save_order_dialog.dart'; +import 'package:enaklo_pos/presentation/home/dialog/payment_add_order_dialog.dart'; +import 'package:enaklo_pos/presentation/home/dialog/payment_save_dialog.dart'; +import 'package:enaklo_pos/presentation/home/models/order_type.dart'; +import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; +import 'package:flutter/material.dart'; + +class SaveDialog extends StatefulWidget { + final TableModel? selectedTable; + final String customerName; + final OrderType orderType; + final List items; + final Customer? customer; + final DeliveryModel? deliveryModel; + + const SaveDialog({ + super.key, + required this.selectedTable, + required this.customerName, + required this.orderType, + required this.items, + required this.customer, + this.deliveryModel, + }); + + @override + State createState() => _SaveDialogState(); +} + +class _SaveDialogState extends State { + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Pilih Aksi', + subtitle: 'Lanjutkan proses pesanan atau simpan untuk nanti.', + contentPadding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0), + child: Column( + children: [ + _item( + icon: Icons.schedule_outlined, + title: 'Bayar Nanti', + subtitle: 'Simpan pesanan dan bayar nanti', + onTap: () { + context.pop(); + if (OrderType.dineIn == widget.orderType) { + showDialog( + context: context, + builder: (context) => PaymentSaveDialog( + selectedTable: widget.selectedTable, + customerName: widget.customerName, + orderType: widget.orderType, + items: widget.items, + customer: widget.customer, + ), + ); + } else { + showDialog( + context: context, + builder: (context) => ConfirmSaveOrderDialog( + delivery: widget.deliveryModel, + customerName: widget.customerName, + orderType: widget.orderType, + items: widget.items, + customer: widget.customer, + ), + ); + } + }), + SpaceHeight(16.0), + _item( + icon: Icons.shopping_cart_checkout_outlined, + title: 'Tambahkan Pesanan', + subtitle: 'Tambah item ke daftar pesanan', + onTap: () => showDialog( + context: context, + builder: (context) => PaymentAddOrderDialog( + items: widget.items, + ), + ), + ), + ], + ), + ); + } + + Widget _item({ + required IconData icon, + required String title, + required String subtitle, + required Function() onTap, + }) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(8.0), + border: Border.all( + color: AppColors.grey, + width: 1.0, + ), + ), + child: Row( + children: [ + Icon(icon, color: AppColors.primary, size: 26.0), + SpaceWidth(12.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle(fontWeight: FontWeight.w600), + ), + Text( + subtitle, + style: const TextStyle(color: Colors.grey), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/home/dialog/service_dialog.dart b/lib/presentation/home/dialog/service_dialog.dart index 8d76cd6..ccff751 100644 --- a/lib/presentation/home/dialog/service_dialog.dart +++ b/lib/presentation/home/dialog/service_dialog.dart @@ -1,3 +1,4 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; @@ -11,27 +12,38 @@ class ServiceDialog extends StatelessWidget { @override Widget build(BuildContext context) { return AlertDialog( - title: Stack( - alignment: Alignment.center, + backgroundColor: AppColors.white, + title: Row( children: [ - const Text( - 'LAYANAN', - style: TextStyle( - color: AppColors.primary, - fontSize: 28, - fontWeight: FontWeight.w600, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Pilih Layanan', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.black, + ), + ), + const SizedBox(height: 4.0), + Text( + 'Pilih layanan yang ingin diterapkan pada pesanan', + style: TextStyle( + fontSize: 14, + color: Colors.grey, + ), + ), + ], ), ), - Align( - alignment: Alignment.centerRight, - child: IconButton( - onPressed: () => context.pop(), - icon: const Icon( - Icons.cancel, - color: AppColors.primary, - size: 30.0, - ), - ), + SpaceWidth(12), + IconButton( + icon: const Icon(Icons.close, color: AppColors.black), + onPressed: () { + context.pop(); + }, ), ], ), @@ -44,23 +56,20 @@ class ServiceDialog extends StatelessWidget { return state.maybeWhen( initial: () => const SizedBox(), loading: () => const Center(child: CircularProgressIndicator()), - loaded: (data, a, b, c, d, service, e, f, g, orderType) => ListTile( - title: Text('Presentase ($service%)'), - subtitle: const Text('Biaya layanan'), - contentPadding: EdgeInsets.zero, - textColor: AppColors.primary, - trailing: Checkbox( - value: service > 0, - onChanged: (value) { - context.read().add( - CheckoutEvent.addServiceCharge(service > 0 ? 0 : service), - ); - }, - ), - onTap: () { - context.pop(); - }, - ), + loaded: ( + data, + a, + b, + c, + d, + service, + e, + f, + g, + orderType, + deliveryType, + ) => + _buildServiceItem(context, service), orElse: () => const SizedBox(), ); }, @@ -69,4 +78,47 @@ class ServiceDialog extends StatelessWidget { ), ); } + + Widget _buildServiceItem(BuildContext context, int service) { + return GestureDetector( + onTap: () { + context.read().add( + CheckoutEvent.addServiceCharge(service > 0 ? 0 : service), + ); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0), + margin: const EdgeInsets.only(bottom: 8.0), + decoration: BoxDecoration( + color: AppColors.primary, + borderRadius: BorderRadius.circular(8.0), + border: Border.all( + color: AppColors.primary, + width: 1.0, + ), + ), + child: Row( + children: [ + Expanded( + child: Text( + "Biaya layanan ($service%)", + style: TextStyle( + fontSize: 16, + color: AppColors.white, + fontWeight: FontWeight.w500, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + SpaceWidth(12.0), + Icon( + Icons.check_circle, + color: AppColors.green, + ), + ], + ), + ), + ); + } } diff --git a/lib/presentation/home/dialog/tax_dialog.dart b/lib/presentation/home/dialog/tax_dialog.dart index dea83a1..1f55b8a 100644 --- a/lib/presentation/home/dialog/tax_dialog.dart +++ b/lib/presentation/home/dialog/tax_dialog.dart @@ -44,7 +44,20 @@ class TaxDialog extends StatelessWidget { return state.maybeWhen( initial: () => const SizedBox(), loading: () => const Center(child: CircularProgressIndicator()), - loaded: (data, a, b, c, tax, d, e, f, g, orderType) => ListTile( + loaded: ( + data, + a, + b, + c, + tax, + d, + e, + f, + g, + orderType, + deliveryType, + ) => + ListTile( title: const Text('PB1'), subtitle: Text('tarif pajak ($tax%)'), contentPadding: EdgeInsets.zero, diff --git a/lib/presentation/home/dialog/type_dialog.dart b/lib/presentation/home/dialog/type_dialog.dart new file mode 100644 index 0000000..65d6788 --- /dev/null +++ b/lib/presentation/home/dialog/type_dialog.dart @@ -0,0 +1,140 @@ +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart'; +import 'package:enaklo_pos/presentation/home/models/order_type.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class TypeDialog extends StatefulWidget { + const TypeDialog({super.key}); + + @override + State createState() => _TypeDialogState(); +} + +class _TypeDialogState extends State { + List> types = [ + { + 'value': 'dine_in', + 'label': 'Dine In', + 'icon': Icons.restaurant_outlined, + 'type': OrderType.dineIn, + }, + { + 'value': 'take_away', + 'label': 'Take Away', + 'icon': Icons.takeout_dining_outlined, + 'type': OrderType.takeAway, + }, + { + 'value': 'delivery', + 'label': 'Delivery', + 'icon': Icons.delivery_dining_outlined, + 'type': OrderType.delivery, + }, + { + 'value': 'free_table', + 'label': 'Free Table', + 'icon': Icons.table_bar_outlined, + 'type': OrderType.freeTable, + }, + ]; + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Pilih Tipe', + subtitle: 'Silahkan pilih tipe yang sesuai', + contentPadding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0), + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const SizedBox.shrink(), + loaded: ( + items, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { + return Column( + children: List.generate(types.length, (index) { + return _buildItem( + context, + types[index], + selectedType: orderType, + ); + }), + ); + }); + }, + ), + ); + } + + Widget _buildItem(BuildContext context, Map type, + {required OrderType selectedType}) { + return GestureDetector( + onTap: () { + context.read().add( + CheckoutEvent.updateOrderType(type['type']), + ); + Navigator.pop(context); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0), + margin: const EdgeInsets.only(bottom: 8.0), + decoration: BoxDecoration( + color: selectedType == type['type'] + ? AppColors.primary + : AppColors.white, + borderRadius: BorderRadius.circular(8.0), + border: Border.all( + color: selectedType == type['type'] + ? AppColors.primary + : AppColors.grey, + width: 1.0, + ), + ), + child: Row( + children: [ + Icon(type['icon'], + color: selectedType == type['type'] + ? AppColors.white + : AppColors.black), + SpaceWidth(12.0), + Expanded( + child: Text( + type['label']!, + style: TextStyle( + fontSize: 16, + color: selectedType == type['type'] + ? AppColors.white + : AppColors.black, + fontWeight: FontWeight.bold, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + SpaceWidth(12.0), + Icon( + Icons.check_circle, + color: selectedType == type['value'] + ? AppColors.green + : Colors.transparent, + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/home/dialog/variant_dialog.dart b/lib/presentation/home/dialog/variant_dialog.dart new file mode 100644 index 0000000..d62ee66 --- /dev/null +++ b/lib/presentation/home/dialog/variant_dialog.dart @@ -0,0 +1,65 @@ +import 'package:enaklo_pos/core/components/custom_modal_dialog.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/int_ext.dart'; +import 'package:enaklo_pos/data/models/response/product_response_model.dart'; +import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class VariantDialog extends StatelessWidget { + final Product product; + const VariantDialog({super.key, required this.product}); + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Pilih Varian', + subtitle: 'Silahkan pilih varian yang sesuai', + minWidth: context.deviceWidth * 0.4, + contentPadding: EdgeInsets.all(16), + child: Wrap( + spacing: 12, + runSpacing: 12, + children: product.variants!.map((variant) { + return GestureDetector( + onTap: () { + // Aksi saat varian dipilih + context.pop(); + context.read().add( + CheckoutEvent.addItem(product, variant), + ); + }, + child: Container( + width: (context.deviceWidth * 0.4 - 12 - 32) / 2 - 6, // 2 per row + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(12), + color: Colors.white, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + variant.name ?? "", + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + (variant.priceModifier ?? 0).currencyFormatRpV2, + style: TextStyle( + color: AppColors.black, + ), + ), + ], + ), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/lib/presentation/home/models/order_item.dart b/lib/presentation/home/models/order_item.dart index 4e81869..b8e9420 100644 --- a/lib/presentation/home/models/order_item.dart +++ b/lib/presentation/home/models/order_item.dart @@ -1,7 +1,5 @@ import 'package:enaklo_pos/data/models/response/product_response_model.dart'; -import 'product_model.dart'; - class OrderItem { final Product product; int quantity; diff --git a/lib/presentation/home/models/order_request.dart b/lib/presentation/home/models/order_request.dart new file mode 100644 index 0000000..7eabb86 --- /dev/null +++ b/lib/presentation/home/models/order_request.dart @@ -0,0 +1,102 @@ +import 'dart:convert'; + +class OrderRequestModel { + final String? outletId; + final String? customerId; + final String? tableNumber; + final String? tableId; + final String? orderType; + final String? notes; + final List? orderItems; + final String? customerName; + + OrderRequestModel({ + this.outletId, + this.customerId, + this.tableNumber, + this.tableId, + this.orderType, + this.notes, + this.orderItems, + this.customerName, + }); + + factory OrderRequestModel.fromJson(String str) => + OrderRequestModel.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); + + factory OrderRequestModel.fromMap(Map json) => + OrderRequestModel( + outletId: json["outlet_id"], + customerId: json["customer_id"], + tableNumber: json["table_number"], + tableId: json["table_id"], + orderType: json["order_type"], + notes: json["notes"], + orderItems: json["order_items"] == null + ? [] + : List.from( + json["order_items"].map((x) => OrderItemRequest.fromMap(x))), + customerName: json["customer_name"], + ); + + Map toMap() { + Map data = { + "outlet_id": outletId, + "table_number": tableNumber, + "order_type": orderType, + "notes": notes, + "order_items": orderItems == null + ? [] + : List.from(orderItems!.map((x) => x.toMap())), + "customer_name": customerName, + }; + + if (customerId != null && customerId != "") { + data["customer_id"] = customerId; + } + + if (tableId != null && tableId != "") { + data["table_id"] = tableId; + } + + return data; + } +} + +class OrderItemRequest { + final String? productId; + final String? productVariantId; + final int? quantity; + final int? unitPrice; + final String? notes; + + OrderItemRequest({ + this.productId, + this.productVariantId, + this.quantity, + this.unitPrice, + this.notes, + }); + + factory OrderItemRequest.fromJson(String str) => + OrderItemRequest.fromMap(json.decode(str)); + + factory OrderItemRequest.fromMap(Map json) => + OrderItemRequest( + productId: json["product_id"], + productVariantId: json["product_variant_id"], + quantity: json["quantity"], + unitPrice: json["unit_price"]?.toDouble(), + notes: json["notes"], + ); + + Map toMap() => { + "product_id": productId, + "product_variant_id": productVariantId, + "quantity": quantity, + "unit_price": unitPrice, + "notes": notes, + }; +} diff --git a/lib/presentation/home/models/order_type.dart b/lib/presentation/home/models/order_type.dart index d9b4019..4de6b5b 100644 --- a/lib/presentation/home/models/order_type.dart +++ b/lib/presentation/home/models/order_type.dart @@ -1,8 +1,8 @@ enum OrderType { dineIn('DINE IN'), takeAway('TAKE AWAY'), - grab('GRAB'), - delivery('DELIVERY'); + delivery('DELIVERY'), + freeTable('FREE TABLE'); final String value; const OrderType(this.value); @@ -13,4 +13,4 @@ enum OrderType { orElse: () => OrderType.dineIn, ); } -} \ No newline at end of file +} diff --git a/lib/presentation/home/models/outlet_model.dart b/lib/presentation/home/models/outlet_model.dart new file mode 100644 index 0000000..6293af5 --- /dev/null +++ b/lib/presentation/home/models/outlet_model.dart @@ -0,0 +1,162 @@ +import 'dart:convert'; + +import 'package:enaklo_pos/data/type/bussines_type.dart'; + +class OutletResponse { + final bool? success; + final OutletData? data; + final dynamic errors; + + OutletResponse({ + this.success, + this.data, + this.errors, + }); + + factory OutletResponse.fromJson(String str) => + OutletResponse.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); + + factory OutletResponse.fromMap(Map json) => OutletResponse( + success: json["success"], + data: json["data"] == null ? null : OutletData.fromMap(json["data"]), + errors: json["errors"], + ); + + Map toMap() => { + "success": success, + "data": data?.toMap(), + "errors": errors, + }; +} + +class OutletDetailResponse { + final bool? success; + final Outlet? data; + final dynamic errors; + + OutletDetailResponse({ + this.success, + this.data, + this.errors, + }); + + factory OutletDetailResponse.fromJson(String str) => + OutletDetailResponse.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); + + factory OutletDetailResponse.fromMap(Map json) => + OutletDetailResponse( + success: json["success"], + data: json["data"] == null ? null : Outlet.fromMap(json["data"]), + errors: json["errors"], + ); + + Map toMap() => { + "success": success, + "data": data?.toMap(), + "errors": errors, + }; +} + +class OutletData { + final List? outlets; + final int? totalCount; + final int? page; + final int? limit; + final int? totalPages; + + OutletData({ + this.outlets, + this.totalCount, + this.page, + this.limit, + this.totalPages, + }); + + factory OutletData.fromMap(Map json) => OutletData( + outlets: json["outlets"] == null + ? [] + : List.from(json["outlets"].map((x) => Outlet.fromMap(x))), + totalCount: json["total_count"], + page: json["page"], + limit: json["limit"], + totalPages: json["total_pages"], + ); + + Map toMap() => { + "outlets": outlets == null + ? [] + : List.from(outlets!.map((x) => x.toMap())), + "total_count": totalCount, + "page": page, + "limit": limit, + "total_pages": totalPages, + }; +} + +class Outlet { + final String? id; + final String? organizationId; + final String? name; + final String? address; + final String? phoneNumber; + final BusinessType? businessType; + final String? currency; + final int? taxRate; + final bool? isActive; + final DateTime? createdAt; + final DateTime? updatedAt; + + Outlet({ + this.id, + this.organizationId, + this.name, + this.address, + this.phoneNumber, + this.businessType, + this.currency, + this.taxRate, + this.isActive, + this.createdAt, + this.updatedAt, + }); + + factory Outlet.fromMap(Map json) => Outlet( + id: json["id"], + organizationId: json["organization_id"], + name: json["name"], + address: json["address"], + phoneNumber: json["phone_number"], + businessType: BusinessType.fromString(json["business_type"]), + currency: json["currency"], + taxRate: json["tax_rate"], + isActive: json["is_active"], + createdAt: json["created_at"] == null + ? null + : DateTime.parse(json["created_at"]), + updatedAt: json["updated_at"] == null + ? null + : DateTime.parse(json["updated_at"]), + ); + + Map toMap() => { + "id": id, + "organization_id": organizationId, + "name": name, + "address": address, + "phone_number": phoneNumber, + "business_type": businessType?.value, + "currency": currency, + "tax_rate": taxRate, + "is_active": isActive, + "created_at": createdAt?.toIso8601String(), + "updated_at": updatedAt?.toIso8601String(), + }; + + factory Outlet.fromJson(String str) => Outlet.fromMap(json.decode(str)); + + String toJson() => json.encode(toMap()); +} diff --git a/lib/presentation/home/models/product_quantity.dart b/lib/presentation/home/models/product_quantity.dart index 335ccdb..0d2951d 100644 --- a/lib/presentation/home/models/product_quantity.dart +++ b/lib/presentation/home/models/product_quantity.dart @@ -5,12 +5,14 @@ import 'package:enaklo_pos/data/models/response/product_response_model.dart'; class ProductQuantity { final Product product; + ProductVariant? variant; int quantity; String notes; ProductQuantity({ required this.product, required this.quantity, this.notes = '', + this.variant, }); @override @@ -20,17 +22,20 @@ class ProductQuantity { return other is ProductQuantity && other.product == product && other.quantity == quantity && + other.variant == variant && other.notes == notes; } @override - int get hashCode => product.hashCode ^ quantity.hashCode ^ notes.hashCode; + int get hashCode => + product.hashCode ^ quantity.hashCode ^ variant.hashCode ^ notes.hashCode; Map toMap() { return { 'product': product.toMap(), 'quantity': quantity, 'notes': notes, + 'variant': variant?.toMap(), }; } @@ -39,22 +44,24 @@ class ProductQuantity { return { 'id_order': orderId, - 'id_product': product.productId, + 'id_product': product.id, 'quantity': quantity, 'price': product.price, 'notes': notes, + 'variant': variant?.toMap(), }; } Map toServerMap(int? orderId) { - log("toServerMap: ${product.productId}"); + log("toServerMap: ${product.id}"); return { 'id_order': orderId ?? 0, - 'id_product': product.productId, + 'id_product': product.id, 'quantity': quantity, 'price': product.price, 'notes': notes, + 'variant': variant?.toMap(), }; } @@ -63,6 +70,9 @@ class ProductQuantity { product: Product.fromMap(map['product']), quantity: map['quantity']?.toInt() ?? 0, notes: map['notes'] ?? '', + variant: map['variant'] != null + ? ProductVariant.fromMap(map['variant']) + : null, ); } @@ -72,6 +82,9 @@ class ProductQuantity { product: Product.fromOrderMap(map), quantity: map['quantity']?.toInt() ?? 0, notes: map['notes'] ?? '', + variant: map['variant'] != null + ? ProductVariant.fromMap(map['variant']) + : null, ); } @@ -89,6 +102,7 @@ class ProductQuantity { product: product ?? this.product, quantity: quantity ?? this.quantity, notes: notes ?? this.notes, + variant: variant, ); } } diff --git a/lib/presentation/home/pages/confirm_payment_page-old.dart b/lib/presentation/home/pages/confirm_payment_page-old.dart new file mode 100644 index 0000000..89f2a0d --- /dev/null +++ b/lib/presentation/home/pages/confirm_payment_page-old.dart @@ -0,0 +1,1754 @@ +// 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: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/models/response/table_model.dart'; +import 'package:enaklo_pos/presentation/home/bloc/get_table_status/get_table_status_bloc.dart'; +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/models/product_quantity.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/data/models/response/payment_methods_response_model.dart'; +import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; + +import '../../../core/components/buttons.dart'; +import '../../../core/components/spaces.dart'; +import '../../../core/constants/colors.dart'; +import '../bloc/checkout/checkout_bloc.dart'; +import '../widgets/order_menu.dart'; +import '../widgets/success_payment_dialog.dart'; +import '../widgets/order_type_selector.dart'; + +class ConfirmPaymentPage extends StatefulWidget { + final bool isTable; + final TableModel? table; + + const ConfirmPaymentPage({ + super.key, + required this.isTable, + this.table, + }); + + @override + State createState() => _ConfirmPaymentPageState(); +} + +class _ConfirmPaymentPageState extends State { + final totalPriceController = TextEditingController(); + final customerController = TextEditingController(); + bool isPayNow = true; + bool isAddToOrder = false; + PaymentMethod? selectedPaymentMethod; + TableModel? selectTable; + int discountAmount = 0; + int priceValue = 0; + int uangPas = 0; + int uangPas2 = 0; + int uangPas3 = 0; + + // int discountAmountValue = 0; + int totalPriceFinal = 0; + + // int taxFinal = 0; + // int serviceChargeFinal = 0; + + @override + void initState() { + // Fetch available tables by default + context + .read() + .add(GetTableStatusEvent.getTablesStatus('available')); + context + .read() + .add(PaymentMethodsEvent.fetchPaymentMethods()); + + // Set a default payment method in case API fails + selectedPaymentMethod = PaymentMethod( + id: "4b1c0d21-c98a-4fc0-a2f9-8d90a0c9d905", + organizationId: "3e8b1793-d18b-40c4-a03d-0c6480b630c7", + name: "CASH", + type: "cash", + isActive: true, + createdAt: DateTime.tryParse('2025-07-18T03:43:13.857048+07:00'), + updatedAt: DateTime.tryParse('2025-07-18T03:43:13.857048+07:00'), + ); + // if (selectTable == null && widget.table != null) { + // selectTable = tables.firstWhere( + // (t) => t.id == widget.table!.id, + // orElse: () => null, + // ); + // } + if (widget.table != null) { + // selectTable = TableModel( + // tableNumber: widget.table!.tableNumber, + // startTime: widget.table!.startTime, + // status: widget.table!.status, + // orderId: widget.table!.orderId, + // paymentAmount: widget.table!.paymentAmount, + // position: widget.table!.position, + // ); + } + super.initState(); + } + + @override + void dispose() { + totalPriceController.dispose(); + customerController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Hero( + tag: 'payment_confirmation_screen', + child: Scaffold( + backgroundColor: AppColors.white, + body: Row( + children: [ + Expanded( + flex: 2, + child: Align( + alignment: Alignment.topCenter, + child: SingleChildScrollView( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Konfirmasi', + style: TextStyle( + color: AppColors.primary, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + Text( + widget.isTable + ? 'Orders Table ${widget.table?.tableName}' + : 'Orders #1', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ], + ), + const SpaceHeight(8.0), + const Divider(), + const SpaceHeight(24.0), + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Item', + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + SizedBox( + width: 160, + ), + SizedBox( + width: 50.0, + child: Text( + 'Qty', + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + SizedBox( + child: Text( + 'Price', + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + const SpaceHeight(8), + const Divider(), + const SpaceHeight(8), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const Center( + child: Text('No Items'), + ), + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { + if (products.isEmpty) { + return const Center( + child: Text('No Items'), + ); + } + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => + OrderMenu(data: products[index]), + separatorBuilder: (context, index) => + const SpaceHeight(12.0), + itemCount: products.length, + ); + }, + ); + }, + ), + const SpaceHeight(8.0), + const Divider(), + const SpaceHeight(4.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Sub total', + style: TextStyle(color: AppColors.grey), + ), + BlocBuilder( + builder: (context, state) { + final price = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + products.fold( + 0, + (previousValue, element) => + previousValue + + (element.product.price! * + element.quantity), + )); + return Text( + price.currencyFormatRp, + style: const TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.w600, + ), + ); + }, + ), + ], + ), + const SpaceHeight(4.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Diskon', + style: TextStyle(color: AppColors.grey), + ), + BlocBuilder( + builder: (context, state) { + final discount = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { + if (discountModel == null) { + return 0; + } + return discountModel.value! + .replaceAll('.00', '') + .toIntegerFromText; + }); + + final subTotal = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + products.fold( + 0, + (previousValue, element) => + previousValue + + (element.product.price! * + element.quantity), + )); + + final finalDiscount = discount / 100 * subTotal; + discountAmount = finalDiscount.toInt(); + return Text( + '$discount % (${finalDiscount.toInt().currencyFormatRp})', + style: TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.w600, + ), + ); + }, + ), + ], + ), + const SpaceHeight(4.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Pajak PB1', + style: TextStyle(color: AppColors.grey), + ), + BlocBuilder( + builder: (context, state) { + final tax = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + tax, + ); + final price = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + products.fold( + 0, + (previousValue, element) => + previousValue + + (element.product.price! * + element.quantity), + ), + ); + + final discount = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { + if (discountModel == null) { + return 0; + } + return discountModel.value! + .replaceAll('.00', '') + .toIntegerFromText; + }); + + final subTotal = + price - (discount / 100 * price); + final finalTax = subTotal * (tax / 100); + // final finalDiscount = discount / 100 * subTotal; + // discountAmountValue = finalDiscount.toInt(); + // taxFinal = finalTax.toInt(); + return Text( + '$tax % (${finalTax.toInt().currencyFormatRp})', + style: const TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.w600, + ), + ); + }, + ), + ], + ), + const SpaceHeight(4.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Biaya Layanan', + style: TextStyle(color: AppColors.grey), + ), + BlocBuilder( + builder: (context, state) { + final serviceCharge = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + serviceCharge, + ); + + final price = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + products.fold( + 0, + (previousValue, element) => + previousValue + + (element.product.price! * + element.quantity), + ), + ); + + final nominalServiceCharge = + (serviceCharge / 100) * + (price - discountAmount); + + // serviceChargeFinal = + // nominalServiceCharge.toInt(); + return Text( + '$serviceCharge % (${nominalServiceCharge.toInt().currencyFormatRp})', + style: const TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.w600, + ), + ); + }, + ), + ], + ), + const SpaceHeight(10.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Total', + style: TextStyle( + color: AppColors.grey, + fontWeight: FontWeight.bold, + fontSize: 16), + ), + BlocBuilder( + builder: (context, state) { + final price = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + products.fold( + 0, + (previousValue, element) => + previousValue + + (element.product.price! * + element.quantity), + ), + ); + + final discount = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { + if (discountModel == null) { + return 0; + } + return discountModel.value! + .replaceAll('.00', '') + .toIntegerFromText; + }); + + final tax = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + tax, + ); + + final serviceCharge = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + serviceCharge, + ); + + final subTotal = + price - (discount / 100 * price); + final finalTax = subTotal * (tax / 100); + final service = + (serviceCharge / 100) * subTotal; + final total = subTotal + finalTax + service; + priceValue = total.toInt(); + totalPriceController.text = + total.ceil().currencyFormatRpV2; + uangPas = total.ceil(); + uangPas2 = uangPas ~/ 50000 * 50000 + 50000; + uangPas3 = uangPas ~/ 50000 * 50000 + 100000; + totalPriceFinal = total.ceil(); + // log("totalPriceFinal: $totalPriceFinal"); + return Text( + total.ceil().currencyFormatRp, + style: const TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.w600, + fontSize: 16, + ), + ); + }, + ), + ], + ), + ], + ), + ), + ), + ), + Expanded( + flex: 3, + child: Align( + alignment: Alignment.topCenter, + child: ListView( + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.isTable != true) ...[ + const Text( + 'Pembayaran', + style: TextStyle( + color: AppColors.primary, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + const SpaceHeight(16.0), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + isPayNow && !isAddToOrder + ? Button.filled( + height: 52.0, + width: 200.0, + onPressed: () { + isPayNow = true; + isAddToOrder = false; + setState(() {}); + }, + label: 'Bayar Sekarang', + ) + : Button.outlined( + width: 200.0, + height: 52.0, + onPressed: () { + isPayNow = true; + isAddToOrder = false; + setState(() {}); + }, + label: 'Bayar Sekarang'), + SpaceWidth(8), + !isPayNow && !isAddToOrder + ? Button.filled( + width: 200.0, + height: 52.0, + onPressed: () async { + print( + "🔘 Bayar Nanti button pressed (filled)"); + print( + "🔘 Fetching available tables for Bayar Nanti"); + isPayNow = false; + isAddToOrder = false; + + // Debug: Check all tables first + final allTables = + await ProductLocalDatasource + .instance + .getAllTable(); + print( + "🔘 All tables in database: ${allTables.length}"); + allTables.forEach((table) { + print( + "🔘 Table: ${table.tableName} - Status: ${table.status} - ID: ${table.id}"); + }); + + // Fetch available tables for Bayar Nanti + context + .read() + .add( + GetTableStatusEvent + .getTablesStatus( + 'available'), + ); + setState(() {}); + }, + label: 'Bayar Nanti', + ) + : Button.outlined( + width: 200.0, + height: 52.0, + onPressed: () async { + print( + "🔘 Bayar Nanti button pressed (outlined)"); + print( + "🔘 Fetching available tables for Bayar Nanti"); + isPayNow = false; + isAddToOrder = false; + + // Debug: Check all tables first + final allTables = + await ProductLocalDatasource + .instance + .getAllTable(); + print( + "🔘 All tables in database: ${allTables.length}"); + allTables.forEach((table) { + print( + "🔘 Table: ${table.tableName} - Status: ${table.status} - ID: ${table.id}"); + }); + + // Fetch available tables for Bayar Nanti + context + .read() + .add( + GetTableStatusEvent + .getTablesStatus( + 'available'), + ); + setState(() {}); + }, + label: 'Bayar Nanti'), + SpaceWidth(8), + isAddToOrder + ? Button.filled( + width: 200.0, + height: 52.0, + onPressed: () { + isPayNow = false; + isAddToOrder = true; + setState(() {}); + }, + label: 'Tambah ke Pesanan', + ) + : Button.outlined( + width: 200.0, + height: 52.0, + onPressed: () { + print( + "🔘 Tambah button pressed (outlined)"); + print( + "🔘 Fetching occupied tables for Tambah ke Pesanan"); + isPayNow = false; + isAddToOrder = true; + // Fetch occupied tables for Tambah ke Pesanan + context + .read() + .add( + GetTableStatusEvent + .getTablesStatus( + 'occupied'), + ); + setState(() {}); + }, + label: 'Tambah ke Pesanan', + ), + ], + ), + ), + ], + const SpaceHeight(8.0), + if (!isPayNow && !isAddToOrder) ...[ + const Divider(), + const SpaceHeight(8.0), + const Text( + 'Pilih Meja', + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + const SpaceHeight(12.0), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => + const CircularProgressIndicator(), + success: (tables) { + print( + "🔘 Tables fetched: ${tables.length} tables"); + print( + "🔘 Table statuses: ${tables.map((t) => '${t.tableName}: ${t.status}').join(', ')}"); + print( + "🔘 Current mode: ${!isPayNow && !isAddToOrder ? 'Pay Later' : isAddToOrder ? 'Add to Order' : 'Pay Now'}"); + // No need to filter since we're fetching the correct tables directly + final availableTables = tables; + + if (selectTable == null && + availableTables.isNotEmpty) { + selectTable = availableTables.first; + } + + if (availableTables.isEmpty) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.orange[50], + borderRadius: + BorderRadius.circular(16), + border: Border.all( + color: Colors.orange, + width: 1, + ), + ), + child: const Text( + 'Tidak ada meja yang tersedia. Silakan pilih opsi lain.', + style: + TextStyle(color: Colors.orange), + ), + ); + } + + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Theme.of(context).primaryColor, + width: 2, + ), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + isExpanded: true, + value: selectTable, + onChanged: (TableModel? newValue) { + setState(() { + selectTable = newValue; + }); + }, + items: availableTables + .map< + DropdownMenuItem>( + (TableModel value) => + DropdownMenuItem< + TableModel>( + value: value, + child: Text( + value.tableName ?? ""), + ), + ) + .toList(), + ), + ), + ); + }, + ); + }), + ], + const SpaceHeight(8.0), + if (isAddToOrder) ...[ + const Divider(), + const SpaceHeight(8.0), + const Text( + 'Pilih Meja yang Sudah Ada Pesanan', + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + const SpaceHeight(12.0), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => + const CircularProgressIndicator(), + success: (tables) { + print( + "🔘 Add to Order - Tables fetched: ${tables.length} tables"); + print( + "🔘 Add to Order - Table statuses: ${tables.map((t) => '${t.tableName}: ${t.status}').join(', ')}"); + // No need to filter since we're fetching occupied tables directly + final occupiedTables = tables; + + if (selectTable == null && + occupiedTables.isNotEmpty) { + selectTable = occupiedTables.first; + } + + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Theme.of(context).primaryColor, + width: 2, + ), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + isExpanded: true, + value: selectTable, + onChanged: (TableModel? newValue) { + setState(() { + selectTable = newValue; + }); + }, + items: occupiedTables + .map< + DropdownMenuItem>( + (TableModel value) => + DropdownMenuItem< + TableModel>( + value: value, + child: Text( + '${value.tableName} (Occupied)'), + ), + ) + .toList(), + ), + ), + ); + }, + ); + }), + ], + if (!isAddToOrder) ...[ + const SpaceHeight(8.0), + const Divider(), + const SpaceHeight(8.0), + const Text( + 'Customer', + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + const SpaceHeight(12.0), + TextFormField( + controller: customerController, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + ), + hintText: 'Nama Customer', + ), + textCapitalization: TextCapitalization.words, + ), + ], + const SpaceHeight(8.0), + const Divider(), + const SpaceHeight(8.0), + const OrderTypeSelector(), + const SpaceHeight(8.0), + if (isPayNow) ...[ + const Divider(), + const SpaceHeight(8.0), + const Text( + 'Metode Bayar', + style: TextStyle( + color: AppColors.primary, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + const SpaceHeight(12.0), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const Center( + child: CircularProgressIndicator(), + ), + loading: () => const Center( + child: Column( + children: [ + CircularProgressIndicator(), + SizedBox(height: 8.0), + Text('Loading payment methods...'), + ], + ), + ), + error: (message) => Column( + children: [ + Center( + child: Text( + 'Error loading payment methods: $message'), + ), + const SpaceHeight(16.0), + Button.filled( + onPressed: () { + context + .read() + .add(PaymentMethodsEvent + .fetchPaymentMethods()); + }, + label: 'Retry', + ), + ], + ), + loaded: (paymentMethods) { + log("Loaded ${paymentMethods.length} payment methods"); + paymentMethods.forEach((method) { + log("Payment method: ${method.name} (ID: ${method.id})"); + }); + if (paymentMethods.isEmpty) { + return Column( + children: [ + const Center( + child: Text( + 'No payment methods available'), + ), + const SpaceHeight(16.0), + Button.filled( + onPressed: () { + context + .read() + .add(PaymentMethodsEvent + .fetchPaymentMethods()); + }, + label: 'Retry', + ), + ], + ); + } + + // Set default selected payment method if none selected or if current selection is not in the list + if (selectedPaymentMethod == null || + !paymentMethods.any((method) => + method.id == + selectedPaymentMethod?.id)) { + selectedPaymentMethod = + paymentMethods.first; + } + + return Wrap( + spacing: 12.0, + runSpacing: 8.0, + children: paymentMethods.map((method) { + final isSelected = + selectedPaymentMethod?.id == + method.id; + return Container( + constraints: const BoxConstraints( + minWidth: 120.0, + ), + decoration: isSelected + ? BoxDecoration( + border: Border.all( + color: AppColors.primary, + width: 2.0, + ), + borderRadius: + BorderRadius.circular( + 8.0), + ) + : null, + child: isSelected + ? Button.filled( + width: double.infinity, + height: 50.0, + onPressed: () { + setState(() { + selectedPaymentMethod = + method; + }); + }, + label: method.name + ?.isNotEmpty == + true + ? method.name! + : 'Unknown', + ) + : Button.outlined( + width: double.infinity, + height: 50.0, + onPressed: () { + setState(() { + selectedPaymentMethod = + method; + }); + }, + label: method.name + ?.isNotEmpty == + true + ? method.name! + : 'Unknown', + ), + ); + }).toList(), + ); + }, + ); + }, + ), + const SpaceHeight(8.0), + const Divider(), + const SpaceHeight(8.0), + const Text( + 'Total Bayar', + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + const SpaceHeight(12.0), + BlocBuilder( + builder: (context, state) { + return TextFormField( + controller: totalPriceController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: + BorderRadius.circular(8.0), + ), + hintText: 'Total harga', + ), + onChanged: (value) { + priceValue = value.toIntegerFromText; + final int newValue = + value.toIntegerFromText; + totalPriceController.text = + newValue.currencyFormatRp; + totalPriceController.selection = + TextSelection.fromPosition( + TextPosition( + offset: totalPriceController + .text.length)); + }, + ); + }, + ), + const SpaceHeight(20.0), + BlocBuilder( + builder: (context, state) { + return Row( + children: [ + Button.filled( + width: 150.0, + onPressed: () { + totalPriceController.text = uangPas + .toString() + .currencyFormatRpV2; + priceValue = uangPas; + }, + label: 'UANG PAS', + ), + const SpaceWidth(20.0), + Button.filled( + width: 150.0, + onPressed: () { + totalPriceController.text = uangPas2 + .toString() + .currencyFormatRpV2; + priceValue = uangPas2; + }, + label: uangPas2 + .toString() + .currencyFormatRpV2, + ), + const SpaceWidth(20.0), + Button.filled( + width: 150.0, + onPressed: () { + totalPriceController.text = uangPas3 + .toString() + .currencyFormatRpV2; + priceValue = uangPas3; + }, + label: uangPas3 + .toString() + .currencyFormatRpV2, + ), + ], + ); + }, + ), + ] + ], + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: ColoredBox( + color: AppColors.white, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24.0, vertical: 16.0), + child: Row( + children: [ + Flexible( + child: Button.outlined( + onPressed: () => context.pop(), + label: 'Kembali', + ), + ), + const SpaceWidth(8.0), + BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + savedDraftOrder: (orderDraftId) { + log("PRICEVALUE: ${priceValue}"); + + // final newTabel = TableModel( + // id: widget.isTable + // ? widget.table!.id + // : selectTable?.id, + // tableName: widget.isTable + // ? widget.table!.tableName + // : selectTable?.tableName ?? + // '0', + // status: 'occupied', + // paymentAmount: priceValue, + // orderId: orderDraftId, + // startTime: DateTime.now() + // .toIso8601String(), + // position: widget.isTable + // ? widget.table!.position + // : selectTable!.position); + // log('new tabel: ${newTabel.toMap()}'); + // context + // .read() + // .add(StatusTableEvent.statusTabel( + // newTabel, + // )); + }); + }, + child: + BlocBuilder( + builder: (context, state) { + final discount = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { + if (discountModel == null) { + return 0; + } + return discountModel.value! + .replaceAll('.00', '') + .toIntegerFromText; + }); + + final price = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + products.fold( + 0, + (previousValue, element) => + previousValue + + (element.product.price! * + element.quantity), + ), + ); + + final serviceCharge = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderTyp, + deliveryType, + ) => + serviceCharge, + ); + + final tax = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + tax, + ); + + final orderType = state.maybeWhen( + orElse: () => OrderType.dineIn, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + orderType, + ); + + final subTotal = + price - (discount / 100 * price); + final totalDiscount = + discount / 100 * price; + final finalTax = subTotal * (tax / 100); + final totalServiceCharge = + (serviceCharge / 100) * price; + + List items = + state.maybeWhen( + orElse: () => [], + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + products, + ); + final totalQty = items.fold( + 0, + (previousValue, element) => + previousValue + element.quantity, + ); + + return Flexible( + child: Button.filled( + onPressed: () async { + print("🔘 Payment button pressed"); + print("🔘 isPayNow: $isPayNow"); + print( + "🔘 isAddToOrder: $isAddToOrder"); + print( + "🔘 selectedPaymentMethod: ${selectedPaymentMethod?.name}"); + if (selectedPaymentMethod == null) { + ScaffoldMessenger.of(context) + .showSnackBar( + const SnackBar( + content: Text( + 'Please select a payment method'), + backgroundColor: Colors.red, + ), + ); + return; + } + + if (widget.isTable) { + log("discountAmountValue: $totalDiscount"); + // context.read().add( + // CheckoutEvent + // .saveDraftOrder( + // widget.isTable == true + // ? widget.table!.id! + // : selectTable!.id!, + // customerController.text, + // totalDiscount.toInt(), + // ), + // ); + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => + SaveOrderDialog( + data: items, + totalQty: totalQty, + totalPrice: totalPriceFinal, + totalTax: finalTax.toInt(), + totalDiscount: + totalDiscount.toInt(), + subTotal: subTotal.toInt(), + normalPrice: price, + table: widget.table!, + draftName: + customerController.text, + ), + ); + } else if (isPayNow) { + log("🔘 Entering Pay Now flow"); + // context.read().add( + // OrderEvent.addPaymentMethod( + // items, + // totalPrice, + // finalTax, + // discount != null + // ? discount.value + // .replaceAll( + // '.00', '') + // .toIntegerFromText + // : 0, + // finalDiscountAmount, + // finalService, + // subTotal, + // totalPriceController.text + // .toIntegerFromText, + // auth?.user.name ?? '-', + // totalQuantity, + // auth?.user.id ?? 1, + // isCash + // ? 'Cash' + // : 'QR Pay')); + final paymentMethodName = + selectedPaymentMethod?.name + ?.toLowerCase() ?? + ''; + log("Selected payment method: ${selectedPaymentMethod?.name} (normalized: $paymentMethodName)"); + if (paymentMethodName == 'cash' || + paymentMethodName == + 'tunai' || + paymentMethodName == + 'uang tunai' || + paymentMethodName == + 'cash payment') { + log("🔘 Payment method is cash, proceeding with OrderBloc call"); + log("discountAmountValue: $totalDiscount"); + log("💳 About to call OrderBloc for cash payment"); + log("💳 OrderBloc instance: ${context.read()}"); + log("💳 About to dispatch OrderEvent.order for cash payment"); + context.read().add( + OrderEvent.order( + items, + discount, + totalDiscount.toInt(), + finalTax.toInt(), + 0, + totalPriceController + .text + .toIntegerFromText, + customerController.text, + 0, + 'completed', + 'paid', + selectedPaymentMethod + ?.name ?? + 'Cash', + totalPriceFinal, + orderType)); + log("💳 OrderEvent.order dispatched for cash payment"); + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => + SuccessPaymentDialog( + data: items, + totalQty: totalQty, + totalPrice: totalPriceFinal, + totalTax: finalTax.toInt(), + totalDiscount: + totalDiscount.toInt(), + subTotal: subTotal.toInt(), + normalPrice: price, + totalService: + totalServiceCharge + .toInt(), + draftName: + customerController.text, + ), + ); + } else { + // Process non-cash payment directly without QRIS dialog + log("Processing non-cash payment: ${selectedPaymentMethod?.name}"); + log("💳 About to call OrderBloc for non-cash payment"); + log("💳 OrderBloc instance: ${context.read()}"); + context.read().add( + OrderEvent.order( + items, + discount, + totalDiscount.toInt(), + finalTax.toInt(), + 0, + totalPriceController + .text + .toIntegerFromText, + customerController.text, + 0, + 'completed', + 'paid', + selectedPaymentMethod + ?.name ?? + 'Unknown Payment Method', + totalPriceFinal, + orderType)); + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => + SuccessPaymentDialog( + data: items, + totalQty: totalQty, + totalPrice: totalPriceFinal, + totalTax: finalTax.toInt(), + totalDiscount: + totalDiscount.toInt(), + subTotal: subTotal.toInt(), + normalPrice: price, + totalService: + totalServiceCharge + .toInt(), + draftName: + customerController.text, + ), + ); + } + } else if (isAddToOrder) { + // Tambahkan ke pesanan meja yang sudah ada + if (selectTable != null) { + // Ambil draft order yang sudah ada + // final existingDraftOrder = + // await ProductLocalDatasource + // .instance + // .getDraftOrderById( + // selectTable + // ?.orderId ?? + // 0); + + // if (existingDraftOrder != + // null) { + // // Convert items ke DraftOrderItem + // final newDraftItems = items + // .map((item) => + // DraftOrderItem( + // product: + // item.product, + // quantity: + // item.quantity, + // )) + // .toList(); + + // // Gabungkan dengan pesanan yang sudah ada + // final updatedItems = [ + // ...existingDraftOrder + // .orders, + // ...newDraftItems + // ]; + + // final updatedDraftOrder = + // existingDraftOrder + // .copyWith( + // orders: updatedItems, + // totalQuantity: + // updatedItems.fold( + // 0, + // (sum, item) => + // sum + + // item.quantity), + // subTotal: updatedItems.fold< + // int>( + // 0, + // (sum, item) => + // sum + + // (item.product + // .price ?? + // 0) * + // item.quantity), + // ); + + // // Update draft order + // await ProductLocalDatasource + // .instance + // .updateDraftOrder( + // updatedDraftOrder); + + // // Tampilkan dialog sukses + // await showDialog( + // context: context, + // barrierDismissible: false, + // builder: (context) => + // SaveOrderDialog( + // data: items, + // totalQty: totalQty, + // totalPrice: + // totalPriceFinal, + // totalTax: + // finalTax.toInt(), + // totalDiscount: + // totalDiscount.toInt(), + // subTotal: + // subTotal.toInt(), + // normalPrice: price, + // table: selectTable!, + // draftName: + // customerController + // .text, + // ), + // ); + // } else { + // // Jika tidak ada draft order, buat baru + // context + // .read() + // .add( + // CheckoutEvent + // .saveDraftOrder( + // selectTable!.id!, + // customerController + // .text, + // totalDiscount.toInt(), + // ), + // ); + // await showDialog( + // context: context, + // barrierDismissible: false, + // builder: (context) => + // SaveOrderDialog( + // data: items, + // totalQty: totalQty, + // totalPrice: + // totalPriceFinal, + // totalTax: + // finalTax.toInt(), + // totalDiscount: + // totalDiscount.toInt(), + // subTotal: + // subTotal.toInt(), + // normalPrice: price, + // table: selectTable!, + // draftName: + // customerController + // .text, + // ), + // ); + // } + } else { + ScaffoldMessenger.of(context) + .showSnackBar( + const SnackBar( + content: Text( + 'Pilih meja terlebih dahulu'), + backgroundColor: Colors.red, + ), + ); + } + } else { + // context.read().add( + // CheckoutEvent + // .saveDraftOrder( + // widget.isTable == true + // ? widget.table!.id! + // : selectTable!.id!, + // customerController.text, + // totalDiscount.toInt(), + // ), + // ); + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => + SaveOrderDialog( + data: items, + totalQty: totalQty, + totalPrice: totalPriceFinal, + totalTax: finalTax.toInt(), + totalDiscount: + totalDiscount.toInt(), + subTotal: subTotal.toInt(), + normalPrice: price, + table: selectTable!, + draftName: + customerController.text, + ), + ); + } + }, + label: isPayNow + ? 'Bayar' + : isAddToOrder + ? 'Simpan' + : 'Simpan Order', + ), + ); + }, + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/home/pages/confirm_payment_page.dart b/lib/presentation/home/pages/confirm_payment_page.dart index 5f8863c..b03e1f8 100644 --- a/lib/presentation/home/pages/confirm_payment_page.dart +++ b/lib/presentation/home/pages/confirm_payment_page.dart @@ -1,41 +1,42 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:developer'; +import 'package:enaklo_pos/core/components/dashed_divider.dart'; +import 'package:enaklo_pos/core/components/flushbar.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import 'package:enaklo_pos/data/models/response/delivery_response_model.dart'; +import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart'; +import 'package:enaklo_pos/presentation/home/dialog/save_dialog.dart'; +import 'package:enaklo_pos/presentation/home/models/order_type.dart'; +import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; +import 'package:enaklo_pos/presentation/home/widgets/confirm_payment_title.dart'; +import 'package:enaklo_pos/presentation/home/widgets/customer_auto_complete_field.dart'; +import 'package:enaklo_pos/presentation/success/pages/success_order_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.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/models/response/table_model.dart'; -import 'package:enaklo_pos/presentation/home/bloc/get_table_status/get_table_status_bloc.dart'; -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/status_table/status_table_bloc.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/widgets/save_order_dialog.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/presentation/table/models/draft_order_item.dart'; import '../../../core/components/buttons.dart'; import '../../../core/components/spaces.dart'; import '../../../core/constants/colors.dart'; import '../bloc/checkout/checkout_bloc.dart'; import '../widgets/order_menu.dart'; -import '../widgets/success_payment_dialog.dart'; -import '../widgets/order_type_selector.dart'; class ConfirmPaymentPage extends StatefulWidget { final bool isTable; final TableModel? table; const ConfirmPaymentPage({ - Key? key, + super.key, required this.isTable, this.table, - }) : super(key: key); + }); @override State createState() => _ConfirmPaymentPageState(); @@ -53,6 +54,7 @@ class _ConfirmPaymentPageState extends State { int uangPas = 0; int uangPas2 = 0; int uangPas3 = 0; + Customer? selectedCustomer; // int discountAmountValue = 0; int totalPriceFinal = 0; @@ -63,20 +65,20 @@ class _ConfirmPaymentPageState extends State { @override void initState() { // Fetch available tables by default - context - .read() - .add(GetTableStatusEvent.getTablesStatus('available')); + context .read() .add(PaymentMethodsEvent.fetchPaymentMethods()); // Set a default payment method in case API fails selectedPaymentMethod = PaymentMethod( - id: 1, - name: 'Cash', - description: 'Cash payment', + id: "4b1c0d21-c98a-4fc0-a2f9-8d90a0c9d905", + organizationId: "3e8b1793-d18b-40c4-a03d-0c6480b630c7", + name: "CASH", + type: "cash", isActive: true, - sortOrder: 1, + createdAt: DateTime.tryParse('2025-07-18T03:43:13.857048+07:00'), + updatedAt: DateTime.tryParse('2025-07-18T03:43:13.857048+07:00'), ); // if (selectTable == null && widget.table != null) { // selectTable = tables.firstWhere( @@ -110,48 +112,75 @@ class _ConfirmPaymentPageState extends State { child: Hero( tag: 'payment_confirmation_screen', child: Scaffold( + backgroundColor: AppColors.white, body: Row( children: [ Expanded( - flex: 2, + flex: 3, child: Align( alignment: Alignment.topCenter, - child: SingleChildScrollView( - padding: const EdgeInsets.all(24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Konfirmasi', - style: TextStyle( - color: AppColors.primary, - fontSize: 20, - fontWeight: FontWeight.w600, - ), - ), - Text( - widget.isTable - ? 'Orders Table ${widget.table?.tableName}' - : 'Orders #1', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - ], + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ConfirmPaymentTitle( + isBack: false, + title: 'Konfirmasi', + subtitle: widget.isTable + ? 'Orders Table ${widget.table?.tableName}' + : 'Orders #1', + actionWidget: [ + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const SizedBox(), + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.0), + border: Border.all( + color: AppColors.primary, + width: 1.0, + ), + ), + padding: const EdgeInsets.all(8.0), + child: Text( + orderType.value, + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ); + }, + ); + }, + ), + ], + ), + Container( + padding: const EdgeInsets.all(16.0).copyWith(bottom: 8), + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide( + color: AppColors.grey, + width: 1.0, ), - ], + ), ), - const SpaceHeight(8.0), - const Divider(), - const SpaceHeight(24.0), - const Row( + child: const Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( @@ -188,25 +217,27 @@ class _ConfirmPaymentPageState extends State { ), ], ), - const SpaceHeight(8), - const Divider(), - const SpaceHeight(8), - BlocBuilder( + ), + Expanded( + child: BlocBuilder( builder: (context, state) { return state.maybeWhen( orElse: () => const Center( child: Text('No Items'), ), - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) { + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { if (products.isEmpty) { return const Center( child: Text('No Items'), @@ -214,7 +245,8 @@ class _ConfirmPaymentPageState extends State { } return ListView.separated( shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(16.0) + .copyWith(top: 8.0), itemBuilder: (context, index) => OrderMenu(data: products[index]), separatorBuilder: (context, index) => @@ -225,126 +257,88 @@ class _ConfirmPaymentPageState extends State { ); }, ), - const SpaceHeight(8.0), - const Divider(), - const SpaceHeight(4.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Sub total', - style: TextStyle(color: AppColors.grey), + ), + Container( + padding: const EdgeInsets.all(16.0), + decoration: const BoxDecoration( + border: Border( + top: BorderSide( + color: AppColors.grey, + width: 1.0, ), + ), + ), + child: Column( + children: [ BlocBuilder( builder: (context, state) { - final price = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - products.fold( - 0, - (previousValue, element) => - previousValue + - (element.product.price! - .toIntegerFromText * - element.quantity), - )); - return Text( - price.currencyFormatRp, - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.w600, - ), - ); + return state.maybeWhen( + orElse: () => const SizedBox.shrink(), + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { + if (deliveryType == null) { + return const SizedBox.shrink(); + } + + return Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Pengiriman', + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.w600, + ), + ), + Text( + deliveryType.name, + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + const SpaceHeight(8.0), + DashedDivider( + color: AppColors.grey, + ), + const SpaceHeight(8.0), + ], + ); + }); }, ), - ], - ), - const SpaceHeight(4.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Diskon', - style: TextStyle(color: AppColors.grey), - ), - BlocBuilder( - builder: (context, state) { - final discount = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) { - if (discountModel == null) { - return 0; - } - return discountModel.value! - .replaceAll('.00', '') - .toIntegerFromText; - }); - - final subTotal = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - products.fold( - 0, - (previousValue, element) => - previousValue + - (element.product.price! - .toIntegerFromText * - element.quantity), - )); - - final finalDiscount = discount / 100 * subTotal; - discountAmount = finalDiscount.toInt(); - return Text( - '$discount % (${finalDiscount.toInt().currencyFormatRp})', + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Sub total', style: TextStyle( - color: AppColors.primary, + color: AppColors.black, fontWeight: FontWeight.w600, ), - ); - }, - ), - ], - ), - const SpaceHeight(4.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Pajak PB1', - style: TextStyle(color: AppColors.grey), - ), - BlocBuilder( - builder: (context, state) { - final tax = state.maybeWhen( - orElse: () => 0, - loaded: (products, + ), + BlocBuilder( + builder: (context, state) { + final price = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, discountModel, discount, discountAmount, @@ -353,34 +347,47 @@ class _ConfirmPaymentPageState extends State { totalQuantity, totalPrice, draftName, - orderType) => - tax, - ); - final price = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - products.fold( - 0, - (previousValue, element) => - previousValue + - (element.product.price! - .toIntegerFromText * - element.quantity), + orderType, + deliveryType, + ) => + products.fold( + 0, + (previousValue, element) => + previousValue + + (element.product.price! * + element.quantity) + + (element.variant + ?.priceModifier ?? + 0), + )); + return Text( + price.currencyFormatRp, + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.w600, + ), + ); + }, + ), + ], + ), + const SpaceHeight(8.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Pajak PB1', + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.w400, ), - ); - - final discount = state.maybeWhen( - orElse: () => 0, - loaded: (products, + ), + BlocBuilder( + builder: (context, state) { + final tax = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, discountModel, discount, discountAmount, @@ -389,134 +396,15 @@ class _ConfirmPaymentPageState extends State { totalQuantity, totalPrice, draftName, - orderType) { - if (discountModel == null) { - return 0; - } - return discountModel.value! - .replaceAll('.00', '') - .toIntegerFromText; - }); - - final subTotal = - price - (discount / 100 * price); - final finalTax = subTotal * (tax / 100); - final finalDiscount = discount / 100 * subTotal; - // discountAmountValue = finalDiscount.toInt(); - // taxFinal = finalTax.toInt(); - return Text( - '$tax % (${finalTax.toInt().currencyFormatRp})', - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.w600, - ), - ); - }, - ), - ], - ), - const SpaceHeight(4.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Biaya Layanan', - style: TextStyle(color: AppColors.grey), - ), - BlocBuilder( - builder: (context, state) { - final serviceCharge = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, + orderType, + deliveryType, + ) => tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - serviceCharge, - ); - - final price = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - products.fold( - 0, - (previousValue, element) => - previousValue + - (element.product.price! - .toIntegerFromText * - element.quantity), - ), - ); - - final nominalServiceCharge = - (serviceCharge / 100) * - (price - discountAmount); - - // serviceChargeFinal = - // nominalServiceCharge.toInt(); - return Text( - '$serviceCharge % (${nominalServiceCharge.toInt().currencyFormatRp})', - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.w600, - ), - ); - }, - ), - ], - ), - const SpaceHeight(10.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Total', - style: TextStyle( - color: AppColors.grey, - fontWeight: FontWeight.bold, - fontSize: 16), - ), - BlocBuilder( - builder: (context, state) { - final price = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - products.fold( - 0, - (previousValue, element) => - previousValue + - (element.product.price! - .toIntegerFromText * - element.quantity), - ), - ); - - final discount = state.maybeWhen( - orElse: () => 0, - loaded: (products, + ); + final price = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, discountModel, discount, discountAmount, @@ -525,18 +413,22 @@ class _ConfirmPaymentPageState extends State { totalQuantity, totalPrice, draftName, - orderType) { - if (discountModel == null) { - return 0; - } - return discountModel.value! - .replaceAll('.00', '') - .toIntegerFromText; - }); + orderType, + deliveryType, + ) => + products.fold( + 0, + (previousValue, element) => + previousValue + + (element.product.price! * + element.quantity), + ), + ); - final tax = state.maybeWhen( - orElse: () => 0, - loaded: (products, + final discount = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, discountModel, discount, discountAmount, @@ -545,1135 +437,659 @@ class _ConfirmPaymentPageState extends State { totalQuantity, totalPrice, draftName, - orderType) => - tax, - ); + orderType, + deliveryType, + ) { + if (discountModel == null) { + return 0; + } + return discountModel.value! + .replaceAll('.00', '') + .toIntegerFromText; + }); - final serviceCharge = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - serviceCharge, - ); - - final subTotal = - price - (discount / 100 * price); - final finalTax = subTotal * (tax / 100); - final service = - (serviceCharge / 100) * subTotal; - final total = subTotal + finalTax + service; - priceValue = total.toInt(); - totalPriceController.text = - total.ceil().currencyFormatRpV2; - uangPas = total.ceil(); - uangPas2 = uangPas ~/ 50000 * 50000 + 50000; - uangPas3 = uangPas ~/ 50000 * 50000 + 100000; - totalPriceFinal = total.ceil(); - // log("totalPriceFinal: $totalPriceFinal"); - return Text( - total.ceil().currencyFormatRp, - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.w600, - fontSize: 16, + final subTotal = + price - (discount / 100 * price); + final finalTax = subTotal * (tax / 100); + // final finalDiscount = discount / 100 * subTotal; + // discountAmountValue = finalDiscount.toInt(); + // taxFinal = finalTax.toInt(); + return Text( + '$tax % (${finalTax.toInt().currencyFormatRp})', + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.w500, + ), + ); + }, + ), + ], + ), + const SpaceHeight(8.0), + DashedDivider( + color: AppColors.grey, + ), + const SpaceHeight(8.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Total', + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.bold, + fontSize: 18, ), - ); - }, + ), + BlocBuilder( + builder: (context, state) { + final price = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + products.fold( + 0, + (previousValue, element) => + previousValue + + (element.product.price! * + element.quantity) + + (element.variant?.priceModifier ?? + 0), + ), + ); + + final discount = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { + if (discountModel == null) { + return 0; + } + return discountModel.value! + .replaceAll('.00', '') + .toIntegerFromText; + }); + + final tax = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + tax, + ); + + final serviceCharge = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + serviceCharge, + ); + + final subTotal = + price - (discount / 100 * price); + final finalTax = subTotal * (tax / 100); + final service = + (serviceCharge / 100) * subTotal; + final total = subTotal + finalTax + service; + priceValue = total.toInt(); + + WidgetsBinding.instance + .addPostFrameCallback((_) { + totalPriceController.text = + total.ceil().currencyFormatRpV2; + }); + uangPas = total.ceil(); + uangPas2 = uangPas ~/ 50000 * 50000 + 50000; + uangPas3 = + uangPas ~/ 50000 * 50000 + 100000; + totalPriceFinal = total.ceil(); + // log("totalPriceFinal: $totalPriceFinal"); + return Text( + total.ceil().currencyFormatRp, + style: const TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ); + }, + ), + ], ), ], ), - ], - ), + ), + ], ), ), ), + SpaceWidth(2), Expanded( flex: 3, child: Align( alignment: Alignment.topCenter, - child: ListView( + child: Column( children: [ - SingleChildScrollView( - padding: const EdgeInsets.all(24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (widget.isTable != true) ...[ - const Text( - 'Pembayaran', - style: TextStyle( - color: AppColors.primary, - fontSize: 20, - fontWeight: FontWeight.w600, + ConfirmPaymentTitle( + title: 'Pembayaran', + isBack: false, + subtitle: 'Silahkan lakukan pembayaran', + ), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.grey, + width: 1.0, + ), + ), + ), + child: CustomerAutocomplete( + controller: customerController, + selectedCustomer: selectedCustomer, + onSelected: (customer) { + setState(() { + selectedCustomer = customer; + }); + }, ), ), - const SpaceHeight(16.0), - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, + Container( + padding: const EdgeInsets.all(16), + width: double.infinity, + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.grey, + width: 1.0, + ), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - isPayNow && !isAddToOrder - ? Button.filled( - height: 52.0, - width: 200.0, - onPressed: () { - isPayNow = true; - isAddToOrder = false; - setState(() {}); - }, - label: 'Bayar Sekarang', - ) - : Button.outlined( - width: 200.0, - height: 52.0, - onPressed: () { - isPayNow = true; - isAddToOrder = false; - setState(() {}); - }, - label: 'Bayar Sekarang'), - SpaceWidth(8), - !isPayNow && !isAddToOrder - ? Button.filled( - width: 200.0, - height: 52.0, - onPressed: () async { - print( - "🔘 Bayar Nanti button pressed (filled)"); - print( - "🔘 Fetching available tables for Bayar Nanti"); - isPayNow = false; - isAddToOrder = false; - - // Debug: Check all tables first - final allTables = await ProductLocalDatasource.instance.getAllTable(); - print("🔘 All tables in database: ${allTables.length}"); - allTables.forEach((table) { - print("🔘 Table: ${table.tableName} - Status: ${table.status} - ID: ${table.id}"); - }); - - // Fetch available tables for Bayar Nanti - context - .read() - .add( - GetTableStatusEvent - .getTablesStatus( - 'available'), - ); - setState(() {}); - }, - label: 'Bayar Nanti', - ) - : Button.outlined( - width: 200.0, - height: 52.0, - onPressed: () async { - print( - "🔘 Bayar Nanti button pressed (outlined)"); - print( - "🔘 Fetching available tables for Bayar Nanti"); - isPayNow = false; - isAddToOrder = false; - - // Debug: Check all tables first - final allTables = await ProductLocalDatasource.instance.getAllTable(); - print("🔘 All tables in database: ${allTables.length}"); - allTables.forEach((table) { - print("🔘 Table: ${table.tableName} - Status: ${table.status} - ID: ${table.id}"); - }); - - // Fetch available tables for Bayar Nanti - context - .read() - .add( - GetTableStatusEvent - .getTablesStatus( - 'available'), - ); - setState(() {}); - }, - label: 'Bayar Nanti'), - SpaceWidth(8), - isAddToOrder - ? Button.filled( - width: 200.0, - height: 52.0, - onPressed: () { - isPayNow = false; - isAddToOrder = true; - setState(() {}); - }, - label: 'Tambah ke Pesanan', - ) - : Button.outlined( - width: 200.0, - height: 52.0, - onPressed: () { - print( - "🔘 Tambah button pressed (outlined)"); - print( - "🔘 Fetching occupied tables for Tambah ke Pesanan"); - isPayNow = false; - isAddToOrder = true; - // Fetch occupied tables for Tambah ke Pesanan - context - .read() - .add( - GetTableStatusEvent - .getTablesStatus( - 'occupied'), - ); - setState(() {}); - }, - label: 'Tambah ke Pesanan', + const Text( + 'Metode Pembayaran', + style: TextStyle( + color: AppColors.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SpaceHeight(12.0), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const Center( + child: CircularProgressIndicator(), ), + loading: () => const Center( + child: Column( + children: [ + CircularProgressIndicator(), + SizedBox(height: 8.0), + Text( + 'Loading payment methods...'), + ], + ), + ), + error: (message) => Column( + children: [ + Center( + child: Text( + 'Error loading payment methods: $message'), + ), + const SpaceHeight(16.0), + Button.filled( + onPressed: () { + context + .read< + PaymentMethodsBloc>() + .add(PaymentMethodsEvent + .fetchPaymentMethods()); + }, + label: 'Retry', + ), + ], + ), + loaded: (paymentMethods) { + log("Loaded ${paymentMethods.length} payment methods"); + paymentMethods.forEach((method) { + log("Payment method: ${method.name} (ID: ${method.id})"); + }); + if (paymentMethods.isEmpty) { + return Column( + children: [ + const Center( + child: Text( + 'No payment methods available'), + ), + const SpaceHeight(16.0), + Button.filled( + onPressed: () { + context + .read< + PaymentMethodsBloc>() + .add(PaymentMethodsEvent + .fetchPaymentMethods()); + }, + label: 'Retry', + ), + ], + ); + } + + // Set default selected payment method if none selected or if current selection is not in the list + if (selectedPaymentMethod == null || + !paymentMethods.any((method) => + method.id == + selectedPaymentMethod + ?.id)) { + selectedPaymentMethod = + paymentMethods.first; + } + + return Wrap( + spacing: 12.0, + runSpacing: 8.0, + children: + paymentMethods.map((method) { + final isSelected = + selectedPaymentMethod?.id == + method.id; + return GestureDetector( + onTap: () { + setState(() { + selectedPaymentMethod = + method; + }); + }, + child: Container( + height: 60, + width: 80, + alignment: Alignment.center, + padding: + const EdgeInsets.all( + 8.0), + decoration: BoxDecoration( + color: isSelected + ? AppColors.primary + : AppColors.white, + border: Border.all( + color: + AppColors.primary, + width: 1.0, + ), + borderRadius: + BorderRadius.circular( + 8.0), + ), + child: Text( + method.name ?? "", + style: TextStyle( + color: isSelected + ? AppColors.white + : AppColors.primary, + fontWeight: + FontWeight.bold, + ), + textAlign: + TextAlign.center, + ), + ), + ); + }).toList(), + ); + }, + ); + }, + ), ], ), ), - ], - const SpaceHeight(8.0), - if (!isPayNow && !isAddToOrder) ...[ - const Divider(), - const SpaceHeight(8.0), - const Text( - 'Pilih Meja', - style: TextStyle( - color: AppColors.primary, - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - const SpaceHeight(12.0), - BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => - const CircularProgressIndicator(), - success: (tables) { - print( - "🔘 Tables fetched: ${tables.length} tables"); - print( - "🔘 Table statuses: ${tables.map((t) => '${t.tableName}: ${t.status}').join(', ')}"); - print( - "🔘 Current mode: ${!isPayNow && !isAddToOrder ? 'Pay Later' : isAddToOrder ? 'Add to Order' : 'Pay Now'}"); - // No need to filter since we're fetching the correct tables directly - final availableTables = tables; - - if (selectTable == null && - availableTables.isNotEmpty) { - selectTable = availableTables.first; - } - - if (availableTables.isEmpty) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.orange[50], - borderRadius: - BorderRadius.circular(16), - border: Border.all( - color: Colors.orange, - width: 1, - ), - ), - child: const Text( - 'Tidak ada meja yang tersedia. Silakan pilih opsi lain.', - style: - TextStyle(color: Colors.orange), - ), - ); - } - - return Container( - padding: const EdgeInsets.symmetric( - horizontal: 16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: Theme.of(context).primaryColor, - width: 2, - ), + if (selectedPaymentMethod?.type == "cash") + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.grey, + width: 1.0, ), - child: DropdownButtonHideUnderline( - child: DropdownButton( - isExpanded: true, - value: selectTable, - onChanged: (TableModel? newValue) { - setState(() { - selectTable = newValue; - }); - }, - items: availableTables - .map< - DropdownMenuItem>( - (TableModel value) => - DropdownMenuItem< - TableModel>( - value: value, - child: Text(value.tableName), - ), - ) - .toList(), - ), - ), - ); - }, - ); - }), - ], - const SpaceHeight(8.0), - if (isAddToOrder) ...[ - const Divider(), - const SpaceHeight(8.0), - const Text( - 'Pilih Meja yang Sudah Ada Pesanan', - style: TextStyle( - color: AppColors.primary, - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - const SpaceHeight(12.0), - BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => - const CircularProgressIndicator(), - success: (tables) { - print( - "🔘 Add to Order - Tables fetched: ${tables.length} tables"); - print( - "🔘 Add to Order - Table statuses: ${tables.map((t) => '${t.tableName}: ${t.status}').join(', ')}"); - // No need to filter since we're fetching occupied tables directly - final occupiedTables = tables; - - if (selectTable == null && - occupiedTables.isNotEmpty) { - selectTable = occupiedTables.first; - } - - return Container( - padding: const EdgeInsets.symmetric( - horizontal: 16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: Theme.of(context).primaryColor, - width: 2, - ), - ), - child: DropdownButtonHideUnderline( - child: DropdownButton( - isExpanded: true, - value: selectTable, - onChanged: (TableModel? newValue) { - setState(() { - selectTable = newValue; - }); - }, - items: occupiedTables - .map< - DropdownMenuItem>( - (TableModel value) => - DropdownMenuItem< - TableModel>( - value: value, - child: Text( - '${value.tableName} (Occupied)'), - ), - ) - .toList(), - ), - ), - ); - }, - ); - }), - ], - if (!isAddToOrder) ...[ - const SpaceHeight(8.0), - const Divider(), - const SpaceHeight(8.0), - const Text( - 'Customer', - style: TextStyle( - color: AppColors.primary, - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - const SpaceHeight(12.0), - TextFormField( - controller: customerController, - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.0), + ), ), - hintText: 'Nama Customer', - ), - textCapitalization: TextCapitalization.words, - ), - ], - const SpaceHeight(8.0), - const Divider(), - const SpaceHeight(8.0), - const OrderTypeSelector(), - const SpaceHeight(8.0), - if (isPayNow) ...[ - const Divider(), - const SpaceHeight(8.0), - const Text( - 'Metode Bayar', - style: TextStyle( - color: AppColors.primary, - fontSize: 20, - fontWeight: FontWeight.w600, - ), - ), - const SpaceHeight(12.0), - BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => const Center( - child: CircularProgressIndicator(), - ), - loading: () => const Center( - child: Column( - children: [ - CircularProgressIndicator(), - SizedBox(height: 8.0), - Text('Loading payment methods...'), - ], + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + const Text( + 'Total Bayar', + style: TextStyle( + color: AppColors.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), ), - ), - error: (message) => Column( - children: [ - Center( - child: Text( - 'Error loading payment methods: $message'), - ), - const SpaceHeight(16.0), - Button.filled( - onPressed: () { - context - .read() - .add(PaymentMethodsEvent - .fetchPaymentMethods()); - }, - label: 'Retry', - ), - ], - ), - loaded: (paymentMethods) { - log("Loaded ${paymentMethods.length} payment methods"); - paymentMethods.forEach((method) { - log("Payment method: ${method.name} (ID: ${method.id})"); - }); - if (paymentMethods.isEmpty) { - return Column( - children: [ - const Center( - child: Text( - 'No payment methods available'), + const SpaceHeight(8.0), + BlocBuilder( + builder: (context, state) { + return TextFormField( + controller: totalPriceController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: + BorderRadius.circular(8.0), + ), + hintText: 'Total harga', ), - const SpaceHeight(16.0), - Button.filled( - onPressed: () { - context - .read() - .add(PaymentMethodsEvent - .fetchPaymentMethods()); - }, - label: 'Retry', - ), - ], - ); - } - - // Set default selected payment method if none selected or if current selection is not in the list - if (selectedPaymentMethod == null || - !paymentMethods.any((method) => - method.id == - selectedPaymentMethod?.id)) { - selectedPaymentMethod = - paymentMethods.first; - } - - return Wrap( - spacing: 12.0, - runSpacing: 8.0, - children: paymentMethods.map((method) { - final isSelected = - selectedPaymentMethod?.id == - method.id; - return Container( - constraints: const BoxConstraints( - minWidth: 120.0, - ), - decoration: isSelected - ? BoxDecoration( - border: Border.all( - color: AppColors.primary, - width: 2.0, - ), - borderRadius: - BorderRadius.circular( - 8.0), - ) - : null, - child: Tooltip( - message: method.description ?? - 'No description available', - child: isSelected - ? Button.filled( - width: double.infinity, - height: 50.0, - onPressed: () { - setState(() { - selectedPaymentMethod = - method; - }); - }, - label: method.name - ?.isNotEmpty == - true - ? method.name! - : 'Unknown', - ) - : Button.outlined( - width: double.infinity, - height: 50.0, - onPressed: () { - setState(() { - selectedPaymentMethod = - method; - }); - }, - label: method.name - ?.isNotEmpty == - true - ? method.name! - : 'Unknown', - ), + onChanged: (value) { + priceValue = + value.toIntegerFromText; + final int newValue = + value.toIntegerFromText; + totalPriceController.text = + newValue.currencyFormatRp; + totalPriceController.selection = + TextSelection.fromPosition( + TextPosition( + offset: + totalPriceController + .text + .length)); + }, + ); + }, + ), + const SpaceHeight(20.0), + BlocBuilder( + builder: (context, state) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + Button.outlined( + width: 150.0, + onPressed: () { + totalPriceController.text = + uangPas + .toString() + .currencyFormatRpV2; + priceValue = uangPas; + }, + label: 'UANG PAS', + ), + const SpaceWidth(20.0), + Button.outlined( + width: 150.0, + onPressed: () { + totalPriceController.text = + uangPas2 + .toString() + .currencyFormatRpV2; + priceValue = uangPas2; + }, + label: uangPas2 + .toString() + .currencyFormatRpV2, + ), + const SpaceWidth(20.0), + Button.outlined( + width: 150.0, + onPressed: () { + totalPriceController.text = + uangPas3 + .toString() + .currencyFormatRpV2; + priceValue = uangPas3; + }, + label: uangPas3 + .toString() + .currencyFormatRpV2, + ), + ], ), ); - }).toList(), - ); - }, - ); - }, - ), - const SpaceHeight(8.0), - const Divider(), - const SpaceHeight(8.0), - const Text( - 'Total Bayar', - style: TextStyle( - color: AppColors.primary, - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - const SpaceHeight(12.0), - BlocBuilder( - builder: (context, state) { - return TextFormField( - controller: totalPriceController, - keyboardType: TextInputType.number, - decoration: InputDecoration( - border: OutlineInputBorder( - borderRadius: - BorderRadius.circular(8.0), - ), - hintText: 'Total harga', - ), - onChanged: (value) { - priceValue = value.toIntegerFromText; - final int newValue = - value.toIntegerFromText; - totalPriceController.text = - newValue.currencyFormatRp; - totalPriceController.selection = - TextSelection.fromPosition( - TextPosition( - offset: totalPriceController - .text.length)); - }, - ); - }, - ), - const SpaceHeight(20.0), - BlocBuilder( - builder: (context, state) { - return Row( - children: [ - Button.filled( - width: 150.0, - onPressed: () { - totalPriceController.text = uangPas - .toString() - .currencyFormatRpV2; - priceValue = uangPas; }, - label: 'UANG PAS', - ), - const SpaceWidth(20.0), - Button.filled( - width: 150.0, - onPressed: () { - totalPriceController.text = uangPas2 - .toString() - .currencyFormatRpV2; - priceValue = uangPas2; - }, - label: uangPas2 - .toString() - .currencyFormatRpV2, - ), - const SpaceWidth(20.0), - Button.filled( - width: 150.0, - onPressed: () { - totalPriceController.text = uangPas3 - .toString() - .currencyFormatRpV2; - priceValue = uangPas3; - }, - label: uangPas3 - .toString() - .currencyFormatRpV2, ), ], - ); - }, - ), - ] - ], - ), - ), - Align( - alignment: Alignment.bottomCenter, - child: ColoredBox( - color: AppColors.white, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 24.0, vertical: 16.0), - child: Row( - children: [ - Flexible( - child: Button.outlined( - onPressed: () => context.pop(), - label: 'Kembali', ), ), - const SpaceWidth(8.0), - BlocListener( - listener: (context, state) { - state.maybeWhen( - orElse: () {}, - savedDraftOrder: (orderDraftId) { - log("PRICEVALUE: ${priceValue}"); + ], + ), + ), + ), + BlocBuilder( + builder: (context, state) { + final orderType = state.maybeWhen( + orElse: () => OrderType.dineIn, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + orderType, + ); - final newTabel = TableModel( - id: widget.isTable - ? widget.table!.id - : selectTable?.id, - tableName: widget.isTable - ? widget.table!.tableName - : selectTable?.tableName ?? - '0', - status: 'occupied', - paymentAmount: priceValue, - orderId: orderDraftId, - startTime: DateTime.now() - .toIso8601String(), - position: widget.isTable - ? widget.table!.position - : selectTable!.position); - log('new tabel: ${newTabel.toMap()}'); - context - .read() - .add(StatusTableEvent.statusTabel( - newTabel, - )); - }); - }, - child: - BlocBuilder( - builder: (context, state) { - final discount = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) { - if (discountModel == null) { - return 0; - } - return discountModel.value! - .replaceAll('.00', '') - .toIntegerFromText; - }); + DeliveryModel? delivery = state.maybeWhen( + orElse: () => null, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + deliveryType, + ); - final price = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - products.fold( - 0, - (previousValue, element) => - previousValue + - (element.product.price! - .toIntegerFromText * - element.quantity), + List items = state.maybeWhen( + orElse: () => [], + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + products, + ); + int tax = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => + tax, + ); + + return Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + top: BorderSide( + color: AppColors.background, + width: 1.0, + ), + ), + ), + child: Row( + children: [ + Expanded( + child: Button.outlined( + onPressed: () => context.pop(), + label: 'Batalkan', + ), + ), + SpaceWidth(12), + Expanded( + child: Button.filled( + onPressed: () { + if (customerController.text == '') { + AppFlushbar.showError( + context, + 'Pilih Pelanggan terlebih dahulu', + ); + return; + } + + showDialog( + context: context, + builder: (dcontext) => SaveDialog( + selectedTable: widget.table, + customerName: customerController.text, + items: items, + orderType: orderType, + customer: selectedCustomer, + deliveryModel: delivery, ), ); - - final serviceCharge = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - serviceCharge, - ); - - final tax = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - tax, - ); - - final orderType = state.maybeWhen( - orElse: () => OrderType.dineIn, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - orderType, - ); - - final subTotal = - price - (discount / 100 * price); - final totalDiscount = - discount / 100 * price; - final finalTax = subTotal * (tax / 100); - final totalServiceCharge = - (serviceCharge / 100) * price; - - List items = - state.maybeWhen( - orElse: () => [], - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => - products, - ); - final totalQty = items.fold( - 0, - (previousValue, element) => - previousValue + element.quantity, - ); - - return Flexible( - child: Button.filled( - onPressed: () async { - print("🔘 Payment button pressed"); - print("🔘 isPayNow: $isPayNow"); - print( - "🔘 isAddToOrder: $isAddToOrder"); - print( - "🔘 selectedPaymentMethod: ${selectedPaymentMethod?.name}"); - if (selectedPaymentMethod == null) { - ScaffoldMessenger.of(context) - .showSnackBar( - const SnackBar( - content: Text( - 'Please select a payment method'), - backgroundColor: Colors.red, - ), - ); - return; - } - - if (widget.isTable) { - log("discountAmountValue: $totalDiscount"); - context.read().add( - CheckoutEvent - .saveDraftOrder( - widget.isTable == true - ? widget.table!.id! - : selectTable!.id!, - customerController.text, - totalDiscount.toInt(), - ), - ); - await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => - SaveOrderDialog( - data: items, - totalQty: totalQty, - totalPrice: totalPriceFinal, - totalTax: finalTax.toInt(), - totalDiscount: - totalDiscount.toInt(), - subTotal: subTotal.toInt(), - normalPrice: price, - table: widget.table!, - draftName: - customerController.text, - ), - ); - } else if (isPayNow) { - log("🔘 Entering Pay Now flow"); - // context.read().add( - // OrderEvent.addPaymentMethod( - // items, - // totalPrice, - // finalTax, - // discount != null - // ? discount.value - // .replaceAll( - // '.00', '') - // .toIntegerFromText - // : 0, - // finalDiscountAmount, - // finalService, - // subTotal, - // totalPriceController.text - // .toIntegerFromText, - // auth?.user.name ?? '-', - // totalQuantity, - // auth?.user.id ?? 1, - // isCash - // ? 'Cash' - // : 'QR Pay')); - final paymentMethodName = - selectedPaymentMethod?.name - ?.toLowerCase() ?? - ''; - log("Selected payment method: ${selectedPaymentMethod?.name} (normalized: $paymentMethodName)"); - if (paymentMethodName == 'cash' || - paymentMethodName == - 'tunai' || - paymentMethodName == - 'uang tunai' || - paymentMethodName == - 'cash payment') { - log("🔘 Payment method is cash, proceeding with OrderBloc call"); - log("discountAmountValue: $totalDiscount"); - log("💳 About to call OrderBloc for cash payment"); - log("💳 OrderBloc instance: ${context.read()}"); - log("💳 About to dispatch OrderEvent.order for cash payment"); - context.read().add( - OrderEvent.order( - items, - discount, - totalDiscount.toInt(), - finalTax.toInt(), - 0, - totalPriceController - .text - .toIntegerFromText, - customerController.text, - 0, - 'completed', - 'paid', - selectedPaymentMethod - ?.name ?? - 'Cash', - totalPriceFinal, - orderType)); - log("💳 OrderEvent.order dispatched for cash payment"); - await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => - SuccessPaymentDialog( - data: items, - totalQty: totalQty, - totalPrice: totalPriceFinal, - totalTax: finalTax.toInt(), - totalDiscount: - totalDiscount.toInt(), - subTotal: subTotal.toInt(), - normalPrice: price, - totalService: - totalServiceCharge - .toInt(), - draftName: - customerController.text, - ), - ); - } else { - // Process non-cash payment directly without QRIS dialog - log("Processing non-cash payment: ${selectedPaymentMethod?.name}"); - log("💳 About to call OrderBloc for non-cash payment"); - log("💳 OrderBloc instance: ${context.read()}"); - context.read().add( - OrderEvent.order( - items, - discount, - totalDiscount.toInt(), - finalTax.toInt(), - 0, - totalPriceController - .text - .toIntegerFromText, - customerController.text, - 0, - 'completed', - 'paid', - selectedPaymentMethod - ?.name ?? - 'Unknown Payment Method', - totalPriceFinal, - orderType)); - await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => - SuccessPaymentDialog( - data: items, - totalQty: totalQty, - totalPrice: totalPriceFinal, - totalTax: finalTax.toInt(), - totalDiscount: - totalDiscount.toInt(), - subTotal: subTotal.toInt(), - normalPrice: price, - totalService: - totalServiceCharge - .toInt(), - draftName: - customerController.text, - ), + }, + label: 'Simpan', + ), + ), + SpaceWidth(12), + BlocListener( + listener: (lcontext, state) { + state.maybeWhen( + orElse: () {}, + success: (data) { + context + .pushReplacement(SuccessOrderPage( + productQuantity: items, + order: data, + paymentMethod: + selectedPaymentMethod?.name ?? "", + nominalBayar: totalPriceController + .text.toIntegerFromText, + taxPercentage: tax, + )); + }, + error: (message) => AppFlushbar.showError( + context, + message, + ), + ); + }, + child: BlocBuilder( + builder: (context, state) { + return Expanded( + child: state.maybeMap( + orElse: () => Button.filled( + onPressed: () { + if (customerController.text == + '') { + AppFlushbar.showError( + context, + 'Pilih Pelanggan terlebih dahulu', ); + return; } - } else if (isAddToOrder) { - // Tambahkan ke pesanan meja yang sudah ada - if (selectTable != null) { - // Ambil draft order yang sudah ada - final existingDraftOrder = - await ProductLocalDatasource - .instance - .getDraftOrderById( - selectTable! - .orderId ?? - 0); - if (existingDraftOrder != - null) { - // Convert items ke DraftOrderItem - final newDraftItems = items - .map((item) => - DraftOrderItem( - product: - item.product, - quantity: - item.quantity, - )) - .toList(); - - // Gabungkan dengan pesanan yang sudah ada - final updatedItems = [ - ...existingDraftOrder - .orders, - ...newDraftItems - ]; - - final updatedDraftOrder = - existingDraftOrder - .copyWith( - orders: updatedItems, - totalQuantity: - updatedItems.fold( - 0, - (sum, item) => - sum + - item.quantity), - subTotal: updatedItems.fold< - int>( - 0, - (sum, item) => - sum + - (int.tryParse(item - .product - .price ?? - '0') ?? - 0) * - item.quantity), - ); - - // Update draft order - await ProductLocalDatasource - .instance - .updateDraftOrder( - updatedDraftOrder); - - // Tampilkan dialog sukses - await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => - SaveOrderDialog( - data: items, - totalQty: totalQty, - totalPrice: - totalPriceFinal, - totalTax: - finalTax.toInt(), - totalDiscount: - totalDiscount.toInt(), - subTotal: - subTotal.toInt(), - normalPrice: price, - table: selectTable!, - draftName: + context.read().add( + OrderFormEvent + .createWithPayment( + items: items, + customerName: customerController .text, + orderType: orderType, + paymentMethod: + selectedPaymentMethod!, + table: widget.table, + customer: + selectedCustomer, ), ); - } else { - // Jika tidak ada draft order, buat baru - context - .read() - .add( - CheckoutEvent - .saveDraftOrder( - selectTable!.id!, - customerController - .text, - totalDiscount.toInt(), - ), - ); - await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => - SaveOrderDialog( - data: items, - totalQty: totalQty, - totalPrice: - totalPriceFinal, - totalTax: - finalTax.toInt(), - totalDiscount: - totalDiscount.toInt(), - subTotal: - subTotal.toInt(), - normalPrice: price, - table: selectTable!, - draftName: - customerController - .text, - ), - ); - } - } else { - ScaffoldMessenger.of(context) - .showSnackBar( - const SnackBar( - content: Text( - 'Pilih meja terlebih dahulu'), - backgroundColor: Colors.red, - ), - ); - } - } else { - context.read().add( - CheckoutEvent - .saveDraftOrder( - widget.isTable == true - ? widget.table!.id! - : selectTable!.id!, - customerController.text, - totalDiscount.toInt(), - ), - ); - await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => - SaveOrderDialog( - data: items, - totalQty: totalQty, - totalPrice: totalPriceFinal, - totalTax: finalTax.toInt(), - totalDiscount: - totalDiscount.toInt(), - subTotal: subTotal.toInt(), - normalPrice: price, - table: selectTable!, - draftName: - customerController.text, - ), - ); - } - }, - label: isPayNow - ? 'Bayar' - : isAddToOrder - ? 'Simpan' - : 'Simpan Order', + }, + label: 'Bayar', + ), + loading: (_) => Center( + child: CircularProgressIndicator(), + ), ), ); }, @@ -1681,8 +1097,8 @@ class _ConfirmPaymentPageState extends State { ), ], ), - ), - ), + ); + }, ), ], ), diff --git a/lib/presentation/home/pages/dashboard_page.dart b/lib/presentation/home/pages/dashboard_page.dart index 547f8f9..f00472c 100644 --- a/lib/presentation/home/pages/dashboard_page.dart +++ b/lib/presentation/home/pages/dashboard_page.dart @@ -3,23 +3,19 @@ import 'dart:async'; import 'dart:developer'; import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:enaklo_pos/presentation/customer/pages/customer_page.dart'; +import 'package:enaklo_pos/presentation/setting/pages/setting_page.dart'; +import 'package:enaklo_pos/presentation/table/pages/table_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/data/models/response/table_model.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:enaklo_pos/core/components/spaces.dart'; import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; import 'package:enaklo_pos/presentation/auth/login_page.dart'; import 'package:enaklo_pos/presentation/report/pages/report_page.dart'; -import 'package:enaklo_pos/presentation/sales/pages/sales_page.dart'; import 'package:enaklo_pos/presentation/setting/bloc/sync_order/sync_order_bloc.dart'; -import 'package:enaklo_pos/presentation/setting/pages/printer_configuration_page.dart'; -import 'package:enaklo_pos/presentation/setting/pages/settings_page.dart'; -import 'package:enaklo_pos/presentation/table/pages/new_table_management_page.dart'; -import 'package:enaklo_pos/presentation/table/pages/table_page.dart'; import '../../../core/assets/assets.gen.dart'; import '../../auth/bloc/logout/logout_bloc.dart'; @@ -31,10 +27,10 @@ class DashboardPage extends StatefulWidget { final int? index; final TableModel? table; const DashboardPage({ - Key? key, + super.key, this.index = 0, this.table, - }) : super(key: key); + }); @override State createState() => _DashboardPageState(); @@ -50,6 +46,8 @@ class _DashboardPageState extends State { setState(() {}); } + late StreamSubscription> _connectivitySubscription; + @override void initState() { super.initState(); @@ -59,31 +57,25 @@ class _DashboardPageState extends State { isTable: false, table: widget.table, ), - // const TablePage(), - TableManagementScreen(), + const TablePage(), const ReportPage(), - const PrinterConfigurationPage(), - // SalesPage(), - const SettingsPage(), + const CustomerPage(), + const SettingPage(), ]; - StreamSubscription> subscription = Connectivity() + // ignore: unused_local_variable + _connectivitySubscription = Connectivity() .onConnectivityChanged .listen((List connectivityResult) { - // Received changes in available connectivity types! + if (!mounted) return; // <-- Tambahkan ini! if (connectivityResult.contains(ConnectivityResult.mobile)) { - // Mobile network available. context .read() .add(const OnlineCheckerEvent.check(true)); } else if (connectivityResult.contains(ConnectivityResult.wifi)) { - // Wi-fi is available. context .read() .add(const OnlineCheckerEvent.check(true)); - // Note for Android: - // When both mobile and Wi-Fi are turned on system will return Wi-Fi only as active network type } else { - // Neither mobile network nor Wi-fi available. context .read() .add(const OnlineCheckerEvent.check(false)); @@ -91,6 +83,12 @@ class _DashboardPageState extends State { }); } + @override + void dispose() { + _connectivitySubscription.cancel(); // <-- Cancel subscription di dispose + super.dispose(); + } + @override Widget build(BuildContext context) { return SafeArea( @@ -100,7 +98,8 @@ class _DashboardPageState extends State { Expanded( flex: 1, child: SizedBox( - height: (context.deviceHeight - 40.0).clamp(0.0, double.infinity), + height: + (context.deviceHeight - 40.0).clamp(0.0, double.infinity), child: ColoredBox( color: AppColors.primary, child: ListView( @@ -121,7 +120,7 @@ class _DashboardPageState extends State { onTap: () => _onItemTapped(2), ), NavItem( - iconPath: Assets.icons.print.path, + iconPath: Assets.icons.people.path, isActive: _selectedIndex == 3, onTap: () => _onItemTapped(3), ), diff --git a/lib/presentation/home/pages/home_page.dart b/lib/presentation/home/pages/home_page.dart index a255743..f3e3bd6 100644 --- a/lib/presentation/home/pages/home_page.dart +++ b/lib/presentation/home/pages/home_page.dart @@ -1,18 +1,18 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first +import 'package:enaklo_pos/core/components/flushbar.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/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/widgets/category_tab_bar.dart'; +import 'package:enaklo_pos/presentation/home/widgets/home_right_title.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.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/models/response/table_model.dart'; -import 'package:enaklo_pos/presentation/home/bloc/local_product/local_product_bloc.dart'; -import 'package:enaklo_pos/presentation/home/dialog/discount_dialog.dart'; -import 'package:enaklo_pos/presentation/home/dialog/tax_dialog.dart'; import 'package:enaklo_pos/presentation/home/pages/confirm_payment_page.dart'; -import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart'; -import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; -import 'package:enaklo_pos/presentation/setting/bloc/sync_product/sync_product_bloc.dart'; import 'package:enaklo_pos/data/models/response/product_response_model.dart'; import '../../../core/assets/assets.gen.dart'; @@ -20,9 +20,6 @@ import '../../../core/components/buttons.dart'; import '../../../core/components/spaces.dart'; import '../../../core/constants/colors.dart'; import '../bloc/checkout/checkout_bloc.dart'; -import '../dialog/service_dialog.dart'; -import '../widgets/column_button.dart'; -import '../widgets/custom_tab_bar.dart'; import '../widgets/home_title.dart'; import '../widgets/order_menu.dart'; import '../widgets/product_card.dart'; @@ -31,10 +28,10 @@ class HomePage extends StatefulWidget { final bool isTable; final TableModel? table; const HomePage({ - Key? key, + super.key, required this.isTable, this.table, - }) : super(key: key); + }); @override State createState() => _HomePageState(); @@ -42,26 +39,49 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { final searchController = TextEditingController(); + final ScrollController scrollController = ScrollController(); String searchQuery = ''; + test() async { + // await AuthLocalDataSource().removeAuthData(); + } + @override void initState() { + test(); // First sync products from API, then load local products _syncAndLoadProducts(); super.initState(); } + @override + void dispose() { + // Properly dispose controllers + searchController.dispose(); + scrollController.dispose(); + super.dispose(); + } + void _syncAndLoadProducts() { // Trigger sync from API first - context.read().add(const SyncProductEvent.syncProduct()); - + // context.read().add(const SyncProductEvent.syncProduct()); + // Also load local products initially in case sync fails or takes time + // context + // .read() + // .add(const LocalProductEvent.getLocalProduct()); context - .read() - .add(const LocalProductEvent.getLocalProduct()); - + .read() + .add(const ProductLoaderEvent.getProduct()); + // Initialize checkout with tax and service charge settings context.read().add(const CheckoutEvent.started()); + + // Get Category + context.read().add(CategoryLoaderEvent.get()); + + // Get Outlets + context.read().add(CurrentOutletEvent.currentOutlet()); } void onCategoryTap(int index) { @@ -75,7 +95,7 @@ class _HomePageState extends State { if (searchQuery.isEmpty) { return products; } - + return products.where((product) { final productName = product.name?.toLowerCase() ?? ''; final queryLower = searchQuery.toLowerCase(); @@ -83,139 +103,129 @@ class _HomePageState extends State { }).toList(); } - List _filterProductsByCategory(List products, int categoryId) { - final filteredBySearch = _filterProducts(products); - return filteredBySearch.where((element) => element.category?.id == categoryId).toList(); + bool _handleScrollNotification( + ScrollNotification notification, String? categoryId) { + // Check if the ScrollController is attached before accessing position + if (!scrollController.hasClients) { + return false; + } + + if (notification is ScrollEndNotification && + scrollController.position.extentAfter == 0) { + context.read().add( + ProductLoaderEvent.loadMore( + categoryId: categoryId, + search: searchQuery, + ), + ); + return true; + } + return false; } @override Widget build(BuildContext context) { - return Hero( - tag: 'confirmation_screen', - child: Scaffold( - body: BlocListener( - listener: (context, state) { - state.maybeWhen( - orElse: () {}, - error: (message) { - // If sync fails, still try to load local products - context - .read() - .add(const LocalProductEvent.getLocalProduct()); - }, - loaded: (productResponseModel) async { - // Store context reference before async operations - final localProductBloc = context.read(); - - // Save synced products to local database - await ProductLocalDatasource.instance.deleteAllProducts(); - await ProductLocalDatasource.instance.insertProducts( - productResponseModel.data!, - ); - // Then load local products to display - localProductBloc.add(const LocalProductEvent.getLocalProduct()); - }, - ); + return BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + loading: () {}, + success: () { + Future.delayed(Duration(milliseconds: 300), () { + AppFlushbar.showSuccess(context, 'Outlet berhasil diubah'); + context + .read() + .add(CurrentOutletEvent.currentOutlet()); + }); }, - child: Row( + error: (message) => AppFlushbar.showError(context, message), + ); + }, + child: Hero( + tag: 'confirmation_screen', + child: Scaffold( + backgroundColor: AppColors.white, + body: Row( children: [ Expanded( flex: 3, child: Align( alignment: AlignmentDirectional.topStart, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - HomeTitle( - controller: searchController, - onChanged: (value) { - setState(() { - searchQuery = value; - }); - }, - ), - const SizedBox(height: 24), - BlocBuilder( - builder: (context, state) { - return CustomTabBar( - tabTitles: const [ - 'Semua', - 'Makanan', - 'Minuman', - 'Paket' - ], - initialTabIndex: 0, - tabViews: [ - // All Products Tab - SizedBox( - child: state.maybeWhen(orElse: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loaded: (products) { - final filteredProducts = _filterProducts(products); - if (filteredProducts.isEmpty) { - return const Center( - child: Text('No Items Found'), - ); - } - return GridView.builder( - shrinkWrap: true, - itemCount: filteredProducts.length, - physics: - const NeverScrollableScrollPhysics(), - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - childAspectRatio: 0.85, - crossAxisCount: 3, - crossAxisSpacing: 30.0, - mainAxisSpacing: 30.0, - ), - itemBuilder: (context, index) => - ProductCard( - data: filteredProducts[index], - onCartButton: () {}, + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Center( + child: CircularProgressIndicator(), + ), + loaded: (categories, categoryId) => Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + HomeTitle( + controller: searchController, + onChanged: (value) { + setState(() { + searchQuery = value; + }); + Future.delayed(Duration(milliseconds: 600), () { + context.read().add( + ProductLoaderEvent.getProduct( + categoryId: categoryId, + search: value, ), ); - }), - ), - // Makanan Tab - SizedBox( - child: state.maybeWhen(orElse: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loaded: (products) { - if (products.isEmpty) { - return const Center( - child: Text('No Items'), - ); - } - final filteredProducts = _filterProductsByCategory(products, 1); - return filteredProducts.isEmpty - ? const _IsEmpty() - : GridView.builder( - shrinkWrap: true, - itemCount: filteredProducts.length, - physics: - const NeverScrollableScrollPhysics(), + }); + }, + ), + BlocBuilder( + builder: (context, state) { + return NotificationListener( + onNotification: (notification) { + return state.maybeWhen( + orElse: () => false, + loaded: (products, hasReachedMax, + currentPage, isLoadingMore) { + return _handleScrollNotification( + notification, categoryId); + }, + ); + }, + child: Expanded( + child: CategoryTabBar( + categories: categories, + tabViews: categories.map((category) { + return SizedBox( + child: state.maybeWhen(orElse: () { + return const Center( + child: + CircularProgressIndicator(), + ); + }, loading: () { + 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( + itemCount: + filteredProducts.length, + controller: scrollController, + padding: const EdgeInsets.all(16), gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - childAspectRatio: 0.85, - crossAxisCount: 3, - crossAxisSpacing: 30.0, - mainAxisSpacing: 30.0, + SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 180, + mainAxisSpacing: 30, + crossAxisSpacing: 30, + childAspectRatio: 180 / 240, ), itemBuilder: (context, index) => ProductCard( @@ -223,95 +233,18 @@ class _HomePageState extends State { onCartButton: () {}, ), ); - }), - ), - // Minuman Tab - SizedBox( - child: state.maybeWhen(orElse: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loaded: (products) { - if (products.isEmpty) { - return const Center( - child: Text('No Items'), + }), ); - } - final filteredProducts = _filterProductsByCategory(products, 2); - return filteredProducts.isEmpty - ? const _IsEmpty() - : GridView.builder( - shrinkWrap: true, - itemCount: filteredProducts.length, - physics: - const NeverScrollableScrollPhysics(), - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - childAspectRatio: 0.85, - crossAxisCount: 3, - crossAxisSpacing: 30.0, - mainAxisSpacing: 30.0, - ), - itemBuilder: (context, index) { - return ProductCard( - data: filteredProducts[index], - onCartButton: () {}, - ); - }, - ); - }), + }).toList(), + ), ), - // Snack Tab - SizedBox( - child: state.maybeWhen(orElse: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loaded: (products) { - if (products.isEmpty) { - return const Center( - child: Text('No Items'), - ); - } - final filteredProducts = _filterProductsByCategory(products, 3); - return filteredProducts.isEmpty - ? const _IsEmpty() - : GridView.builder( - shrinkWrap: true, - itemCount: filteredProducts.length, - physics: - const NeverScrollableScrollPhysics(), - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - childAspectRatio: 0.85, - crossAxisCount: 3, - crossAxisSpacing: 30.0, - mainAxisSpacing: 30.0, - ), - itemBuilder: (context, index) { - return ProductCard( - data: filteredProducts[index], - onCartButton: () {}, - ); - }, - ); - }), - ), - ], - ); - }, - ), - ], - ), - ), + ); + }, + ), + ], + ), + ); + }, ), ), ), @@ -319,344 +252,273 @@ class _HomePageState extends State { flex: 2, child: Align( alignment: Alignment.topCenter, - child: Stack( - children: [ - SingleChildScrollView( - padding: const EdgeInsets.all(24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - onTap: () { - if (widget.table == null) { - context.push(DashboardPage( - index: 1, - )); - } - }, - child: Text( - 'Meja: ${widget.table == null ? 'Belum Pilih Meja' : '${widget.table!.id}'}', - style: TextStyle( - color: AppColors.primary, - fontSize: 20, - fontWeight: FontWeight.w600, - ), - ), - ), - const SpaceHeight(8.0), - Button.filled( - width: 180.0, - height: 40, - onPressed: () {}, - label: 'Pesanan#', - ), - - // Row( - // children: [ - // Button.filled( - // width: 120.0, - // height: 40, - // onPressed: () {}, - // label: 'Dine In', - // ), - // const SpaceWidth(8.0), - // Button.outlined( - // width: 100.0, - // height: 40, - // onPressed: () {}, - // label: 'To Go', - // ), - // ], - // ), - const SpaceHeight(16.0), - const Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Item', - style: TextStyle( - color: AppColors.primary, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - SizedBox( - width: 130, - ), - SizedBox( - width: 50.0, - child: Text( - 'Qty', - style: TextStyle( - color: AppColors.primary, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - ), - SizedBox( - child: Text( - 'Price', - style: TextStyle( - color: AppColors.primary, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - ), - ], - ), - const SpaceHeight(8), - const Divider(), - const SpaceHeight(8), - BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => const Center( - child: Text('No Items'), - ), - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) { - if (products.isEmpty) { - return const Center( - child: Text('No Items'), - ); - } - return ListView.separated( - shrinkWrap: true, - physics: - const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) => - OrderMenu(data: products[index]), - separatorBuilder: (context, index) => - const SpaceHeight(1.0), - itemCount: products.length, - ); - }, - ); - }, - ), - const SpaceHeight(8.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ColumnButton( - label: 'Diskon', - svgGenImage: Assets.icons.diskon, - onPressed: () => showDialog( - context: context, - barrierDismissible: false, - builder: (context) => const DiscountDialog(), - ), - ), - ColumnButton( - label: 'Pajak PB1', - svgGenImage: Assets.icons.pajak, - onPressed: () => showDialog( - context: context, - builder: (context) => const TaxDialog(), - ), - ), - ColumnButton( - label: 'Layanan', - svgGenImage: Assets.icons.layanan, - onPressed: () => showDialog( - context: context, - builder: (context) => const ServiceDialog(), - ), - ), - ], - ), - const SpaceHeight(8.0), - const Divider(), - const SpaceHeight(8.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Pajak PB1', - style: TextStyle(color: AppColors.grey), - ), - BlocBuilder( - builder: (context, state) { - final tax = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) { - if (products.isEmpty) { - return 0; - } - return tax; - }); - return Text( - '$tax %', - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.w600, - ), - ); - }, - ), - ], - ), - const SpaceHeight(8.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Layanan', - style: TextStyle(color: AppColors.grey), - ), - BlocBuilder( - builder: (context, state) { - final serviceCharge = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) { - return serviceCharge; - }); - return Text( - '$serviceCharge %', - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.w600, - ), - ); - }, - ), - ], - ), - const SpaceHeight(8.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Diskon', - style: TextStyle(color: AppColors.grey), - ), - BlocBuilder( - builder: (context, state) { - final discount = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) { - if (discountModel == null) { - return 0; - } - return discountModel.value! - .replaceAll('.00', '') - .toIntegerFromText; - }); - return Text( - '$discount %', - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.w600, - ), - ); - }, - ), - ], - ), - const SpaceHeight(8.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Sub total', - style: TextStyle( - color: AppColors.grey, - fontWeight: FontWeight.bold, - fontSize: 16), - ), - BlocBuilder( - builder: (context, state) { - final price = state.maybeWhen( - orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) { - if (products.isEmpty) { - return 0; - } - return products - .map((e) => - e.product.price! - .toIntegerFromText * - e.quantity) - .reduce((value, element) => - value + element); - }); - - return Text( - price.currencyFormatRp, - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.bold, - fontSize: 16), - ); - }, - ), - ], - ), - const SpaceHeight(100.0), - ], + child: Material( + color: Colors.white, + child: Column( + children: [ + HomeRightTitle( + table: widget.table, ), - ), - Align( - alignment: Alignment.bottomCenter, - child: ColoredBox( - color: AppColors.white, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 24.0, vertical: 16.0), - child: Button.filled( - onPressed: () { - context.push(ConfirmPaymentPage( - isTable: widget.isTable, - table: widget.table, - )); - }, - label: 'Lanjutkan Pembayaran', - ), + Padding( + padding: const EdgeInsets.all(16.0) + .copyWith(bottom: 0, top: 27), + child: Column( + children: [ + const Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Item', + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + SizedBox( + width: 130, + ), + SizedBox( + width: 50.0, + child: Text( + 'Qty', + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + SizedBox( + child: Text( + 'Price', + style: TextStyle( + color: AppColors.primary, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + const SpaceHeight(8), + const Divider(), + ], ), ), - ), - ], + Expanded( + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const Center( + child: Text('No Items'), + ), + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { + if (products.isEmpty) { + return const Center( + child: Text('No Items'), + ); + } + return ListView.separated( + shrinkWrap: true, + padding: const EdgeInsets.symmetric( + horizontal: 16), + itemBuilder: (context, index) => + OrderMenu(data: products[index]), + separatorBuilder: (context, index) => + const SpaceHeight(1.0), + itemCount: products.length, + ); + }, + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.all(16.0).copyWith(top: 0), + child: Column( + children: [ + const Divider(), + const SpaceHeight(16.0), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Pajak', + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.bold, + ), + ), + BlocBuilder( + builder: (context, state) { + final tax = state.maybeWhen( + orElse: () => 0, + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { + if (products.isEmpty) { + return 0; + } + return tax; + }); + return Text( + '$tax %', + style: const TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.w600, + ), + ); + }, + ), + ], + ), + const SpaceHeight(16.0), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Sub total', + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.bold, + ), + ), + BlocBuilder( + builder: (context, state) { + final price = state.maybeWhen( + orElse: () => 0, + loaded: (products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType) { + if (products.isEmpty) { + return 0; + } + return products + .map((e) => + (e.product.price! * + e.quantity) + + (e.variant?.priceModifier ?? + 0)) + .reduce((value, element) => + value + element); + }); + + return Text( + price.currencyFormatRp, + style: const TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.w900, + ), + ); + }, + ), + ], + ), + SpaceHeight(16.0), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Align( + alignment: Alignment.bottomCenter, + child: Button.filled( + borderRadius: 12, + elevation: 1, + disabled: true, + onPressed: () { + context.push(ConfirmPaymentPage( + isTable: widget.table == null + ? false + : true, + table: widget.table, + )); + }, + label: 'Lanjutkan Pembayaran', + ), + ), + loaded: (items, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType) => + Align( + alignment: Alignment.bottomCenter, + child: Button.filled( + borderRadius: 12, + elevation: 1, + disabled: items.isEmpty, + onPressed: () { + if (orderType.name == 'dineIn' && + widget.table == null) { + AppFlushbar.showError(context, + 'Mohon pilih meja terlebih dahulu'); + return; + } + + if (orderType.name == 'delivery' && + deliveryType == null) { + AppFlushbar.showError(context, + 'Mohon pilih pengiriman terlebih dahulu'); + return; + } + + context.push(ConfirmPaymentPage( + isTable: widget.table == null + ? false + : true, + table: widget.table, + )); + }, + label: 'Lanjutkan Pembayaran', + ), + ), + ); + }, + ), + ], + ), + ), + ], + ), ), ), ), @@ -668,6 +530,7 @@ class _HomePageState extends State { } } +// ignore: unused_element class _IsEmpty extends StatelessWidget { const _IsEmpty(); diff --git a/lib/presentation/home/widgets/category_tab_bar.dart b/lib/presentation/home/widgets/category_tab_bar.dart new file mode 100644 index 0000000..5abfaf3 --- /dev/null +++ b/lib/presentation/home/widgets/category_tab_bar.dart @@ -0,0 +1,100 @@ +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/data/models/response/category_response_model.dart'; +import 'package:enaklo_pos/presentation/home/bloc/category_loader/category_loader_bloc.dart'; +import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CategoryTabBar extends StatefulWidget { + final List categories; + final List tabViews; + const CategoryTabBar( + {super.key, required this.categories, required this.tabViews}); + + @override + State createState() => _CategoryTabBarState(); +} + +class _CategoryTabBarState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + String? selectedCategoryId; + + @override + void initState() { + super.initState(); + _tabController = + TabController(length: widget.categories.length, vsync: this); + + _tabController.addListener(() { + if (_tabController.indexIsChanging) { + if (_tabController.index == 0) { + context.read().add( + ProductLoaderEvent.getProduct(), + ); + } else { + selectedCategoryId = widget.categories[_tabController.index].id; + context.read().add( + ProductLoaderEvent.getProduct(categoryId: selectedCategoryId), + ); + context + .read() + .add(CategoryLoaderEvent.setCategoryId(selectedCategoryId ?? "")); + } + } + }); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: widget.categories.length, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Material( + elevation: 0, + color: Colors.white, + borderOnForeground: false, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: TabBar( + controller: _tabController, + isScrollable: true, + tabAlignment: TabAlignment.start, + labelColor: AppColors.primary, + labelStyle: TextStyle( + fontWeight: FontWeight.bold, + ), + dividerColor: AppColors.primary, + unselectedLabelColor: AppColors.primary, + indicatorSize: TabBarIndicatorSize.label, + indicatorWeight: 4, + indicatorColor: AppColors.primary, + tabs: widget.categories + .map((category) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Tab(text: category.name), + )) + .toList(), + ), + ), + ), + Expanded( + // ✅ ini bagian penting + child: TabBarView( + controller: _tabController, + children: widget.tabViews, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/home/widgets/confirm_payment_title.dart b/lib/presentation/home/widgets/confirm_payment_title.dart new file mode 100644 index 0000000..345e048 --- /dev/null +++ b/lib/presentation/home/widgets/confirm_payment_title.dart @@ -0,0 +1,74 @@ +import 'package:enaklo_pos/core/components/components.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:flutter/material.dart'; + +class ConfirmPaymentTitle extends StatelessWidget { + final String title; + final String? subtitle; + final bool isBack; + final List? actionWidget; + const ConfirmPaymentTitle({ + super.key, + required this.title, + this.subtitle, + this.isBack = true, + this.actionWidget, + }); + + @override + Widget build(BuildContext context) { + return Container( + height: context.deviceHeight * 0.1, + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.grey, + width: 1.0, + ), + ), + ), + child: Row( + children: [ + if (isBack) ...[ + GestureDetector( + onTap: () => context.pop(), + child: Icon( + Icons.arrow_back, + color: AppColors.primary, + size: 24, + ), + ), + SpaceWidth(16), + ], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + color: AppColors.black, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + if (subtitle != null) ...[ + const SizedBox(height: 4.0), + Text( + subtitle!, + style: + const TextStyle(fontSize: 14.0, color: AppColors.grey), + ), + ] + ], + ), + ), + if (actionWidget != null) ...actionWidget!, + ], + ), + ); + } +} diff --git a/lib/presentation/home/widgets/custom_tab_bar.dart b/lib/presentation/home/widgets/custom_tab_bar.dart index ca7c652..dd3d51b 100644 --- a/lib/presentation/home/widgets/custom_tab_bar.dart +++ b/lib/presentation/home/widgets/custom_tab_bar.dart @@ -2,8 +2,6 @@ import 'package:flutter/material.dart'; import '../../../core/constants/colors.dart'; - - class CustomTabBar extends StatefulWidget { final List tabTitles; final int initialTabIndex; @@ -34,33 +32,38 @@ class _CustomTabBarState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Row( - children: List.generate( - widget.tabTitles.length, - (index) => GestureDetector( - onTap: () { - setState(() { - _selectedIndex = index; - }); - }, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 14), - margin: const EdgeInsets.only(right: 32), - decoration: BoxDecoration( - border: _selectedIndex == index - ? const Border( - bottom: BorderSide( - width: 3.0, - color: AppColors.primary, - ), - ) - : null, - ), - child: Text( - widget.tabTitles[index], - style: const TextStyle( - color: AppColors.primary, - fontWeight: FontWeight.bold, + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + ), + child: Row( + children: List.generate( + widget.tabTitles.length, + (index) => GestureDetector( + onTap: () { + setState(() { + _selectedIndex = index; + }); + }, + child: Container( + padding: + const EdgeInsets.symmetric(vertical: 6, horizontal: 12), + margin: const EdgeInsets.only(right: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: _selectedIndex == index + ? AppColors.primary + : Colors.transparent, + ), + child: Text( + widget.tabTitles[index], + style: TextStyle( + color: _selectedIndex == index + ? Colors.white + : AppColors.primary, + fontWeight: FontWeight.bold, + ), ), ), ), @@ -75,3 +78,59 @@ class _CustomTabBarState extends State { ); } } + +class CustomTabBarV2 extends StatelessWidget { + final List tabTitles; + final List tabViews; + + const CustomTabBarV2({ + super.key, + required this.tabTitles, + required this.tabViews, + }); + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: tabTitles.length, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Material( + elevation: 0, + color: Colors.white, + borderOnForeground: false, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: TabBar( + isScrollable: true, + tabAlignment: TabAlignment.start, + labelColor: AppColors.primary, + labelStyle: TextStyle( + fontWeight: FontWeight.bold, + ), + dividerColor: AppColors.primary, + unselectedLabelColor: AppColors.primary, + indicatorSize: TabBarIndicatorSize.label, + indicatorWeight: 4, + indicatorColor: AppColors.primary, + tabs: tabTitles + .map((title) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Tab(text: title), + )) + .toList(), + ), + ), + ), + Expanded( + // ✅ ini bagian penting + child: TabBarView( + children: tabViews, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/home/widgets/customer_auto_complete_field.dart b/lib/presentation/home/widgets/customer_auto_complete_field.dart new file mode 100644 index 0000000..c92870f --- /dev/null +++ b/lib/presentation/home/widgets/customer_auto_complete_field.dart @@ -0,0 +1,119 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import 'package:enaklo_pos/presentation/customer/bloc/customer_loader/customer_loader_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CustomerAutocomplete extends StatefulWidget { + final TextEditingController controller; + final Customer? selectedCustomer; + final ValueChanged? onSelected; + + const CustomerAutocomplete({ + super.key, + required this.controller, + this.selectedCustomer, + this.onSelected, + }); + + @override + State createState() => _CustomerAutocompleteState(); +} + +class _CustomerAutocompleteState extends State { + final FocusNode _focusNode = FocusNode(); + @override + void initState() { + super.initState(); + context.read().add(CustomerLoaderEvent.getCustomer()); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Pelanggan', + style: TextStyle( + color: AppColors.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SpaceHeight(6.0), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const SizedBox.shrink(), + loading: () => const Center(child: CircularProgressIndicator()), + loaded: (customers, totalCustomer) => RawAutocomplete( + textEditingController: widget.controller, + focusNode: _focusNode, + optionsBuilder: (TextEditingValue textEditingValue) { + if (textEditingValue.text.isEmpty) { + return const Iterable.empty(); + } + return customers.where((Customer customer) { + return customer.name?.toLowerCase().contains( + textEditingValue.text.toLowerCase(), + ) ?? + false; + }); + }, + displayStringForOption: (Customer option) => option.name ?? "", + onSelected: (Customer selection) { + widget.controller.text = selection.name ?? ""; + if (widget.onSelected != null) { + widget.onSelected!(selection); + } + }, + fieldViewBuilder: + (context, controller, focusNode, onFieldSubmitted) { + return TextFormField( + controller: controller, + focusNode: focusNode, + decoration: const InputDecoration( + hintText: 'Nama Pelanggan', + ), + onChanged: (value) { + if (widget.onSelected != null) { + widget.onSelected!(null); // reset jika ketik manual + } + }, + ); + }, + optionsViewBuilder: (context, onSelected, options) { + return Material( + elevation: 1, + borderRadius: BorderRadius.circular(8), + color: Colors.white, + child: SizedBox( + height: context.deviceHeight * 0.4, + child: ListView.separated( + padding: EdgeInsets.zero, + itemCount: options.length, + separatorBuilder: (_, __) => const Divider(height: 1), + itemBuilder: (BuildContext context, int index) { + final Customer option = options.elementAt(index); + return ListTile( + title: Text(option.name ?? ""), + onTap: () { + onSelected(option); + }, + ); + }, + ), + ), + ); + }, + ), + ); + }, + ), + ], + ); + } +} diff --git a/lib/presentation/home/widgets/home_right_title.dart b/lib/presentation/home/widgets/home_right_title.dart new file mode 100644 index 0000000..3d1ba60 --- /dev/null +++ b/lib/presentation/home/widgets/home_right_title.dart @@ -0,0 +1,189 @@ +import 'package:enaklo_pos/core/components/buttons.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/string_ext.dart'; +import 'package:enaklo_pos/data/models/response/table_model.dart'; +import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart'; +import 'package:enaklo_pos/presentation/home/dialog/delivery_dialog.dart'; +import 'package:enaklo_pos/presentation/home/dialog/type_dialog.dart'; +import 'package:enaklo_pos/presentation/home/models/order_type.dart'; +import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart'; +import 'package:enaklo_pos/presentation/sales/pages/sales_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class HomeRightTitle extends StatelessWidget { + final TableModel? table; + const HomeRightTitle({super.key, this.table}); + + @override + Widget build(BuildContext context) { + return Container( + height: context.deviceHeight * 0.12, + decoration: BoxDecoration( + color: AppColors.primary, + border: Border( + left: BorderSide( + color: Colors.white, + width: 1.0, + ), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Row( + children: [ + Expanded( + child: Button.filled( + width: 180.0, + height: 40, + elevation: 0, + onPressed: () => context.push(SalesPage( + status: 'pending', + )), + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + icon: Icon( + Icons.list, + color: Colors.white, + size: 24, + ), + label: 'Daftar Pesanan', + ), + ), + ], + ), + ), + Expanded( + child: Row( + children: [ + Expanded( + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const SizedBox.shrink(), + loaded: ( + items, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { + return Button.filled( + width: 180.0, + height: 40, + elevation: 0, + onPressed: () { + showDialog( + context: context, + builder: (context) { + return TypeDialog(); + }); + }, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + icon: Icon( + Icons.dinner_dining_outlined, + color: Colors.white, + size: 24, + ), + label: orderType.value.toTitleCase(), + ); + }, + ); + }, + ), + ), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const SizedBox.shrink(), + loaded: ( + items, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { + switch (orderType) { + case OrderType.dineIn: + return Expanded( + child: Button.filled( + width: 180.0, + height: 40, + elevation: 0, + icon: Icon( + Icons.table_restaurant_outlined, + color: Colors.white, + size: 24, + ), + onPressed: () { + if (table == null) { + context.push(DashboardPage( + index: 1, + )); + } + }, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + label: table == null + ? 'Pilih Meja' + : '${table!.tableName}', + ), + ); + case OrderType.takeAway: + return const SizedBox.shrink(); + case OrderType.delivery: + return Expanded( + child: Button.filled( + width: 180.0, + height: 40, + elevation: 0, + icon: Icon( + Icons.motorcycle_outlined, + color: Colors.white, + size: 24, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) { + return DeliveryDialog(); + }); + }, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + label: deliveryType == null + ? 'Pilih Pengiriman' + : deliveryType.name, + ), + ); + default: + return const SizedBox.shrink(); + } + }, + ); + }, + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/home/widgets/home_title.dart b/lib/presentation/home/widgets/home_title.dart index 17636cc..2d1b876 100644 --- a/lib/presentation/home/widgets/home_title.dart +++ b/lib/presentation/home/widgets/home_title.dart @@ -1,5 +1,9 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/presentation/home/bloc/current_outlet/current_outlet_bloc.dart'; +import 'package:enaklo_pos/presentation/home/dialog/outlet_dialog.dart'; import 'package:flutter/material.dart'; -import 'package:enaklo_pos/core/extensions/date_time_ext.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../core/components/search_input.dart'; import '../../../core/constants/colors.dart'; @@ -16,39 +20,68 @@ class HomeTitle extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Enaklo POS', - style: TextStyle( - color: AppColors.primary, - fontSize: 22, - fontWeight: FontWeight.w600, - ), + return Container( + height: context.deviceHeight * 0.1, + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + decoration: BoxDecoration( + color: AppColors.white, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () => showDialog( + context: context, + builder: (context) => OutletDialog(), ), - const SizedBox(height: 4.0), - Text( - DateTime.now().toFormattedDate(), - style: const TextStyle( - color: AppColors.subtitle, - fontSize: 16, - ), + child: BlocBuilder( + builder: (context, state) { + return Row( + children: [ + state.maybeWhen( + orElse: () => const Text( + 'DEFAULT OUTLET', + style: TextStyle( + color: AppColors.primary, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + loading: () => Center( + child: SizedBox( + height: 16, + width: 16, + child: CircularProgressIndicator( + color: AppColors.primary, + ), + ), + ), + loaded: (outlet) => Text( + outlet.name ?? 'DEFAULT OUTLET', + style: TextStyle( + color: AppColors.primary, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + ), + SpaceWidth(2), + Icon(Icons.keyboard_arrow_down, color: AppColors.primary), + ], + ); + }, ), - ], - ), - SizedBox( - width: 300.0, - child: SearchInput( - controller: controller, - onChanged: onChanged, - hintText: 'Search..', ), - ), - ], + SizedBox( + width: context.deviceWidth * 0.2, + child: SearchInput( + controller: controller, + onChanged: onChanged, + hintText: 'Search..', + ), + ), + ], + ), ); } } diff --git a/lib/presentation/home/widgets/item_notes_dialog.dart b/lib/presentation/home/widgets/item_notes_dialog.dart index 2384997..319b971 100644 --- a/lib/presentation/home/widgets/item_notes_dialog.dart +++ b/lib/presentation/home/widgets/item_notes_dialog.dart @@ -1,16 +1,15 @@ +import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart'; import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; -import 'package:enaklo_pos/data/models/response/product_response_model.dart'; import '../../../core/components/buttons.dart'; import '../../../core/components/spaces.dart'; -import '../../../core/constants/colors.dart'; class ItemNotesDialog extends StatefulWidget { final ProductQuantity item; - + const ItemNotesDialog({ super.key, required this.item, @@ -38,24 +37,52 @@ class _ItemNotesDialogState extends State { @override Widget build(BuildContext context) { return AlertDialog( - title: const Text('Add Notes'), + backgroundColor: Colors.white, + title: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.item.product.name ?? 'Catatan Item', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.black, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4.0), + Text( + 'Masukkan catatan untuk item ini', + style: TextStyle( + fontSize: 14, + color: Colors.grey, + ), + ), + ], + ), + ), + SpaceWidth(12), + IconButton( + icon: const Icon(Icons.close, color: AppColors.black), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - widget.item.product.name ?? 'Product', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - const SpaceHeight(16.0), TextField( controller: notesController, maxLines: 3, decoration: const InputDecoration( - hintText: 'Enter notes for this item...', + hintText: 'Masukkan catatan untuk item ini', border: OutlineInputBorder(), ), ), @@ -69,11 +96,11 @@ class _ItemNotesDialogState extends State { Button.filled( onPressed: () { context.read().add( - CheckoutEvent.updateItemNotes( - widget.item.product, - notesController.text, - ), - ); + CheckoutEvent.updateItemNotes( + widget.item.product, + notesController.text, + ), + ); Navigator.of(context).pop(); }, label: 'Save', @@ -81,4 +108,4 @@ class _ItemNotesDialogState extends State { ], ); } -} \ No newline at end of file +} diff --git a/lib/presentation/home/widgets/order_menu.dart b/lib/presentation/home/widgets/order_menu.dart index 667675f..be91269 100644 --- a/lib/presentation/home/widgets/order_menu.dart +++ b/lib/presentation/home/widgets/order_menu.dart @@ -3,18 +3,44 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/core/constants/variables.dart'; import 'package:enaklo_pos/core/extensions/int_ext.dart'; -import 'package:enaklo_pos/core/extensions/string_ext.dart'; import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart'; import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; -import 'package:enaklo_pos/presentation/home/widgets/item_notes_dialog.dart'; import '../../../core/components/spaces.dart'; import '../../../core/constants/colors.dart'; -class OrderMenu extends StatelessWidget { +class OrderMenu extends StatefulWidget { final ProductQuantity data; const OrderMenu({super.key, required this.data}); + @override + State createState() => _OrderMenuState(); +} + +class _OrderMenuState extends State { + final _controller = TextEditingController(); + + @override + void initState() { + super.initState(); + _controller.text = widget.data.notes; + + _controller.addListener(() { + context.read().add( + CheckoutEvent.updateItemNotes( + widget.data.product, + _controller.text, + ), + ); + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Column( @@ -25,7 +51,7 @@ class OrderMenu extends StatelessWidget { child: ListTile( contentPadding: EdgeInsets.zero, leading: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(50.0)), + borderRadius: BorderRadius.all(Radius.circular(8.0)), child: // Icon( // Icons.fastfood, @@ -33,65 +59,47 @@ class OrderMenu extends StatelessWidget { // color: AppColors.primary, // ), CachedNetworkImage( - imageUrl: data.product.image!.contains('http') - ? data.product.image! - : '${Variables.baseUrl}/${data.product.image}', + imageUrl: (widget.data.product.imageUrl ?? '') + .contains('http') + ? widget.data.product.imageUrl! + : '${Variables.baseUrl}/${widget.data.product.imageUrl}', width: 50.0, height: 50.0, fit: BoxFit.cover, + errorWidget: (context, url, error) => Container( + width: 50.0, + height: 50.0, + decoration: BoxDecoration( + color: AppColors.disabled.withOpacity(0.4), + ), + child: const Icon( + Icons.image, + color: AppColors.grey, + ), + ), ), ), title: Row( children: [ Expanded( - child: Text(data.product.name ?? "-", - maxLines: 2, + child: Text(widget.data.product.name ?? "-", + maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, )), ), - GestureDetector( - onTap: () { - showDialog( - context: context, - builder: (context) => ItemNotesDialog(item: data), - ); - }, - child: const Icon( - Icons.edit_note, - size: 20, - color: AppColors.primary, - ), - ), ], ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(data.product.price!.toIntegerFromText.currencyFormatRp), - if (data.notes.isNotEmpty) ...[ - const SpaceHeight(4.0), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 4.0, - ), - decoration: BoxDecoration( - color: AppColors.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(4.0), - ), - child: Text( - 'Notes: ${data.notes}', - style: const TextStyle( - fontSize: 12, - color: AppColors.primary, - fontStyle: FontStyle.italic, - ), - ), - ), - ], + Text((widget.data.product.price! + + (widget.data.variant?.priceModifier ?? 0)) + .currencyFormatRp), + if (widget.data.variant != null) + Text(widget.data.variant?.name ?? ""), ], ), ), @@ -100,9 +108,8 @@ class OrderMenu extends StatelessWidget { children: [ GestureDetector( onTap: () { - context - .read() - .add(CheckoutEvent.removeItem(data.product)); + context.read().add(CheckoutEvent.removeItem( + widget.data.product, widget.data.variant)); }, child: Container( width: 30, @@ -118,14 +125,17 @@ class OrderMenu extends StatelessWidget { width: 30.0, child: Center( child: Text( - data.quantity.toString(), + widget.data.quantity.toString(), )), ), GestureDetector( onTap: () { - context - .read() - .add(CheckoutEvent.addItem(data.product)); + context.read().add( + CheckoutEvent.addItem( + widget.data.product, + widget.data.variant, + ), + ); }, child: Container( width: 30, @@ -143,7 +153,8 @@ class OrderMenu extends StatelessWidget { SizedBox( width: 80.0, child: Text( - (data.product.price!.toIntegerFromText * data.quantity) + (widget.data.product.price! * widget.data.quantity + + (widget.data.variant?.priceModifier ?? 0)) .currencyFormatRp, textAlign: TextAlign.right, style: const TextStyle( @@ -154,6 +165,50 @@ class OrderMenu extends StatelessWidget { ), ], ), + SpaceHeight(8.0), + SizedBox( + height: 40, + child: Row( + children: [ + Flexible( + child: TextFormField( + cursorColor: AppColors.primary, + controller: _controller, + style: const TextStyle( + fontSize: 12, + color: AppColors.black, + ), + decoration: InputDecoration( + hintText: 'Catatan Pesanan', + ), + ), + ), + const SpaceWidth(16.0), + GestureDetector( + onTap: () { + context.read().add( + CheckoutEvent.deleteItem( + widget.data.product, + widget.data.variant, + ), + ); + }, + child: Container( + height: 40, + width: 40, + decoration: BoxDecoration( + color: AppColors.primary, + borderRadius: BorderRadius.circular(8.0), + ), + child: Icon( + Icons.delete_outline, + color: AppColors.white, + ), + ), + ), + ], + ), + ) ], ); } diff --git a/lib/presentation/home/widgets/order_type_selector.dart b/lib/presentation/home/widgets/order_type_selector.dart index 01eb355..57d0414 100644 --- a/lib/presentation/home/widgets/order_type_selector.dart +++ b/lib/presentation/home/widgets/order_type_selector.dart @@ -15,7 +15,19 @@ class OrderTypeSelector extends StatelessWidget { builder: (context, state) { return state.maybeWhen( orElse: () => const SizedBox.shrink(), - loaded: (items, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) { + loaded: ( + items, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -36,8 +48,8 @@ class OrderTypeSelector extends StatelessWidget { return GestureDetector( onTap: () { context.read().add( - CheckoutEvent.updateOrderType(type), - ); + CheckoutEvent.updateOrderType(type), + ); }, child: Container( padding: const EdgeInsets.symmetric( @@ -45,9 +57,11 @@ class OrderTypeSelector extends StatelessWidget { vertical: 8.0, ), decoration: BoxDecoration( - color: isSelected ? AppColors.primary : AppColors.white, + color: + isSelected ? AppColors.primary : AppColors.white, border: Border.all( - color: isSelected ? AppColors.primary : AppColors.grey, + color: + isSelected ? AppColors.primary : AppColors.grey, width: 1.0, ), borderRadius: BorderRadius.circular(8.0), @@ -55,9 +69,12 @@ class OrderTypeSelector extends StatelessWidget { child: Text( type.value, style: TextStyle( - color: isSelected ? AppColors.white : AppColors.black, + color: + isSelected ? AppColors.white : AppColors.black, fontSize: 14, - fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, + fontWeight: isSelected + ? FontWeight.w600 + : FontWeight.normal, ), ), ), @@ -71,4 +88,4 @@ class OrderTypeSelector extends StatelessWidget { }, ); } -} \ No newline at end of file +} diff --git a/lib/presentation/home/widgets/outlet_card.dart b/lib/presentation/home/widgets/outlet_card.dart new file mode 100644 index 0000000..8bd44a9 --- /dev/null +++ b/lib/presentation/home/widgets/outlet_card.dart @@ -0,0 +1,55 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/presentation/home/models/outlet_model.dart'; +import 'package:flutter/material.dart'; + +class OutletCard extends StatelessWidget { + final Outlet outlet; + final bool isSelected; + const OutletCard({super.key, required this.outlet, required this.isSelected}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 12.0, + ), + margin: EdgeInsets.only(bottom: 12), + decoration: BoxDecoration( + color: + isSelected ? AppColors.primary.withOpacity(0.1) : AppColors.white, + border: Border.all(color: AppColors.primary), + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + ), + child: Row( + children: [ + Icon(Icons.store, color: AppColors.primary), + SpaceWidth(12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + outlet.name ?? "", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Text( + outlet.address ?? "", + style: const TextStyle( + fontSize: 12, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/home/widgets/product_card.dart b/lib/presentation/home/widgets/product_card.dart index 0477d13..c3239e2 100644 --- a/lib/presentation/home/widgets/product_card.dart +++ b/lib/presentation/home/widgets/product_card.dart @@ -1,14 +1,12 @@ import 'package:cached_network_image/cached_network_image.dart'; +import 'package:enaklo_pos/core/extensions/int_ext.dart'; +import 'package:enaklo_pos/presentation/home/dialog/variant_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/core/constants/variables.dart'; -import 'package:enaklo_pos/core/extensions/int_ext.dart'; -import 'package:enaklo_pos/core/extensions/string_ext.dart'; import 'package:enaklo_pos/data/models/response/product_response_model.dart'; import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart'; -import '../../../core/assets/assets.gen.dart'; -import '../../../core/components/spaces.dart'; import '../../../core/constants/colors.dart'; class ProductCard extends StatelessWidget { @@ -25,162 +23,154 @@ class ProductCard extends StatelessWidget { Widget build(BuildContext context) { return GestureDetector( onTap: () { - context.read().add(CheckoutEvent.addItem(data)); + if (data.isActive == true) { + if (data.variants!.isEmpty) { + context.read().add( + CheckoutEvent.addItem(data, null), + ); + } else { + showDialog( + context: context, + builder: (context) => VariantDialog(product: data), + ); + } + } }, child: Container( - padding: const EdgeInsets.all(16.0), - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - side: const BorderSide(width: 1, color: AppColors.card), - borderRadius: BorderRadius.circular(16), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(8.0), + border: Border.all( + color: AppColors.disabled, ), ), child: Stack( children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SpaceHeight(8), - Container( - alignment: Alignment.center, - padding: const EdgeInsets.all(12.0), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AppColors.disabled.withOpacity(0.4), - ), - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(40.0)), - child: - // Icon( - // Icons.fastfood, - // size: 40, - // color: AppColors.primary, - // ), - CachedNetworkImage( - imageUrl: data.image!.contains('http') - ? data.image! - : '${Variables.baseUrl}/${data.image}', - width: 60, - height: 60, + Padding( + padding: const EdgeInsets.all(4.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + child: CachedNetworkImage( + imageUrl: (data.imageUrl ?? "").contains('http') + ? data.imageUrl! + : '${Variables.baseUrl}/${data.imageUrl}', fit: BoxFit.cover, - ), - ), - ), - const Spacer(), - Text( - "${data.name}", - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w700, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Text( - data.category?.name ?? '-', - style: const TextStyle( + width: double.infinity, + height: 120, + errorWidget: (context, url, error) => Container( + width: double.infinity, + height: 120, + decoration: BoxDecoration( + color: AppColors.disabled.withOpacity(0.4), + ), + child: const Icon( + Icons.image, color: AppColors.grey, - fontSize: 12, ), ), ), - Flexible( - child: Text( - data.price!.toIntegerFromText.currencyFormatRp, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 13, - ), + ), + const Spacer(), + Text( + "${data.name}", + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w700, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const Spacer(), + Align( + alignment: Alignment.center, + child: Text( + '-', + style: const TextStyle( + fontSize: 12, + color: AppColors.grey, + fontWeight: FontWeight.w500, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + const Spacer(), + Align( + alignment: Alignment.center, + child: Text( + data.price!.currencyFormatRp, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 12, ), ), - ], - ), - const Spacer(), - ], + ), + const Spacer(), + ], + ), ), BlocBuilder( builder: (context, state) { return state.maybeWhen( orElse: () => const SizedBox(), - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) { - return products.any((element) => element.product == data) - ? products - .firstWhere( - (element) => element.product == data) - .quantity > - 0 - ? Align( - alignment: Alignment.topRight, - child: Container( - width: 40, - height: 40, - padding: const EdgeInsets.all(6), - decoration: const BoxDecoration( - borderRadius: - BorderRadius.all(Radius.circular(9.0)), - color: AppColors.primary, - ), - child: Center( - child: Text( - products - .firstWhere((element) => - element.product == data) - .quantity - .toString(), - style: const TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.bold), - ), - ), - ), - ) - : Align( - alignment: Alignment.topRight, - child: Container( - width: 36, - height: 36, - padding: const EdgeInsets.all(6), - decoration: const BoxDecoration( - borderRadius: - BorderRadius.all(Radius.circular(9.0)), - color: AppColors.primary, - ), - child: Assets.icons.shoppingBasket.svg(), - ), - ) - : Align( - alignment: Alignment.topRight, - child: Container( - width: 36, - height: 36, - padding: const EdgeInsets.all(6), - decoration: const BoxDecoration( - borderRadius: - BorderRadius.all(Radius.circular(9.0)), - color: AppColors.primary, - ), - child: Assets.icons.shoppingBasket.svg(), - ), - ); + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { + final totalQuantity = products + .where((item) => item.product.id == data.id) + .map((item) => item.quantity) + .fold(0, (sum, qty) => sum + qty); + + if (totalQuantity == 0) { + return const SizedBox.shrink(); + } + + return Align( + alignment: Alignment.topRight, + child: Container( + width: 40, + height: 40, + padding: const EdgeInsets.all(6), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(9.0)), + color: AppColors.primary, + ), + child: Center( + child: Text( + totalQuantity.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.bold), + ), + ), + ), + ); }, ); }, ), + if (data.isActive == false) + Container( + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + color: AppColors.grey.withOpacity(0.5), + ), + ), ], ), ), diff --git a/lib/presentation/home/widgets/save_order_dialog.dart b/lib/presentation/home/widgets/save_order_dialog.dart index e164aae..6e6179e 100644 --- a/lib/presentation/home/widgets/save_order_dialog.dart +++ b/lib/presentation/home/widgets/save_order_dialog.dart @@ -2,15 +2,9 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_esc_pos_network/flutter_esc_pos_network.dart'; import 'package:enaklo_pos/core/extensions/string_ext.dart'; -import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; -import 'package:intl/intl.dart'; -import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart'; - import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; -import 'package:enaklo_pos/core/extensions/int_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'; @@ -20,7 +14,6 @@ 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'; import '../models/order_type.dart'; import 'package:enaklo_pos/core/utils/printer_service.dart'; @@ -97,98 +90,111 @@ class _SaveOrderDialogState extends State { builder: (context, state) { final orderType = state.maybeWhen( orElse: () => OrderType.dineIn, - loaded: (items, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => orderType, + 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')), - ); - } - } + final checkerPrinter = await ProductLocalDatasource + .instance + .getPrinterByCode('checker'); + final kitchenPrinter = await ProductLocalDatasource + .instance + .getPrinterByCode('kitchen'); + final barPrinter = await ProductLocalDatasource + .instance + .getPrinterByCode('bar'); - // 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')), - ); - } - } + log("Checker printer: ${checkerPrinter?.toMap()}"); + log("Kitchen printer: ${kitchenPrinter?.toMap()}"); + log("Bar printer: ${barPrinter?.toMap()}"); - // 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')), - ); - } - } + // 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', ); diff --git a/lib/presentation/home/widgets/success_payment_dialog.dart b/lib/presentation/home/widgets/success_payment_dialog.dart index eaf331a..cdfac45 100644 --- a/lib/presentation/home/widgets/success_payment_dialog.dart +++ b/lib/presentation/home/widgets/success_payment_dialog.dart @@ -3,16 +3,12 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_esc_pos_network/flutter_esc_pos_network.dart'; import 'package:intl/intl.dart'; -import 'package:print_bluetooth_thermal/print_bluetooth_thermal.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/laman_print.dart'; import 'package:enaklo_pos/data/dataoutputs/print_dataoutputs.dart'; -import 'package:enaklo_pos/data/datasources/auth_local_datasource.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'; @@ -101,7 +97,7 @@ class _SuccessPaymentDialogState extends State { const SpaceHeight(5.0), BlocBuilder( builder: (context, state) { - final total = state.maybeWhen( + state.maybeWhen( orElse: () => 0, loaded: (model, orderId) => model.total, ); @@ -201,118 +197,140 @@ class _SuccessPaymentDialogState extends State { ); final kembalian = paymentAmount - widget.totalPrice; - return BlocBuilder( + return BlocBuilder( builder: (context, checkoutState) { final orderType = checkoutState.maybeWhen( orElse: () => OrderType.dineIn, - loaded: (items, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => orderType, + 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'); + 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')), - ); - } - } + // Receipt Printer + if (receiptPrinter != null) { + try { + final settingsLocalDatasource = + SettingsLocalDatasource(); + final taxModel = + await settingsLocalDatasource.getTax(); + final serviceChargeValue = + await settingsLocalDatasource + .getServiceCharge(); - // 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')), - ); - } - } + // Get the actual payment method from OrderBloc + final paymentMethod = state.maybeWhen( + orElse: () => 'Cash', + loaded: (model, orderId) => + model.paymentMethod, + ); - // 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')), - ); - } - } + 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', ); diff --git a/lib/presentation/payment/pages/payment_page.dart b/lib/presentation/payment/pages/payment_page.dart new file mode 100644 index 0000000..4c99448 --- /dev/null +++ b/lib/presentation/payment/pages/payment_page.dart @@ -0,0 +1,484 @@ +import 'package:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/components/flushbar.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/int_ext.dart'; +import 'package:enaklo_pos/core/extensions/string_ext.dart'; +import 'package:enaklo_pos/data/models/request/payment_request.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:enaklo_pos/data/models/response/payment_methods_response_model.dart'; +import 'package:enaklo_pos/data/models/response/product_response_model.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/sales/blocs/payment_form/payment_form_bloc.dart'; +import 'package:enaklo_pos/presentation/success/pages/success_payment_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class PaymentPage extends StatefulWidget { + final Order order; + final bool isSplit; + final String? splitType; + final String? customerId; + const PaymentPage({ + super.key, + required this.order, + this.isSplit = false, + this.splitType, + this.customerId, + }); + + @override + State createState() => _PaymentPageState(); +} + +class _PaymentPageState extends State { + PaymentMethod? selectedPaymentMethod; + final totalPriceController = TextEditingController(); + int priceValue = 0; + late int uangPas; + final int uangPas2 = 50000; + final int uangPas3 = 100000; + + List getOrderItemPending() { + final itemPending = widget.order.orderItems + ?.where((item) => item.status == "pending") + .toList(); + return itemPending ?? []; + } + + @override + void initState() { + super.initState(); + uangPas = widget.order.totalAmount ?? 0; + totalPriceController.text = uangPas.currencyFormatRpV2; + priceValue = uangPas; + + context + .read() + .add(PaymentMethodsEvent.fetchPaymentMethods()); + } + + @override + void dispose() { + totalPriceController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final totalAmount = widget.order.totalAmount ?? 0; + + return Scaffold( + backgroundColor: AppColors.background, + appBar: AppBar( + backgroundColor: AppColors.primary, + title: const Text( + 'Pembayaran', + style: TextStyle(color: AppColors.white), + ), + leading: IconButton( + onPressed: () => context.pop(), + icon: Icon( + Icons.arrow_back, + color: AppColors.white, + ), + ), + ), + body: LayoutBuilder( + builder: (context, constraints) { + final isWide = constraints.maxWidth > 800; + return Padding( + padding: const EdgeInsets.all(16), + child: isWide + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(flex: 3, child: _buildOrderDetail()), + const SizedBox(width: 24), + Expanded(flex: 2, child: _buildPaymentForm(totalAmount)), + ], + ) + : SingleChildScrollView( + child: Column( + children: [ + _buildOrderDetail(), + const SizedBox(height: 24), + _buildPaymentForm(totalAmount), + ], + ), + ), + ); + }, + ), + ); + } + + Widget _buildOrderDetail() { + final order = widget.order; + final items = order.orderItems ?? []; + + return Card( + elevation: 0, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + color: AppColors.white, + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Detail Order', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColors.primary)), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('No. Pesanan', + style: TextStyle(fontWeight: FontWeight.w600)), + Text(order.orderNumber ?? '-', + style: const TextStyle(fontWeight: FontWeight.bold)), + ], + ), + const SizedBox(height: 8), + Divider(color: AppColors.grey), + const SizedBox(height: 8), + Expanded( + child: items.isEmpty + ? const Center(child: Text('Tidak ada item')) + : ListView.separated( + shrinkWrap: true, + itemCount: getOrderItemPending().length, + separatorBuilder: (_, __) => + Divider(color: AppColors.grey.withOpacity(0.5)), + itemBuilder: (context, index) { + final item = getOrderItemPending()[index]; + return ListTile( + contentPadding: EdgeInsets.zero, + title: Text(item.productName ?? '-'), + subtitle: Text( + 'Qty: ${item.quantity ?? 0} | ${item.productVariantName ?? ''}'), + trailing: Text( + (item.totalPrice ?? 0).currencyFormatRpV2, + style: + const TextStyle(fontWeight: FontWeight.bold)), + ); + }, + ), + ), + Divider(color: AppColors.grey), + _buildSummaryRow('Subtotal', order.subtotal ?? 0), + _buildSummaryRow('Pajak', order.taxAmount ?? 0), + _buildSummaryRow('Diskon', order.discountAmount ?? 0), + Divider(thickness: 2, color: AppColors.primary), + _buildSummaryRow('Total', order.totalAmount ?? 0, + isTotal: true, color: AppColors.primary), + ], + ), + ), + ); + } + + Widget _buildSummaryRow(String label, int amount, + {bool isTotal = false, Color? color}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label, + style: TextStyle( + fontWeight: isTotal ? FontWeight.bold : FontWeight.normal, + fontSize: isTotal ? 18 : 14, + color: color ?? Colors.black)), + Text(amount.currencyFormatRpV2, + style: TextStyle( + fontWeight: isTotal ? FontWeight.bold : FontWeight.normal, + fontSize: isTotal ? 18 : 14, + color: color ?? Colors.black)), + ], + ), + ); + } + + Widget _buildPaymentForm(int totalAmount) { + return Card( + elevation: 0, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + color: AppColors.white, + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('Pembayaran', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColors.primary)), + Expanded( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 24), + const Text('Metode Pembayaran', + style: TextStyle(fontWeight: FontWeight.w600)), + const SizedBox(height: 12), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => + const Center(child: CircularProgressIndicator()), + loading: () => const Center( + child: Column( + children: [ + CircularProgressIndicator(), + SizedBox(height: 8), + Text('Loading metode pembayaran...'), + ], + ), + ), + error: (message) => Column( + children: [ + Text('Gagal memuat metode pembayaran: $message'), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () { + context.read().add( + PaymentMethodsEvent + .fetchPaymentMethods()); + }, + child: const Text('Coba Lagi'), + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + ), + ), + ], + ), + loaded: (methods) { + if (methods.isEmpty) { + return Column( + children: [ + const Text( + 'Tidak ada metode pembayaran tersedia'), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () { + context.read().add( + PaymentMethodsEvent + .fetchPaymentMethods()); + }, + child: const Text('Coba Lagi'), + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + ), + ), + ], + ); + } + return Wrap( + spacing: 12, + runSpacing: 12, + children: methods.map((method) { + final isSelected = + selectedPaymentMethod?.id == method.id; + return ChoiceChip( + label: Text(method.name ?? ''), + selected: isSelected, + onSelected: (_) { + setState(() { + selectedPaymentMethod = method; + if (method.type != "cash") { + totalPriceController.text = + totalAmount.currencyFormatRpV2; + priceValue = totalAmount; + } + }); + }, + selectedColor: AppColors.primary, + backgroundColor: AppColors.white, + labelStyle: TextStyle( + color: isSelected + ? AppColors.white + : AppColors.primary, + fontWeight: FontWeight.bold, + ), + shape: RoundedRectangleBorder( + side: BorderSide(color: AppColors.primary), + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 12), + ); + }).toList(), + ); + }, + ); + }, + ), + const SizedBox(height: 24), + if (selectedPaymentMethod?.type == "cash") ...[ + const Text('Total Bayar', + style: TextStyle(fontWeight: FontWeight.w600)), + const SizedBox(height: 8), + TextFormField( + controller: totalPriceController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8)), + hintText: 'Masukkan nominal bayar', + ), + onChanged: (value) { + final newValue = value.toIntegerFromText; + setState(() { + priceValue = newValue; + totalPriceController.text = + newValue.currencyFormatRp; + totalPriceController.selection = + TextSelection.fromPosition(TextPosition( + offset: totalPriceController.text.length)); + }); + }, + ), + const SizedBox(height: 20), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + _quickAmountButton('UANG PAS', uangPas), + const SizedBox(width: 16), + _quickAmountButton( + uangPas2.currencyFormatRpV2, uangPas2), + const SizedBox(width: 16), + _quickAmountButton( + uangPas3.currencyFormatRpV2, uangPas3), + ], + ), + ), + ], + const SizedBox(height: 32), + ], + ), + ), + ), + BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: (data) { + context.pushReplacement(SuccessPaymentPage( + productQuantity: widget.order.orderItems + ?.map( + (item) => ProductQuantity( + product: Product( + name: item.productName, + price: item.unitPrice, + ), + quantity: item.quantity ?? 0, + ), + ) + .toList() ?? + [], + payment: data, + paymentMethod: selectedPaymentMethod?.name ?? '', + nominalBayar: totalPriceController.text.toIntegerFromText, + )); + }, + error: (message) { + AppFlushbar.showError(context, message); + }, + ); + }, + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => + Button.filled(onPressed: _onPayPressed, label: "Bayar"), + loading: () => + const Center(child: CircularProgressIndicator()), + ); + }, + ), + ), + ], + ), + ), + ); + } + + Widget _quickAmountButton(String label, int amount) { + return OutlinedButton( + onPressed: () { + setState(() { + totalPriceController.text = amount.currencyFormatRpV2; + priceValue = amount; + }); + }, + style: OutlinedButton.styleFrom( + side: BorderSide(color: AppColors.primary), + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + ), + child: Text(label, + style: + TextStyle(color: AppColors.primary, fontWeight: FontWeight.bold)), + ); + } + + void _onPayPressed() { + if (selectedPaymentMethod == null) { + AppFlushbar.showError(context, 'Pilih metode pembayaran terlebih dahulu'); + return; + } + + if (selectedPaymentMethod?.type == "cash" && priceValue == 0) { + AppFlushbar.showError(context, 'Total bayar tidak boleh 0'); + return; + } + + final itemPending = widget.order.orderItems + ?.where((item) => item.status == "pending") + .toList(); + if (widget.isSplit == false) { + final request = PaymentRequestModel( + amount: widget.order.totalAmount ?? 0, + orderId: widget.order.id, + paymentMethodId: selectedPaymentMethod?.id, + splitDescription: '', + splitNumber: 1, + splitTotal: 1, + transactionId: '', + paymentOrderItems: itemPending + ?.map((item) => PaymentOrderItemModel( + orderItemId: item.id, + amount: item.totalPrice, + )) + .toList(), + ); + + context.read().add(PaymentFormEvent.create(request)); + } else { + final request = PaymentSplitBillRequest( + amount: widget.order.totalAmount ?? 0, + customerId: widget.customerId ?? "", + items: itemPending + ?.map((item) => SplitItem( + orderItemId: item.id ?? "", + quantity: item.quantity ?? 0, + )) + .toList() ?? + [], + orderId: widget.order.id ?? "", + paymentMethodId: selectedPaymentMethod?.id ?? "", + type: widget.splitType ?? "", + ); + context.read().add( + PaymentFormEvent.createSplitBill(request), + ); + } + } +} diff --git a/lib/presentation/refund/bloc/refund_bloc.dart b/lib/presentation/refund/bloc/refund_bloc.dart new file mode 100644 index 0000000..e16896d --- /dev/null +++ b/lib/presentation/refund/bloc/refund_bloc.dart @@ -0,0 +1,40 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'refund_event.dart'; +part 'refund_state.dart'; +part 'refund_bloc.freezed.dart'; + +class RefundBloc extends Bloc { + final OrderRemoteDatasource _orderRemoteDatasource; + + RefundBloc(this._orderRemoteDatasource) : super(const RefundState.initial()) { + on((event, emit) async { + await event.when( + refundPayment: (paymentId, reason, refundAmount) => + _onRefundPayment(paymentId, reason, refundAmount, emit), + ); + }); + } + + Future _onRefundPayment( + String orderId, + String reason, + int refundAmount, + Emitter emit, + ) async { + emit(const RefundState.loading()); + + final result = await _orderRemoteDatasource.refundPayment( + orderId: orderId, + reason: reason, + refundAmount: refundAmount, + ); + + result.fold( + (error) => emit(RefundState.error(error)), + (success) => emit(const RefundState.success()), + ); + } +} diff --git a/lib/presentation/refund/bloc/refund_bloc.freezed.dart b/lib/presentation/refund/bloc/refund_bloc.freezed.dart new file mode 100644 index 0000000..4799ad7 --- /dev/null +++ b/lib/presentation/refund/bloc/refund_bloc.freezed.dart @@ -0,0 +1,854 @@ +// 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 'refund_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$RefundEvent { + String get orderId => throw _privateConstructorUsedError; + String get reason => throw _privateConstructorUsedError; + int get refundAmount => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(String orderId, String reason, int refundAmount) + refundPayment, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String orderId, String reason, int refundAmount)? + refundPayment, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String orderId, String reason, int refundAmount)? + refundPayment, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_RefundPayment value) refundPayment, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_RefundPayment value)? refundPayment, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_RefundPayment value)? refundPayment, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Create a copy of RefundEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $RefundEventCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RefundEventCopyWith<$Res> { + factory $RefundEventCopyWith( + RefundEvent value, $Res Function(RefundEvent) then) = + _$RefundEventCopyWithImpl<$Res, RefundEvent>; + @useResult + $Res call({String orderId, String reason, int refundAmount}); +} + +/// @nodoc +class _$RefundEventCopyWithImpl<$Res, $Val extends RefundEvent> + implements $RefundEventCopyWith<$Res> { + _$RefundEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of RefundEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? orderId = null, + Object? reason = null, + Object? refundAmount = null, + }) { + return _then(_value.copyWith( + orderId: null == orderId + ? _value.orderId + : orderId // ignore: cast_nullable_to_non_nullable + as String, + reason: null == reason + ? _value.reason + : reason // ignore: cast_nullable_to_non_nullable + as String, + refundAmount: null == refundAmount + ? _value.refundAmount + : refundAmount // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$RefundPaymentImplCopyWith<$Res> + implements $RefundEventCopyWith<$Res> { + factory _$$RefundPaymentImplCopyWith( + _$RefundPaymentImpl value, $Res Function(_$RefundPaymentImpl) then) = + __$$RefundPaymentImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String orderId, String reason, int refundAmount}); +} + +/// @nodoc +class __$$RefundPaymentImplCopyWithImpl<$Res> + extends _$RefundEventCopyWithImpl<$Res, _$RefundPaymentImpl> + implements _$$RefundPaymentImplCopyWith<$Res> { + __$$RefundPaymentImplCopyWithImpl( + _$RefundPaymentImpl _value, $Res Function(_$RefundPaymentImpl) _then) + : super(_value, _then); + + /// Create a copy of RefundEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? orderId = null, + Object? reason = null, + Object? refundAmount = null, + }) { + return _then(_$RefundPaymentImpl( + orderId: null == orderId + ? _value.orderId + : orderId // ignore: cast_nullable_to_non_nullable + as String, + reason: null == reason + ? _value.reason + : reason // ignore: cast_nullable_to_non_nullable + as String, + refundAmount: null == refundAmount + ? _value.refundAmount + : refundAmount // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +class _$RefundPaymentImpl implements _RefundPayment { + const _$RefundPaymentImpl( + {required this.orderId, + required this.reason, + required this.refundAmount}); + + @override + final String orderId; + @override + final String reason; + @override + final int refundAmount; + + @override + String toString() { + return 'RefundEvent.refundPayment(orderId: $orderId, reason: $reason, refundAmount: $refundAmount)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RefundPaymentImpl && + (identical(other.orderId, orderId) || other.orderId == orderId) && + (identical(other.reason, reason) || other.reason == reason) && + (identical(other.refundAmount, refundAmount) || + other.refundAmount == refundAmount)); + } + + @override + int get hashCode => Object.hash(runtimeType, orderId, reason, refundAmount); + + /// Create a copy of RefundEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$RefundPaymentImplCopyWith<_$RefundPaymentImpl> get copyWith => + __$$RefundPaymentImplCopyWithImpl<_$RefundPaymentImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String orderId, String reason, int refundAmount) + refundPayment, + }) { + return refundPayment(orderId, reason, refundAmount); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String orderId, String reason, int refundAmount)? + refundPayment, + }) { + return refundPayment?.call(orderId, reason, refundAmount); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String orderId, String reason, int refundAmount)? + refundPayment, + required TResult orElse(), + }) { + if (refundPayment != null) { + return refundPayment(orderId, reason, refundAmount); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_RefundPayment value) refundPayment, + }) { + return refundPayment(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_RefundPayment value)? refundPayment, + }) { + return refundPayment?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_RefundPayment value)? refundPayment, + required TResult orElse(), + }) { + if (refundPayment != null) { + return refundPayment(this); + } + return orElse(); + } +} + +abstract class _RefundPayment implements RefundEvent { + const factory _RefundPayment( + {required final String orderId, + required final String reason, + required final int refundAmount}) = _$RefundPaymentImpl; + + @override + String get orderId; + @override + String get reason; + @override + int get refundAmount; + + /// Create a copy of RefundEvent + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RefundPaymentImplCopyWith<_$RefundPaymentImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$RefundState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RefundStateCopyWith<$Res> { + factory $RefundStateCopyWith( + RefundState value, $Res Function(RefundState) then) = + _$RefundStateCopyWithImpl<$Res, RefundState>; +} + +/// @nodoc +class _$RefundStateCopyWithImpl<$Res, $Val extends RefundState> + implements $RefundStateCopyWith<$Res> { + _$RefundStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of RefundState + /// 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 _$RefundStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of RefundState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'RefundState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements RefundState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$RefundStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of RefundState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'RefundState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class _Loading implements RefundState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$SuccessImplCopyWith<$Res> { + factory _$$SuccessImplCopyWith( + _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = + __$$SuccessImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SuccessImplCopyWithImpl<$Res> + extends _$RefundStateCopyWithImpl<$Res, _$SuccessImpl> + implements _$$SuccessImplCopyWith<$Res> { + __$$SuccessImplCopyWithImpl( + _$SuccessImpl _value, $Res Function(_$SuccessImpl) _then) + : super(_value, _then); + + /// Create a copy of RefundState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SuccessImpl implements _Success { + const _$SuccessImpl(); + + @override + String toString() { + return 'RefundState.success()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SuccessImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return success(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return success?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return success(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return success?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(this); + } + return orElse(); + } +} + +abstract class _Success implements RefundState { + const factory _Success() = _$SuccessImpl; +} + +/// @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 _$RefundStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of RefundState + /// 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 'RefundState.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 RefundState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements RefundState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of RefundState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/refund/bloc/refund_event.dart b/lib/presentation/refund/bloc/refund_event.dart new file mode 100644 index 0000000..364aee9 --- /dev/null +++ b/lib/presentation/refund/bloc/refund_event.dart @@ -0,0 +1,10 @@ +part of 'refund_bloc.dart'; + +@freezed +class RefundEvent with _$RefundEvent { + const factory RefundEvent.refundPayment({ + required String orderId, + required String reason, + required int refundAmount, + }) = _RefundPayment; +} diff --git a/lib/presentation/refund/bloc/refund_state.dart b/lib/presentation/refund/bloc/refund_state.dart new file mode 100644 index 0000000..5cb1be7 --- /dev/null +++ b/lib/presentation/refund/bloc/refund_state.dart @@ -0,0 +1,9 @@ +part of 'refund_bloc.dart'; + +@freezed +class RefundState with _$RefundState { + const factory RefundState.initial() = _Initial; + const factory RefundState.loading() = _Loading; + const factory RefundState.success() = _Success; + const factory RefundState.error(String message) = _Error; +} diff --git a/lib/presentation/refund/dialog/refund_error_dialog.dart b/lib/presentation/refund/dialog/refund_error_dialog.dart new file mode 100644 index 0000000..dc13519 --- /dev/null +++ b/lib/presentation/refund/dialog/refund_error_dialog.dart @@ -0,0 +1,90 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:flutter/material.dart'; + +class RefundErrorDialog extends StatelessWidget { + final String message; + const RefundErrorDialog({super.key, required this.message}); + + @override + Widget build(BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + child: Container( + padding: EdgeInsets.all(32), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + gradient: LinearGradient( + colors: [Colors.red[50]!, Colors.white], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.red.withOpacity(0.3), + blurRadius: 20, + offset: Offset(0, 10), + ), + ], + ), + child: Icon( + Icons.error, + color: Colors.white, + size: 40, + ), + ), + SpaceHeight(24), + Text( + 'Error!', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.red[600], + ), + ), + SpaceHeight(12), + Text( + message, + style: TextStyle( + color: Colors.grey[600], + fontSize: 16, + ), + textAlign: TextAlign.center, + ), + SpaceHeight(32), + Container( + width: double.infinity, + child: ElevatedButton( + onPressed: () => Navigator.pop(context), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red[600], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + padding: EdgeInsets.symmetric(vertical: 16), + ), + child: Text( + 'OK', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/refund/dialog/refund_success_dialog.dart b/lib/presentation/refund/dialog/refund_success_dialog.dart new file mode 100644 index 0000000..b426946 --- /dev/null +++ b/lib/presentation/refund/dialog/refund_success_dialog.dart @@ -0,0 +1,132 @@ +import 'package:enaklo_pos/core/components/spaces.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/int_ext.dart'; +import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart'; +import 'package:flutter/material.dart'; + +class RefundSuccessDialog extends StatelessWidget { + final int refundAmount; + final String selectedReason; + const RefundSuccessDialog( + {super.key, required this.refundAmount, required this.selectedReason}); + + @override + Widget build(BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + child: Container( + padding: EdgeInsets.all(32), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + gradient: LinearGradient( + colors: [Colors.green[50]!, Colors.white], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.green, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.green.withOpacity(0.3), + blurRadius: 20, + offset: Offset(0, 10), + ), + ], + ), + child: Icon( + Icons.check, + color: Colors.white, + size: 40, + ), + ), + SpaceHeight(24), + Text( + 'Refund Berhasil!', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + SpaceHeight(12), + Text( + 'Refund sebesar ${(refundAmount).currencyFormatRpV2} telah diproses.', + style: TextStyle( + color: Colors.grey[600], + fontSize: 16, + ), + textAlign: TextAlign.center, + ), + SpaceHeight(8), + Text( + 'Alasan: $selectedReason', + style: TextStyle( + color: Colors.grey[500], + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + SpaceHeight(32), + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: () { + Navigator.pop(context); + }, + style: OutlinedButton.styleFrom( + side: BorderSide(color: AppColors.primary), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + padding: EdgeInsets.symmetric(vertical: 16), + ), + child: Text( + 'Print Receipt', + style: TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + SizedBox(width: 16), + Expanded( + child: ElevatedButton( + onPressed: () { + context.push(DashboardPage()); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + padding: EdgeInsets.symmetric(vertical: 16), + ), + child: Text( + 'Selesai', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/refund/pages/refund_page.dart b/lib/presentation/refund/pages/refund_page.dart new file mode 100644 index 0000000..3c0144d --- /dev/null +++ b/lib/presentation/refund/pages/refund_page.dart @@ -0,0 +1,802 @@ +import 'package:enaklo_pos/core/components/spaces.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/int_ext.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:enaklo_pos/presentation/refund/bloc/refund_bloc.dart'; +import 'package:enaklo_pos/presentation/refund/dialog/refund_error_dialog.dart'; +import 'package:enaklo_pos/presentation/refund/dialog/refund_success_dialog.dart'; +import 'package:enaklo_pos/presentation/refund/widgets/refund_appbar.dart'; +import 'package:enaklo_pos/presentation/refund/widgets/refund_info_tile.dart'; +import 'package:enaklo_pos/presentation/refund/widgets/refund_order_Item_tile.dart'; +import 'package:enaklo_pos/presentation/refund/widgets/refund_reason_tile.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class RefundPage extends StatefulWidget { + final Order selectedOrder; + + const RefundPage({super.key, required this.selectedOrder}); + + @override + State createState() => _RefundPageState(); +} + +class _RefundPageState extends State with TickerProviderStateMixin { + final TextEditingController _reasonController = TextEditingController(); + final TextEditingController _refundAmountController = TextEditingController(); + final ScrollController _leftPanelScrollController = ScrollController(); + final ScrollController _rightPanelScrollController = ScrollController(); + final ScrollController _itemsScrollController = ScrollController(); + + String selectedReason = 'Barang Rusak'; + + late AnimationController _slideController; + late AnimationController _fadeController; + late AnimationController _scaleController; + late Animation _slideAnimation; + late Animation _fadeAnimation; + late Animation _scaleAnimation; + + final List> refundReasons = [ + { + 'value': 'Barang Rusak', + 'icon': Icons.broken_image, + 'color': AppColors.primary + }, + {'value': 'Salah Item', 'icon': Icons.swap_horiz, 'color': Colors.orange}, + { + 'value': 'Tidak Sesuai Pesanan', + 'icon': Icons.error_outline, + 'color': Colors.amber + }, + { + 'value': 'Permintaan Customer', + 'icon': Icons.person, + 'color': Colors.blue + }, + { + 'value': 'Kualitas Tidak Baik', + 'icon': Icons.thumb_down, + 'color': Colors.purple + }, + { + 'value': 'Lainnya', + 'icon': Icons.more_horiz, + 'color': Colors.red, + }, + ]; + + @override + void initState() { + super.initState(); + _initializeAnimations(); + _refundAmountController.text = + (widget.selectedOrder.totalAmount ?? 0).toString(); + } + + void _initializeAnimations() { + _slideController = AnimationController( + duration: Duration(milliseconds: 1200), + vsync: this, + ); + _fadeController = AnimationController( + duration: Duration(milliseconds: 800), + vsync: this, + ); + _scaleController = AnimationController( + duration: Duration(milliseconds: 600), + vsync: this, + ); + + _slideAnimation = Tween( + begin: Offset(0.0, 1.0), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: _slideController, + curve: Curves.elasticOut, + )); + + _fadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _fadeController, + curve: Curves.easeInOut, + )); + + _scaleAnimation = Tween( + begin: 0.8, + end: 1.0, + ).animate(CurvedAnimation( + parent: _scaleController, + curve: Curves.elasticOut, + )); + + _fadeController.forward(); + _slideController.forward(); + _scaleController.forward(); + } + + @override + void dispose() { + _slideController.dispose(); + _fadeController.dispose(); + _scaleController.dispose(); + _reasonController.dispose(); + _refundAmountController.dispose(); + _leftPanelScrollController.dispose(); + _rightPanelScrollController.dispose(); + _itemsScrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + state.when( + initial: () {}, + loading: () {}, + success: () { + _showSuccessDialog(); + }, + error: (message) { + _showErrorDialog(message); + }, + ); + }, + child: Scaffold( + backgroundColor: Color(0xFFF5F7FA), + body: FadeTransition( + opacity: _fadeAnimation, + child: Column( + children: [ + RefundAppbar( + order: widget.selectedOrder, + ), + Expanded( + child: Padding( + padding: EdgeInsets.all(24.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Left Panel - Order Summary (Scrollable) + Expanded( + flex: 3, + child: Scrollbar( + controller: _leftPanelScrollController, + thumbVisibility: true, + child: SingleChildScrollView( + controller: _leftPanelScrollController, + child: _buildOrderSummaryPanel(), + ), + ), + ), + + SizedBox(width: 24), + + // Right Panel - Refund Configuration (Scrollable) + Expanded( + flex: 4, + child: Scrollbar( + controller: _rightPanelScrollController, + thumbVisibility: true, + child: SingleChildScrollView( + controller: _rightPanelScrollController, + child: _buildRefundConfigPanel(context), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildOrderSummaryPanel() { + return ScaleTransition( + scale: _scaleAnimation, + child: Column( + children: [ + // Order Info Card + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + ), + child: Padding( + padding: EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + const Color.fromARGB(255, 96, 56, 148), + AppColors.primary + ], + ), + borderRadius: BorderRadius.circular(16), + ), + child: Icon( + Icons.receipt_long, + color: Colors.white, + size: 24, + ), + ), + SizedBox(width: 20), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Detail Pesanan', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + SpaceHeight(4), + Text( + (widget.selectedOrder.createdAt ?? DateTime.now()) + .toFormattedDate3(), + style: TextStyle( + color: Colors.grey[600], + fontSize: 14, + ), + ), + ], + ), + ), + ], + ), + + SpaceHeight(20), + + // Order Details Grid + Row( + children: [ + Expanded( + child: RefundInfoTile( + title: 'Meja', + value: widget.selectedOrder.tableNumber ?? 'Takeaway', + icon: Icons.table_restaurant, + color: Colors.blue, + ), + ), + SizedBox(width: 16), + Expanded( + child: RefundInfoTile( + title: 'Tipe', + value: widget.selectedOrder.orderType ?? 'N/A', + icon: Icons.shopping_bag_outlined, + color: Colors.purple, + ), + ), + ], + ), + + SpaceHeight(16), + + Row( + children: [ + Expanded( + child: RefundInfoTile( + title: 'Status', + value: widget.selectedOrder.status?.toUpperCase() ?? + 'N/A', + icon: Icons.check_circle_outline, + color: _getStatusColor(widget.selectedOrder.status), + ), + ), + SizedBox(width: 16), + Expanded( + child: RefundInfoTile( + title: 'Items', + value: + '${widget.selectedOrder.orderItems?.length ?? 0}', + icon: Icons.inventory_2_outlined, + color: Colors.orange, + ), + ), + ], + ), + ], + ), + ), + ), + + SpaceHeight(24), + + // Payment Summary Card + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + ), + child: Padding( + padding: EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + const Color.fromARGB(255, 96, 56, 148), + AppColors.primary + ], + ), + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.account_balance_wallet_outlined, + color: AppColors.white, + size: 24, + ), + ), + SizedBox(width: 16), + Text( + 'Ringkasan Pembayaran', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + SpaceHeight(24), + _buildPaymentRow( + 'Subtotal', widget.selectedOrder.subtotal ?? 0), + _buildPaymentRow( + 'Pajak', widget.selectedOrder.taxAmount ?? 0), + _buildPaymentRow( + 'Diskon', -(widget.selectedOrder.discountAmount ?? 0)), + Padding( + padding: EdgeInsets.symmetric(vertical: 16), + child: Divider(thickness: 2, color: Colors.grey[200]), + ), + _buildPaymentRow( + 'Total Dibayar', + widget.selectedOrder.totalAmount ?? 0, + isTotal: true, + ), + ], + ), + ), + ), + + SpaceHeight(24), // Extra space for scroll + ], + ), + ); + } + + Widget _buildRefundConfigPanel(BuildContext context) { + return SlideTransition( + position: _slideAnimation, + child: Column( + children: [ + // Refund Reason Card + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + ), + child: Padding( + padding: EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + const Color.fromARGB(255, 96, 56, 148), + AppColors.primary + ], + ), + borderRadius: BorderRadius.circular(16), + ), + child: Icon( + Icons.assignment_return_outlined, + color: Colors.white, + size: 24, + ), + ), + SizedBox(width: 20), + Text( + 'Konfigurasi Refund', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + + SpaceHeight(20), + + Text( + 'Pilih Alasan Refund', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.grey[700], + ), + ), + + // Reason Selection Grid + GridView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + childAspectRatio: 2.5, + ), + itemCount: refundReasons.length, + itemBuilder: (context, index) { + final reason = refundReasons[index]; + final isSelected = selectedReason == reason['value']; + + return RefundReasonTile( + isSelected: isSelected, + reason: reason, + onTap: () { + setState(() { + selectedReason = reason['value']; + }); + }, + ); + }, + ), + + if (selectedReason == 'Lainnya') ...[ + SpaceHeight(24), + TextField( + controller: _reasonController, + maxLines: 3, + decoration: InputDecoration( + hintText: 'Jelaskan alasan refund secara detail...', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide(color: Colors.grey[300]!), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: + BorderSide(color: AppColors.primary, width: 2), + ), + filled: true, + fillColor: Colors.grey[50], + contentPadding: EdgeInsets.all(20), + ), + ), + ], + + SpaceHeight(32), + + // Refund Amount Input + Text( + 'Jumlah Refund', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.grey[700], + ), + ), + + SpaceHeight(16), + + TextField( + controller: _refundAmountController, + keyboardType: TextInputType.number, + readOnly: true, + decoration: InputDecoration( + hintText: 'Masukkan jumlah refund', + prefixText: 'Rp ', + prefixStyle: TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide(color: Colors.grey[300]!), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: + BorderSide(color: AppColors.primary, width: 2), + ), + filled: true, + fillColor: Colors.grey[50], + contentPadding: EdgeInsets.all(20), + ), + ), + ], + ), + ), + ), + + SpaceHeight(24), + + // Items Display Card + Container( + height: 500, // Fixed height untuk items list + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 30, + offset: Offset(0, 15), + ), + ], + ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.all(20), + child: Row( + children: [ + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.blue.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.list_alt, + color: Colors.blue[700], + size: 24, + ), + ), + SizedBox(width: 16), + Expanded( + child: Text( + 'Item Pesanan', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ), + Container( + padding: + EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + '${widget.selectedOrder.orderItems?.length ?? 0} item', + style: TextStyle( + color: AppColors.primary, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ), + ], + ), + ), + + // Scrollable Items List + Expanded( + child: Scrollbar( + controller: _itemsScrollController, + thumbVisibility: true, + child: ListView.builder( + controller: _itemsScrollController, + padding: EdgeInsets.symmetric(horizontal: 32), + itemCount: widget.selectedOrder.orderItems?.length ?? 0, + itemBuilder: (context, index) { + final item = widget.selectedOrder.orderItems![index]; + return RefundOrderItemTile(item: item); + }, + ), + ), + ), + + // Process Refund Button + Container( + padding: EdgeInsets.all(32), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(24), + bottomRight: Radius.circular(24), + ), + ), + child: BlocBuilder( + builder: (context, state) { + final isLoading = state.maybeWhen( + loading: () => true, + orElse: () => false, + ); + + return SizedBox( + width: double.infinity, + height: 64, + child: ElevatedButton( + onPressed: + isLoading ? null : () => _processRefund(context), + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + elevation: 0, + shadowColor: Colors.red.withOpacity(0.3), + ), + child: isLoading + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, + ), + ), + SizedBox(width: 16), + Text( + 'Memproses Refund...', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.monetization_on, + color: Colors.white, size: 28), + SizedBox(width: 16), + Text( + 'PROSES REFUND', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + letterSpacing: 0.5, + ), + ), + ], + ), + ), + ); + }, + ), + ), + ], + ), + ), + + SpaceHeight(24), // Extra space for scroll + ], + ), + ); + } + + Widget _buildPaymentRow(String label, int amount, {bool isTotal = false}) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: TextStyle( + fontSize: isTotal ? 18 : 16, + fontWeight: isTotal ? FontWeight.bold : FontWeight.w500, + color: isTotal ? AppColors.primary : Colors.grey[700], + ), + ), + Text( + amount.currencyFormatRpV2, + style: TextStyle( + fontSize: isTotal ? 18 : 16, + fontWeight: isTotal ? FontWeight.bold : FontWeight.w600, + color: isTotal ? AppColors.primary : Colors.grey[800], + ), + ), + ], + ), + ); + } + + Color _getStatusColor(String? status) { + switch (status?.toLowerCase()) { + case 'completed': + case 'paid': + return Colors.green; + case 'pending': + return Colors.orange; + case 'cancelled': + return Colors.red; + default: + return Colors.grey; + } + } + + void _processRefund(BuildContext context) { + // Validate refund amount + final refundAmount = int.tryParse(_refundAmountController.text) ?? 0; + if (refundAmount <= 0) { + _showErrorDialog('Jumlah refund harus lebih dari 0'); + return; + } + + final totalAmount = widget.selectedOrder.totalAmount ?? 0; + if (refundAmount > totalAmount) { + _showErrorDialog('Jumlah refund tidak boleh melebihi total Pesanan'); + return; + } + + // Get reason text + String reason = selectedReason; + if (selectedReason == 'Lainnya' && _reasonController.text.isNotEmpty) { + reason = _reasonController.text; + } + + // Trigger refund event + context.read().add( + RefundEvent.refundPayment( + orderId: widget.selectedOrder.id ?? + '', // Assuming order ID is payment ID + reason: reason, + refundAmount: refundAmount, + ), + ); + } + + void _showErrorDialog(String message) { + showDialog( + context: context, + builder: (context) => RefundErrorDialog(message: message), + ); + } + + void _showSuccessDialog() { + final refundAmount = int.tryParse(_refundAmountController.text) ?? 0; + + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => RefundSuccessDialog( + selectedReason: selectedReason, + refundAmount: refundAmount, + ), + ); + } +} diff --git a/lib/presentation/refund/widgets/refund_appbar.dart b/lib/presentation/refund/widgets/refund_appbar.dart new file mode 100644 index 0000000..cad2618 --- /dev/null +++ b/lib/presentation/refund/widgets/refund_appbar.dart @@ -0,0 +1,104 @@ +import 'package:enaklo_pos/core/extensions/date_time_ext.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:flutter/material.dart'; + +class RefundAppbar extends StatelessWidget { + final Order order; + const RefundAppbar({super.key, required this.order}); + + @override + Widget build(BuildContext context) { + return Container( + height: 120, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xFF36175E), + Color(0xFF4A2C6B), + Color(0xFF5D3F78), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + boxShadow: [ + BoxShadow( + color: Color(0xFF36175E).withOpacity(0.4), + blurRadius: 30, + offset: Offset(0, 15), + ), + ], + ), + child: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 16), + child: Row( + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.white.withOpacity(0.3)), + ), + child: IconButton( + icon: Icon(Icons.arrow_back_ios_new, + color: Colors.white, size: 24), + onPressed: () => Navigator.pop(context), + ), + ), + SizedBox(width: 24), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Refund Pesanan', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + letterSpacing: -0.5, + ), + ), + SizedBox(height: 4), + Text( + 'Order #${order.orderNumber}', + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(25), + border: Border.all(color: Colors.white.withOpacity(0.3)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.schedule, color: Colors.white, size: 18), + SizedBox(width: 8), + Text( + DateTime.now().toFormattedDate3(), + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/refund/widgets/refund_info_tile.dart b/lib/presentation/refund/widgets/refund_info_tile.dart new file mode 100644 index 0000000..61748f8 --- /dev/null +++ b/lib/presentation/refund/widgets/refund_info_tile.dart @@ -0,0 +1,57 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:flutter/material.dart'; + +class RefundInfoTile extends StatelessWidget { + final String title; + final String value; + final IconData icon; + final Color color; + + const RefundInfoTile({ + super.key, + required this.title, + required this.value, + required this.icon, + required this.color, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: color.withOpacity(0.2)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(icon, color: color, size: 20), + SizedBox(width: 8), + Text( + title, + style: TextStyle( + color: Colors.grey[600], + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + SpaceHeight(8), + Text( + value, + style: TextStyle( + color: Colors.grey[800], + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/refund/widgets/refund_order_Item_tile.dart b/lib/presentation/refund/widgets/refund_order_Item_tile.dart new file mode 100644 index 0000000..6585641 --- /dev/null +++ b/lib/presentation/refund/widgets/refund_order_Item_tile.dart @@ -0,0 +1,130 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/int_ext.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:flutter/material.dart'; + +class RefundOrderItemTile extends StatelessWidget { + final OrderItem item; + const RefundOrderItemTile({super.key, required this.item}); + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.circular(20), + border: Border.all(color: Colors.grey[200]!, width: 1), + ), + child: Padding( + padding: EdgeInsets.all(24), + child: Row( + children: [ + // Item Icon + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.restaurant, + color: AppColors.primary, + size: 24, + ), + ), + + SizedBox(width: 20), + + // Item Details + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.productName ?? 'N/A', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + color: Colors.grey[800], + ), + ), + if (item.productVariantName != null) ...[ + SpaceHeight(4), + Text( + item.productVariantName!, + style: TextStyle( + color: Colors.grey[600], + fontSize: 14, + ), + ), + ], + if (item.notes != null && item.notes!.isNotEmpty) ...[ + SpaceHeight(8), + Container( + padding: + EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.amber.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + 'Catatan: ${item.notes}', + style: TextStyle( + color: Colors.amber[700], + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ], + ), + ), + + // Price & Quantity + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + (item.unitPrice ?? 0).currencyFormatRpV2, + style: TextStyle( + fontWeight: FontWeight.bold, + color: AppColors.primary, + fontSize: 16, + ), + ), + SpaceHeight(4), + Container( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: Colors.blue.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + 'x${item.quantity}', + style: TextStyle( + color: Colors.blue[700], + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ), + SpaceHeight(8), + Text( + (item.totalPrice ?? 0).currencyFormatRpV2, + style: TextStyle( + color: Colors.grey[600], + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/refund/widgets/refund_reason_tile.dart b/lib/presentation/refund/widgets/refund_reason_tile.dart new file mode 100644 index 0000000..058af7b --- /dev/null +++ b/lib/presentation/refund/widgets/refund_reason_tile.dart @@ -0,0 +1,53 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:flutter/material.dart'; + +class RefundReasonTile extends StatelessWidget { + final bool isSelected; + final Map reason; + final Function() onTap; + const RefundReasonTile({ + super.key, + required this.isSelected, + required this.reason, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + decoration: BoxDecoration( + color: + isSelected ? reason['color'].withOpacity(0.2) : Colors.grey[100], + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: isSelected ? reason['color'] : Colors.transparent, + width: 2, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + reason['icon'], + color: isSelected ? reason['color'] : Colors.grey[600], + size: 20, + ), + SpaceHeight(4), + Text( + reason['value'], + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w600, + color: isSelected ? reason['color'] : Colors.grey[600], + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/report/blocs/item_sales_report/item_sales_report_bloc.dart b/lib/presentation/report/blocs/item_sales_report/item_sales_report_bloc.dart index c5b3eba..2365ecc 100644 --- a/lib/presentation/report/blocs/item_sales_report/item_sales_report_bloc.dart +++ b/lib/presentation/report/blocs/item_sales_report/item_sales_report_bloc.dart @@ -1,6 +1,6 @@ import 'package:bloc/bloc.dart'; -import 'package:enaklo_pos/data/models/response/item_sales_response_model.dart'; -import 'package:enaklo_pos/data/datasources/order_item_remote_datasource.dart'; +import 'package:enaklo_pos/data/datasources/analytic_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/response/sales_analytic_response_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'item_sales_report_event.dart'; @@ -9,12 +9,14 @@ part 'item_sales_report_bloc.freezed.dart'; class ItemSalesReportBloc extends Bloc { - final OrderItemRemoteDatasource datasource; + final AnalyticRemoteDatasource datasource; ItemSalesReportBloc(this.datasource) : super(const _Initial()) { on<_GetItemSales>((event, emit) async { emit(const _Loading()); - final result = await datasource.getItemSalesByRangeDate( - event.startDate, event.endDate); + final result = await datasource.getSales( + dateFrom: event.startDate, + dateTo: event.endDate, + ); result.fold((l) => emit(_Error(l)), (r) => emit(_Loaded(r.data!))); }); } diff --git a/lib/presentation/report/blocs/item_sales_report/item_sales_report_bloc.freezed.dart b/lib/presentation/report/blocs/item_sales_report/item_sales_report_bloc.freezed.dart index c7012a7..ce47bf4 100644 --- a/lib/presentation/report/blocs/item_sales_report/item_sales_report_bloc.freezed.dart +++ b/lib/presentation/report/blocs/item_sales_report/item_sales_report_bloc.freezed.dart @@ -19,19 +19,20 @@ mixin _$ItemSalesReportEvent { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(String startDate, String endDate) getItemSales, + required TResult Function(DateTime startDate, DateTime endDate) + getItemSales, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(String startDate, String endDate)? getItemSales, + TResult? Function(DateTime startDate, DateTime endDate)? getItemSales, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(String startDate, String endDate)? getItemSales, + TResult Function(DateTime startDate, DateTime endDate)? getItemSales, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -120,7 +121,8 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(String startDate, String endDate) getItemSales, + required TResult Function(DateTime startDate, DateTime endDate) + getItemSales, }) { return started(); } @@ -129,7 +131,7 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(String startDate, String endDate)? getItemSales, + TResult? Function(DateTime startDate, DateTime endDate)? getItemSales, }) { return started?.call(); } @@ -138,7 +140,7 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(String startDate, String endDate)? getItemSales, + TResult Function(DateTime startDate, DateTime endDate)? getItemSales, required TResult orElse(), }) { if (started != null) { @@ -189,7 +191,7 @@ abstract class _$$GetItemSalesImplCopyWith<$Res> { _$GetItemSalesImpl value, $Res Function(_$GetItemSalesImpl) then) = __$$GetItemSalesImplCopyWithImpl<$Res>; @useResult - $Res call({String startDate, String endDate}); + $Res call({DateTime startDate, DateTime endDate}); } /// @nodoc @@ -212,11 +214,11 @@ class __$$GetItemSalesImplCopyWithImpl<$Res> startDate: null == startDate ? _value.startDate : startDate // ignore: cast_nullable_to_non_nullable - as String, + as DateTime, endDate: null == endDate ? _value.endDate : endDate // ignore: cast_nullable_to_non_nullable - as String, + as DateTime, )); } } @@ -227,9 +229,9 @@ class _$GetItemSalesImpl implements _GetItemSales { const _$GetItemSalesImpl({required this.startDate, required this.endDate}); @override - final String startDate; + final DateTime startDate; @override - final String endDate; + final DateTime endDate; @override String toString() { @@ -261,7 +263,8 @@ class _$GetItemSalesImpl implements _GetItemSales { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(String startDate, String endDate) getItemSales, + required TResult Function(DateTime startDate, DateTime endDate) + getItemSales, }) { return getItemSales(startDate, endDate); } @@ -270,7 +273,7 @@ class _$GetItemSalesImpl implements _GetItemSales { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(String startDate, String endDate)? getItemSales, + TResult? Function(DateTime startDate, DateTime endDate)? getItemSales, }) { return getItemSales?.call(startDate, endDate); } @@ -279,7 +282,7 @@ class _$GetItemSalesImpl implements _GetItemSales { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(String startDate, String endDate)? getItemSales, + TResult Function(DateTime startDate, DateTime endDate)? getItemSales, required TResult orElse(), }) { if (getItemSales != null) { @@ -322,11 +325,11 @@ class _$GetItemSalesImpl implements _GetItemSales { abstract class _GetItemSales implements ItemSalesReportEvent { const factory _GetItemSales( - {required final String startDate, - required final String endDate}) = _$GetItemSalesImpl; + {required final DateTime startDate, + required final DateTime endDate}) = _$GetItemSalesImpl; - String get startDate; - String get endDate; + DateTime get startDate; + DateTime get endDate; /// Create a copy of ItemSalesReportEvent /// with the given fields replaced by the non-null parameter values. @@ -341,7 +344,7 @@ mixin _$ItemSalesReportState { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List itemSales) loaded, + required TResult Function(SalesAnalyticData itemSales) loaded, required TResult Function(String message) error, }) => throw _privateConstructorUsedError; @@ -349,7 +352,7 @@ mixin _$ItemSalesReportState { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List itemSales)? loaded, + TResult? Function(SalesAnalyticData itemSales)? loaded, TResult? Function(String message)? error, }) => throw _privateConstructorUsedError; @@ -357,7 +360,7 @@ mixin _$ItemSalesReportState { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List itemSales)? loaded, + TResult Function(SalesAnalyticData itemSales)? loaded, TResult Function(String message)? error, required TResult orElse(), }) => @@ -454,7 +457,7 @@ class _$InitialImpl implements _Initial { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List itemSales) loaded, + required TResult Function(SalesAnalyticData itemSales) loaded, required TResult Function(String message) error, }) { return initial(); @@ -465,7 +468,7 @@ class _$InitialImpl implements _Initial { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List itemSales)? loaded, + TResult? Function(SalesAnalyticData itemSales)? loaded, TResult? Function(String message)? error, }) { return initial?.call(); @@ -476,7 +479,7 @@ class _$InitialImpl implements _Initial { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List itemSales)? loaded, + TResult Function(SalesAnalyticData itemSales)? loaded, TResult Function(String message)? error, required TResult orElse(), }) { @@ -571,7 +574,7 @@ class _$LoadingImpl implements _Loading { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List itemSales) loaded, + required TResult Function(SalesAnalyticData itemSales) loaded, required TResult Function(String message) error, }) { return loading(); @@ -582,7 +585,7 @@ class _$LoadingImpl implements _Loading { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List itemSales)? loaded, + TResult? Function(SalesAnalyticData itemSales)? loaded, TResult? Function(String message)? error, }) { return loading?.call(); @@ -593,7 +596,7 @@ class _$LoadingImpl implements _Loading { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List itemSales)? loaded, + TResult Function(SalesAnalyticData itemSales)? loaded, TResult Function(String message)? error, required TResult orElse(), }) { @@ -651,7 +654,7 @@ abstract class _$$LoadedImplCopyWith<$Res> { _$LoadedImpl value, $Res Function(_$LoadedImpl) then) = __$$LoadedImplCopyWithImpl<$Res>; @useResult - $Res call({List itemSales}); + $Res call({SalesAnalyticData itemSales}); } /// @nodoc @@ -671,9 +674,9 @@ class __$$LoadedImplCopyWithImpl<$Res> }) { return _then(_$LoadedImpl( null == itemSales - ? _value._itemSales + ? _value.itemSales : itemSales // ignore: cast_nullable_to_non_nullable - as List, + as SalesAnalyticData, )); } } @@ -681,15 +684,10 @@ class __$$LoadedImplCopyWithImpl<$Res> /// @nodoc class _$LoadedImpl implements _Loaded { - const _$LoadedImpl(final List itemSales) : _itemSales = itemSales; + const _$LoadedImpl(this.itemSales); - final List _itemSales; @override - List get itemSales { - if (_itemSales is EqualUnmodifiableListView) return _itemSales; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_itemSales); - } + final SalesAnalyticData itemSales; @override String toString() { @@ -701,13 +699,12 @@ class _$LoadedImpl implements _Loaded { return identical(this, other) || (other.runtimeType == runtimeType && other is _$LoadedImpl && - const DeepCollectionEquality() - .equals(other._itemSales, _itemSales)); + (identical(other.itemSales, itemSales) || + other.itemSales == itemSales)); } @override - int get hashCode => - Object.hash(runtimeType, const DeepCollectionEquality().hash(_itemSales)); + int get hashCode => Object.hash(runtimeType, itemSales); /// Create a copy of ItemSalesReportState /// with the given fields replaced by the non-null parameter values. @@ -722,7 +719,7 @@ class _$LoadedImpl implements _Loaded { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List itemSales) loaded, + required TResult Function(SalesAnalyticData itemSales) loaded, required TResult Function(String message) error, }) { return loaded(itemSales); @@ -733,7 +730,7 @@ class _$LoadedImpl implements _Loaded { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List itemSales)? loaded, + TResult? Function(SalesAnalyticData itemSales)? loaded, TResult? Function(String message)? error, }) { return loaded?.call(itemSales); @@ -744,7 +741,7 @@ class _$LoadedImpl implements _Loaded { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List itemSales)? loaded, + TResult Function(SalesAnalyticData itemSales)? loaded, TResult Function(String message)? error, required TResult orElse(), }) { @@ -793,9 +790,9 @@ class _$LoadedImpl implements _Loaded { } abstract class _Loaded implements ItemSalesReportState { - const factory _Loaded(final List itemSales) = _$LoadedImpl; + const factory _Loaded(final SalesAnalyticData itemSales) = _$LoadedImpl; - List get itemSales; + SalesAnalyticData get itemSales; /// Create a copy of ItemSalesReportState /// with the given fields replaced by the non-null parameter values. @@ -874,7 +871,7 @@ class _$ErrorImpl implements _Error { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List itemSales) loaded, + required TResult Function(SalesAnalyticData itemSales) loaded, required TResult Function(String message) error, }) { return error(message); @@ -885,7 +882,7 @@ class _$ErrorImpl implements _Error { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List itemSales)? loaded, + TResult? Function(SalesAnalyticData itemSales)? loaded, TResult? Function(String message)? error, }) { return error?.call(message); @@ -896,7 +893,7 @@ class _$ErrorImpl implements _Error { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List itemSales)? loaded, + TResult Function(SalesAnalyticData itemSales)? loaded, TResult Function(String message)? error, required TResult orElse(), }) { diff --git a/lib/presentation/report/blocs/item_sales_report/item_sales_report_event.dart b/lib/presentation/report/blocs/item_sales_report/item_sales_report_event.dart index 1935cf0..86ff9c1 100644 --- a/lib/presentation/report/blocs/item_sales_report/item_sales_report_event.dart +++ b/lib/presentation/report/blocs/item_sales_report/item_sales_report_event.dart @@ -4,7 +4,7 @@ part of 'item_sales_report_bloc.dart'; class ItemSalesReportEvent with _$ItemSalesReportEvent { const factory ItemSalesReportEvent.started() = _Started; const factory ItemSalesReportEvent.getItemSales({ - required String startDate, - required String endDate, + required DateTime startDate, + required DateTime endDate, }) = _GetItemSales; } diff --git a/lib/presentation/report/blocs/item_sales_report/item_sales_report_state.dart b/lib/presentation/report/blocs/item_sales_report/item_sales_report_state.dart index c0b3de6..733fc58 100644 --- a/lib/presentation/report/blocs/item_sales_report/item_sales_report_state.dart +++ b/lib/presentation/report/blocs/item_sales_report/item_sales_report_state.dart @@ -4,7 +4,7 @@ part of 'item_sales_report_bloc.dart'; class ItemSalesReportState with _$ItemSalesReportState { const factory ItemSalesReportState.initial() = _Initial; const factory ItemSalesReportState.loading() = _Loading; - const factory ItemSalesReportState.loaded(List itemSales) = + const factory ItemSalesReportState.loaded(SalesAnalyticData itemSales) = _Loaded; const factory ItemSalesReportState.error(String message) = _Error; } diff --git a/lib/presentation/report/blocs/payment_method_report/payment_method_report_bloc.dart b/lib/presentation/report/blocs/payment_method_report/payment_method_report_bloc.dart index a45ed06..ddf6ff7 100644 --- a/lib/presentation/report/blocs/payment_method_report/payment_method_report_bloc.dart +++ b/lib/presentation/report/blocs/payment_method_report/payment_method_report_bloc.dart @@ -1,6 +1,6 @@ +import 'package:enaklo_pos/data/datasources/analytic_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/response/payment_method_analytic_response_model.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart'; -import 'package:enaklo_pos/data/models/response/payment_method_response_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'payment_method_report_event.dart'; @@ -9,16 +9,23 @@ part 'payment_method_report_bloc.freezed.dart'; class PaymentMethodReportBloc extends Bloc { - final OrderRemoteDatasource datasource; + final AnalyticRemoteDatasource datasource; PaymentMethodReportBloc(this.datasource) : super(const _Initial()) { on<_GetPaymentMethodReport>((event, emit) async { emit(const _Loading()); - final result = await datasource.getPaymentMethodByRangeDate( - event.startDate, - event.endDate, + final result = await datasource.getPaymentMethod( + dateFrom: event.startDate, + dateTo: event.endDate, ); - result.fold((l) => emit(_Error(l)), (r) => emit(_Loaded(r.data!))); + result.fold( + (l) => emit(_Error(l)), + (r) => emit( + _Loaded( + r.data, + ), + ), + ); }); } -} \ No newline at end of file +} diff --git a/lib/presentation/report/blocs/payment_method_report/payment_method_report_bloc.freezed.dart b/lib/presentation/report/blocs/payment_method_report/payment_method_report_bloc.freezed.dart index 2671d58..7de5a89 100644 --- a/lib/presentation/report/blocs/payment_method_report/payment_method_report_bloc.freezed.dart +++ b/lib/presentation/report/blocs/payment_method_report/payment_method_report_bloc.freezed.dart @@ -16,22 +16,24 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$PaymentMethodReportEvent { - String get startDate => throw _privateConstructorUsedError; - String get endDate => throw _privateConstructorUsedError; + DateTime get startDate => throw _privateConstructorUsedError; + DateTime get endDate => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ - required TResult Function(String startDate, String endDate) + required TResult Function(DateTime startDate, DateTime endDate) getPaymentMethodReport, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(String startDate, String endDate)? getPaymentMethodReport, + TResult? Function(DateTime startDate, DateTime endDate)? + getPaymentMethodReport, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function(String startDate, String endDate)? getPaymentMethodReport, + TResult Function(DateTime startDate, DateTime endDate)? + getPaymentMethodReport, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -66,7 +68,7 @@ abstract class $PaymentMethodReportEventCopyWith<$Res> { $Res Function(PaymentMethodReportEvent) then) = _$PaymentMethodReportEventCopyWithImpl<$Res, PaymentMethodReportEvent>; @useResult - $Res call({String startDate, String endDate}); + $Res call({DateTime startDate, DateTime endDate}); } /// @nodoc @@ -92,11 +94,11 @@ class _$PaymentMethodReportEventCopyWithImpl<$Res, startDate: null == startDate ? _value.startDate : startDate // ignore: cast_nullable_to_non_nullable - as String, + as DateTime, endDate: null == endDate ? _value.endDate : endDate // ignore: cast_nullable_to_non_nullable - as String, + as DateTime, ) as $Val); } } @@ -110,7 +112,7 @@ abstract class _$$GetPaymentMethodReportImplCopyWith<$Res> __$$GetPaymentMethodReportImplCopyWithImpl<$Res>; @override @useResult - $Res call({String startDate, String endDate}); + $Res call({DateTime startDate, DateTime endDate}); } /// @nodoc @@ -135,11 +137,11 @@ class __$$GetPaymentMethodReportImplCopyWithImpl<$Res> startDate: null == startDate ? _value.startDate : startDate // ignore: cast_nullable_to_non_nullable - as String, + as DateTime, endDate: null == endDate ? _value.endDate : endDate // ignore: cast_nullable_to_non_nullable - as String, + as DateTime, )); } } @@ -151,9 +153,9 @@ class _$GetPaymentMethodReportImpl implements _GetPaymentMethodReport { {required this.startDate, required this.endDate}); @override - final String startDate; + final DateTime startDate; @override - final String endDate; + final DateTime endDate; @override String toString() { @@ -185,7 +187,7 @@ class _$GetPaymentMethodReportImpl implements _GetPaymentMethodReport { @override @optionalTypeArgs TResult when({ - required TResult Function(String startDate, String endDate) + required TResult Function(DateTime startDate, DateTime endDate) getPaymentMethodReport, }) { return getPaymentMethodReport(startDate, endDate); @@ -194,7 +196,8 @@ class _$GetPaymentMethodReportImpl implements _GetPaymentMethodReport { @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(String startDate, String endDate)? getPaymentMethodReport, + TResult? Function(DateTime startDate, DateTime endDate)? + getPaymentMethodReport, }) { return getPaymentMethodReport?.call(startDate, endDate); } @@ -202,7 +205,8 @@ class _$GetPaymentMethodReportImpl implements _GetPaymentMethodReport { @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(String startDate, String endDate)? getPaymentMethodReport, + TResult Function(DateTime startDate, DateTime endDate)? + getPaymentMethodReport, required TResult orElse(), }) { if (getPaymentMethodReport != null) { @@ -243,13 +247,13 @@ class _$GetPaymentMethodReportImpl implements _GetPaymentMethodReport { abstract class _GetPaymentMethodReport implements PaymentMethodReportEvent { const factory _GetPaymentMethodReport( - {required final String startDate, - required final String endDate}) = _$GetPaymentMethodReportImpl; + {required final DateTime startDate, + required final DateTime endDate}) = _$GetPaymentMethodReportImpl; @override - String get startDate; + DateTime get startDate; @override - String get endDate; + DateTime get endDate; /// Create a copy of PaymentMethodReportEvent /// with the given fields replaced by the non-null parameter values. @@ -265,7 +269,7 @@ mixin _$PaymentMethodReportState { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(PaymentMethodData data) loaded, + required TResult Function(PaymentMethodAnalyticData data) loaded, required TResult Function(String message) error, }) => throw _privateConstructorUsedError; @@ -273,7 +277,7 @@ mixin _$PaymentMethodReportState { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(PaymentMethodData data)? loaded, + TResult? Function(PaymentMethodAnalyticData data)? loaded, TResult? Function(String message)? error, }) => throw _privateConstructorUsedError; @@ -281,7 +285,7 @@ mixin _$PaymentMethodReportState { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(PaymentMethodData data)? loaded, + TResult Function(PaymentMethodAnalyticData data)? loaded, TResult Function(String message)? error, required TResult orElse(), }) => @@ -378,7 +382,7 @@ class _$InitialImpl implements _Initial { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(PaymentMethodData data) loaded, + required TResult Function(PaymentMethodAnalyticData data) loaded, required TResult Function(String message) error, }) { return initial(); @@ -389,7 +393,7 @@ class _$InitialImpl implements _Initial { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(PaymentMethodData data)? loaded, + TResult? Function(PaymentMethodAnalyticData data)? loaded, TResult? Function(String message)? error, }) { return initial?.call(); @@ -400,7 +404,7 @@ class _$InitialImpl implements _Initial { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(PaymentMethodData data)? loaded, + TResult Function(PaymentMethodAnalyticData data)? loaded, TResult Function(String message)? error, required TResult orElse(), }) { @@ -495,7 +499,7 @@ class _$LoadingImpl implements _Loading { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(PaymentMethodData data) loaded, + required TResult Function(PaymentMethodAnalyticData data) loaded, required TResult Function(String message) error, }) { return loading(); @@ -506,7 +510,7 @@ class _$LoadingImpl implements _Loading { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(PaymentMethodData data)? loaded, + TResult? Function(PaymentMethodAnalyticData data)? loaded, TResult? Function(String message)? error, }) { return loading?.call(); @@ -517,7 +521,7 @@ class _$LoadingImpl implements _Loading { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(PaymentMethodData data)? loaded, + TResult Function(PaymentMethodAnalyticData data)? loaded, TResult Function(String message)? error, required TResult orElse(), }) { @@ -575,7 +579,7 @@ abstract class _$$LoadedImplCopyWith<$Res> { _$LoadedImpl value, $Res Function(_$LoadedImpl) then) = __$$LoadedImplCopyWithImpl<$Res>; @useResult - $Res call({PaymentMethodData data}); + $Res call({PaymentMethodAnalyticData data}); } /// @nodoc @@ -597,7 +601,7 @@ class __$$LoadedImplCopyWithImpl<$Res> null == data ? _value.data : data // ignore: cast_nullable_to_non_nullable - as PaymentMethodData, + as PaymentMethodAnalyticData, )); } } @@ -608,7 +612,7 @@ class _$LoadedImpl implements _Loaded { const _$LoadedImpl(this.data); @override - final PaymentMethodData data; + final PaymentMethodAnalyticData data; @override String toString() { @@ -639,7 +643,7 @@ class _$LoadedImpl implements _Loaded { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(PaymentMethodData data) loaded, + required TResult Function(PaymentMethodAnalyticData data) loaded, required TResult Function(String message) error, }) { return loaded(data); @@ -650,7 +654,7 @@ class _$LoadedImpl implements _Loaded { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(PaymentMethodData data)? loaded, + TResult? Function(PaymentMethodAnalyticData data)? loaded, TResult? Function(String message)? error, }) { return loaded?.call(data); @@ -661,7 +665,7 @@ class _$LoadedImpl implements _Loaded { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(PaymentMethodData data)? loaded, + TResult Function(PaymentMethodAnalyticData data)? loaded, TResult Function(String message)? error, required TResult orElse(), }) { @@ -710,9 +714,9 @@ class _$LoadedImpl implements _Loaded { } abstract class _Loaded implements PaymentMethodReportState { - const factory _Loaded(final PaymentMethodData data) = _$LoadedImpl; + const factory _Loaded(final PaymentMethodAnalyticData data) = _$LoadedImpl; - PaymentMethodData get data; + PaymentMethodAnalyticData get data; /// Create a copy of PaymentMethodReportState /// with the given fields replaced by the non-null parameter values. @@ -791,7 +795,7 @@ class _$ErrorImpl implements _Error { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(PaymentMethodData data) loaded, + required TResult Function(PaymentMethodAnalyticData data) loaded, required TResult Function(String message) error, }) { return error(message); @@ -802,7 +806,7 @@ class _$ErrorImpl implements _Error { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(PaymentMethodData data)? loaded, + TResult? Function(PaymentMethodAnalyticData data)? loaded, TResult? Function(String message)? error, }) { return error?.call(message); @@ -813,7 +817,7 @@ class _$ErrorImpl implements _Error { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(PaymentMethodData data)? loaded, + TResult Function(PaymentMethodAnalyticData data)? loaded, TResult Function(String message)? error, required TResult orElse(), }) { diff --git a/lib/presentation/report/blocs/payment_method_report/payment_method_report_event.dart b/lib/presentation/report/blocs/payment_method_report/payment_method_report_event.dart index 55cd10f..81888b8 100644 --- a/lib/presentation/report/blocs/payment_method_report/payment_method_report_event.dart +++ b/lib/presentation/report/blocs/payment_method_report/payment_method_report_event.dart @@ -3,7 +3,7 @@ part of 'payment_method_report_bloc.dart'; @freezed class PaymentMethodReportEvent with _$PaymentMethodReportEvent { const factory PaymentMethodReportEvent.getPaymentMethodReport({ - required String startDate, - required String endDate, + required DateTime startDate, + required DateTime endDate, }) = _GetPaymentMethodReport; -} \ No newline at end of file +} diff --git a/lib/presentation/report/blocs/payment_method_report/payment_method_report_state.dart b/lib/presentation/report/blocs/payment_method_report/payment_method_report_state.dart index 70cb241..5e85d75 100644 --- a/lib/presentation/report/blocs/payment_method_report/payment_method_report_state.dart +++ b/lib/presentation/report/blocs/payment_method_report/payment_method_report_state.dart @@ -4,6 +4,7 @@ part of 'payment_method_report_bloc.dart'; class PaymentMethodReportState with _$PaymentMethodReportState { const factory PaymentMethodReportState.initial() = _Initial; const factory PaymentMethodReportState.loading() = _Loading; - const factory PaymentMethodReportState.loaded(PaymentMethodData data) = _Loaded; + const factory PaymentMethodReportState.loaded( + PaymentMethodAnalyticData data) = _Loaded; const factory PaymentMethodReportState.error(String message) = _Error; -} \ No newline at end of file +} diff --git a/lib/presentation/report/blocs/product_sales/product_sales_bloc.dart b/lib/presentation/report/blocs/product_sales/product_sales_bloc.dart index 0aaf866..57f61dd 100644 --- a/lib/presentation/report/blocs/product_sales/product_sales_bloc.dart +++ b/lib/presentation/report/blocs/product_sales/product_sales_bloc.dart @@ -1,6 +1,6 @@ +import 'package:enaklo_pos/data/datasources/analytic_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/response/product_analytic_response_model.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:enaklo_pos/data/datasources/order_item_remote_datasource.dart'; -import 'package:enaklo_pos/data/models/response/product_sales_response_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'product_sales_event.dart'; @@ -8,14 +8,16 @@ part 'product_sales_state.dart'; part 'product_sales_bloc.freezed.dart'; class ProductSalesBloc extends Bloc { - final OrderItemRemoteDatasource datasource; + final AnalyticRemoteDatasource datasource; ProductSalesBloc( this.datasource, ) : super(const _Initial()) { on<_GetProductSales>((event, emit) async { emit(const _Loading()); - final result = await datasource.getProductSalesByRangeDate( - event.startDate, event.endDate); + final result = await datasource.getProduct( + dateFrom: event.startDate, + dateTo: event.endDate, + ); result.fold((l) => emit(_Error(l)), (r) => emit(_Success(r.data!))); }); } diff --git a/lib/presentation/report/blocs/product_sales/product_sales_bloc.freezed.dart b/lib/presentation/report/blocs/product_sales/product_sales_bloc.freezed.dart index c44d742..5ab5dc0 100644 --- a/lib/presentation/report/blocs/product_sales/product_sales_bloc.freezed.dart +++ b/lib/presentation/report/blocs/product_sales/product_sales_bloc.freezed.dart @@ -19,19 +19,20 @@ mixin _$ProductSalesEvent { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(String startDate, String endDate) getProductSales, + required TResult Function(DateTime startDate, DateTime endDate) + getProductSales, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(String startDate, String endDate)? getProductSales, + TResult? Function(DateTime startDate, DateTime endDate)? getProductSales, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(String startDate, String endDate)? getProductSales, + TResult Function(DateTime startDate, DateTime endDate)? getProductSales, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -119,7 +120,8 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(String startDate, String endDate) getProductSales, + required TResult Function(DateTime startDate, DateTime endDate) + getProductSales, }) { return started(); } @@ -128,7 +130,7 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(String startDate, String endDate)? getProductSales, + TResult? Function(DateTime startDate, DateTime endDate)? getProductSales, }) { return started?.call(); } @@ -137,7 +139,7 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(String startDate, String endDate)? getProductSales, + TResult Function(DateTime startDate, DateTime endDate)? getProductSales, required TResult orElse(), }) { if (started != null) { @@ -188,7 +190,7 @@ abstract class _$$GetProductSalesImplCopyWith<$Res> { $Res Function(_$GetProductSalesImpl) then) = __$$GetProductSalesImplCopyWithImpl<$Res>; @useResult - $Res call({String startDate, String endDate}); + $Res call({DateTime startDate, DateTime endDate}); } /// @nodoc @@ -211,11 +213,11 @@ class __$$GetProductSalesImplCopyWithImpl<$Res> null == startDate ? _value.startDate : startDate // ignore: cast_nullable_to_non_nullable - as String, + as DateTime, null == endDate ? _value.endDate : endDate // ignore: cast_nullable_to_non_nullable - as String, + as DateTime, )); } } @@ -226,9 +228,9 @@ class _$GetProductSalesImpl implements _GetProductSales { const _$GetProductSalesImpl(this.startDate, this.endDate); @override - final String startDate; + final DateTime startDate; @override - final String endDate; + final DateTime endDate; @override String toString() { @@ -261,7 +263,8 @@ class _$GetProductSalesImpl implements _GetProductSales { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(String startDate, String endDate) getProductSales, + required TResult Function(DateTime startDate, DateTime endDate) + getProductSales, }) { return getProductSales(startDate, endDate); } @@ -270,7 +273,7 @@ class _$GetProductSalesImpl implements _GetProductSales { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(String startDate, String endDate)? getProductSales, + TResult? Function(DateTime startDate, DateTime endDate)? getProductSales, }) { return getProductSales?.call(startDate, endDate); } @@ -279,7 +282,7 @@ class _$GetProductSalesImpl implements _GetProductSales { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(String startDate, String endDate)? getProductSales, + TResult Function(DateTime startDate, DateTime endDate)? getProductSales, required TResult orElse(), }) { if (getProductSales != null) { @@ -321,11 +324,11 @@ class _$GetProductSalesImpl implements _GetProductSales { } abstract class _GetProductSales implements ProductSalesEvent { - const factory _GetProductSales(final String startDate, final String endDate) = - _$GetProductSalesImpl; + const factory _GetProductSales( + final DateTime startDate, final DateTime endDate) = _$GetProductSalesImpl; - String get startDate; - String get endDate; + DateTime get startDate; + DateTime get endDate; /// Create a copy of ProductSalesEvent /// with the given fields replaced by the non-null parameter values. @@ -340,7 +343,7 @@ mixin _$ProductSalesState { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List productSales) success, + required TResult Function(ProductAnalyticData product) success, required TResult Function(String message) error, }) => throw _privateConstructorUsedError; @@ -348,7 +351,7 @@ mixin _$ProductSalesState { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List productSales)? success, + TResult? Function(ProductAnalyticData product)? success, TResult? Function(String message)? error, }) => throw _privateConstructorUsedError; @@ -356,7 +359,7 @@ mixin _$ProductSalesState { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List productSales)? success, + TResult Function(ProductAnalyticData product)? success, TResult Function(String message)? error, required TResult orElse(), }) => @@ -452,7 +455,7 @@ class _$InitialImpl implements _Initial { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List productSales) success, + required TResult Function(ProductAnalyticData product) success, required TResult Function(String message) error, }) { return initial(); @@ -463,7 +466,7 @@ class _$InitialImpl implements _Initial { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List productSales)? success, + TResult? Function(ProductAnalyticData product)? success, TResult? Function(String message)? error, }) { return initial?.call(); @@ -474,7 +477,7 @@ class _$InitialImpl implements _Initial { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List productSales)? success, + TResult Function(ProductAnalyticData product)? success, TResult Function(String message)? error, required TResult orElse(), }) { @@ -569,7 +572,7 @@ class _$LoadingImpl implements _Loading { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List productSales) success, + required TResult Function(ProductAnalyticData product) success, required TResult Function(String message) error, }) { return loading(); @@ -580,7 +583,7 @@ class _$LoadingImpl implements _Loading { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List productSales)? success, + TResult? Function(ProductAnalyticData product)? success, TResult? Function(String message)? error, }) { return loading?.call(); @@ -591,7 +594,7 @@ class _$LoadingImpl implements _Loading { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List productSales)? success, + TResult Function(ProductAnalyticData product)? success, TResult Function(String message)? error, required TResult orElse(), }) { @@ -649,7 +652,7 @@ abstract class _$$SuccessImplCopyWith<$Res> { _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = __$$SuccessImplCopyWithImpl<$Res>; @useResult - $Res call({List productSales}); + $Res call({ProductAnalyticData product}); } /// @nodoc @@ -665,13 +668,13 @@ class __$$SuccessImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? productSales = null, + Object? product = null, }) { return _then(_$SuccessImpl( - null == productSales - ? _value._productSales - : productSales // ignore: cast_nullable_to_non_nullable - as List, + null == product + ? _value.product + : product // ignore: cast_nullable_to_non_nullable + as ProductAnalyticData, )); } } @@ -679,20 +682,14 @@ class __$$SuccessImplCopyWithImpl<$Res> /// @nodoc class _$SuccessImpl implements _Success { - const _$SuccessImpl(final List productSales) - : _productSales = productSales; + const _$SuccessImpl(this.product); - final List _productSales; @override - List get productSales { - if (_productSales is EqualUnmodifiableListView) return _productSales; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_productSales); - } + final ProductAnalyticData product; @override String toString() { - return 'ProductSalesState.success(productSales: $productSales)'; + return 'ProductSalesState.success(product: $product)'; } @override @@ -700,13 +697,11 @@ class _$SuccessImpl implements _Success { return identical(this, other) || (other.runtimeType == runtimeType && other is _$SuccessImpl && - const DeepCollectionEquality() - .equals(other._productSales, _productSales)); + (identical(other.product, product) || other.product == product)); } @override - int get hashCode => Object.hash( - runtimeType, const DeepCollectionEquality().hash(_productSales)); + int get hashCode => Object.hash(runtimeType, product); /// Create a copy of ProductSalesState /// with the given fields replaced by the non-null parameter values. @@ -721,10 +716,10 @@ class _$SuccessImpl implements _Success { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List productSales) success, + required TResult Function(ProductAnalyticData product) success, required TResult Function(String message) error, }) { - return success(productSales); + return success(product); } @override @@ -732,10 +727,10 @@ class _$SuccessImpl implements _Success { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List productSales)? success, + TResult? Function(ProductAnalyticData product)? success, TResult? Function(String message)? error, }) { - return success?.call(productSales); + return success?.call(product); } @override @@ -743,12 +738,12 @@ class _$SuccessImpl implements _Success { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List productSales)? success, + TResult Function(ProductAnalyticData product)? success, TResult Function(String message)? error, required TResult orElse(), }) { if (success != null) { - return success(productSales); + return success(product); } return orElse(); } @@ -792,9 +787,9 @@ class _$SuccessImpl implements _Success { } abstract class _Success implements ProductSalesState { - const factory _Success(final List productSales) = _$SuccessImpl; + const factory _Success(final ProductAnalyticData product) = _$SuccessImpl; - List get productSales; + ProductAnalyticData get product; /// Create a copy of ProductSalesState /// with the given fields replaced by the non-null parameter values. @@ -873,7 +868,7 @@ class _$ErrorImpl implements _Error { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List productSales) success, + required TResult Function(ProductAnalyticData product) success, required TResult Function(String message) error, }) { return error(message); @@ -884,7 +879,7 @@ class _$ErrorImpl implements _Error { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List productSales)? success, + TResult? Function(ProductAnalyticData product)? success, TResult? Function(String message)? error, }) { return error?.call(message); @@ -895,7 +890,7 @@ class _$ErrorImpl implements _Error { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List productSales)? success, + TResult Function(ProductAnalyticData product)? success, TResult Function(String message)? error, required TResult orElse(), }) { diff --git a/lib/presentation/report/blocs/product_sales/product_sales_event.dart b/lib/presentation/report/blocs/product_sales/product_sales_event.dart index 8ff24f2..811a8ca 100644 --- a/lib/presentation/report/blocs/product_sales/product_sales_event.dart +++ b/lib/presentation/report/blocs/product_sales/product_sales_event.dart @@ -4,7 +4,7 @@ part of 'product_sales_bloc.dart'; class ProductSalesEvent with _$ProductSalesEvent { const factory ProductSalesEvent.started() = _Started; const factory ProductSalesEvent.getProductSales( - String startDate, - String endDate, + DateTime startDate, + DateTime endDate, ) = _GetProductSales; } diff --git a/lib/presentation/report/blocs/product_sales/product_sales_state.dart b/lib/presentation/report/blocs/product_sales/product_sales_state.dart index ddf4a39..0c42f67 100644 --- a/lib/presentation/report/blocs/product_sales/product_sales_state.dart +++ b/lib/presentation/report/blocs/product_sales/product_sales_state.dart @@ -6,7 +6,7 @@ class ProductSalesState with _$ProductSalesState { const factory ProductSalesState.loading() = _Loading; - const factory ProductSalesState.success(List productSales) = + const factory ProductSalesState.success(ProductAnalyticData product) = _Success; const factory ProductSalesState.error(String message) = _Error; diff --git a/lib/presentation/report/blocs/profit_loss/profit_loss_bloc.dart b/lib/presentation/report/blocs/profit_loss/profit_loss_bloc.dart new file mode 100644 index 0000000..5b5c4c4 --- /dev/null +++ b/lib/presentation/report/blocs/profit_loss/profit_loss_bloc.dart @@ -0,0 +1,22 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/analytic_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/response/profit_loss_response_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'profit_loss_event.dart'; +part 'profit_loss_state.dart'; +part 'profit_loss_bloc.freezed.dart'; + +class ProfitLossBloc extends Bloc { + final AnalyticRemoteDatasource datasource; + ProfitLossBloc(this.datasource) : super(ProfitLossState.initial()) { + on<_GetProfitLoss>((event, emit) async { + emit(const _Loading()); + final result = await datasource.getProfitLoss( + dateFrom: event.startDate, + dateTo: event.endDate, + ); + result.fold((l) => emit(_Error(l)), (r) => emit(_Success(r.data))); + }); + } +} diff --git a/lib/presentation/report/blocs/profit_loss/profit_loss_bloc.freezed.dart b/lib/presentation/report/blocs/profit_loss/profit_loss_bloc.freezed.dart new file mode 100644 index 0000000..6a1beeb --- /dev/null +++ b/lib/presentation/report/blocs/profit_loss/profit_loss_bloc.freezed.dart @@ -0,0 +1,864 @@ +// 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 'profit_loss_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$ProfitLossEvent { + DateTime get startDate => throw _privateConstructorUsedError; + DateTime get endDate => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(DateTime startDate, DateTime endDate) + getProfitLoss, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(DateTime startDate, DateTime endDate)? getProfitLoss, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(DateTime startDate, DateTime endDate)? getProfitLoss, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_GetProfitLoss value) getProfitLoss, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetProfitLoss value)? getProfitLoss, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetProfitLoss value)? getProfitLoss, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Create a copy of ProfitLossEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProfitLossEventCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProfitLossEventCopyWith<$Res> { + factory $ProfitLossEventCopyWith( + ProfitLossEvent value, $Res Function(ProfitLossEvent) then) = + _$ProfitLossEventCopyWithImpl<$Res, ProfitLossEvent>; + @useResult + $Res call({DateTime startDate, DateTime endDate}); +} + +/// @nodoc +class _$ProfitLossEventCopyWithImpl<$Res, $Val extends ProfitLossEvent> + implements $ProfitLossEventCopyWith<$Res> { + _$ProfitLossEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProfitLossEvent + /// 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 _$$GetProfitLossImplCopyWith<$Res> + implements $ProfitLossEventCopyWith<$Res> { + factory _$$GetProfitLossImplCopyWith( + _$GetProfitLossImpl value, $Res Function(_$GetProfitLossImpl) then) = + __$$GetProfitLossImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({DateTime startDate, DateTime endDate}); +} + +/// @nodoc +class __$$GetProfitLossImplCopyWithImpl<$Res> + extends _$ProfitLossEventCopyWithImpl<$Res, _$GetProfitLossImpl> + implements _$$GetProfitLossImplCopyWith<$Res> { + __$$GetProfitLossImplCopyWithImpl( + _$GetProfitLossImpl _value, $Res Function(_$GetProfitLossImpl) _then) + : super(_value, _then); + + /// Create a copy of ProfitLossEvent + /// 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(_$GetProfitLossImpl( + null == startDate + ? _value.startDate + : startDate // ignore: cast_nullable_to_non_nullable + as DateTime, + null == endDate + ? _value.endDate + : endDate // ignore: cast_nullable_to_non_nullable + as DateTime, + )); + } +} + +/// @nodoc + +class _$GetProfitLossImpl implements _GetProfitLoss { + const _$GetProfitLossImpl(this.startDate, this.endDate); + + @override + final DateTime startDate; + @override + final DateTime endDate; + + @override + String toString() { + return 'ProfitLossEvent.getProfitLoss(startDate: $startDate, endDate: $endDate)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$GetProfitLossImpl && + (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 ProfitLossEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$GetProfitLossImplCopyWith<_$GetProfitLossImpl> get copyWith => + __$$GetProfitLossImplCopyWithImpl<_$GetProfitLossImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(DateTime startDate, DateTime endDate) + getProfitLoss, + }) { + return getProfitLoss(startDate, endDate); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(DateTime startDate, DateTime endDate)? getProfitLoss, + }) { + return getProfitLoss?.call(startDate, endDate); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(DateTime startDate, DateTime endDate)? getProfitLoss, + required TResult orElse(), + }) { + if (getProfitLoss != null) { + return getProfitLoss(startDate, endDate); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetProfitLoss value) getProfitLoss, + }) { + return getProfitLoss(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetProfitLoss value)? getProfitLoss, + }) { + return getProfitLoss?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetProfitLoss value)? getProfitLoss, + required TResult orElse(), + }) { + if (getProfitLoss != null) { + return getProfitLoss(this); + } + return orElse(); + } +} + +abstract class _GetProfitLoss implements ProfitLossEvent { + const factory _GetProfitLoss( + final DateTime startDate, final DateTime endDate) = _$GetProfitLossImpl; + + @override + DateTime get startDate; + @override + DateTime get endDate; + + /// Create a copy of ProfitLossEvent + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GetProfitLossImplCopyWith<_$GetProfitLossImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$ProfitLossState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(ProfitLossData data) success, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(ProfitLossData data)? success, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(ProfitLossData data)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProfitLossStateCopyWith<$Res> { + factory $ProfitLossStateCopyWith( + ProfitLossState value, $Res Function(ProfitLossState) then) = + _$ProfitLossStateCopyWithImpl<$Res, ProfitLossState>; +} + +/// @nodoc +class _$ProfitLossStateCopyWithImpl<$Res, $Val extends ProfitLossState> + implements $ProfitLossStateCopyWith<$Res> { + _$ProfitLossStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProfitLossState + /// 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 _$ProfitLossStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of ProfitLossState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'ProfitLossState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(ProfitLossData data) success, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(ProfitLossData data)? success, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(ProfitLossData data)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements ProfitLossState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$ProfitLossStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of ProfitLossState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'ProfitLossState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(ProfitLossData data) success, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(ProfitLossData data)? success, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(ProfitLossData data)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class _Loading implements ProfitLossState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$SuccessImplCopyWith<$Res> { + factory _$$SuccessImplCopyWith( + _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = + __$$SuccessImplCopyWithImpl<$Res>; + @useResult + $Res call({ProfitLossData data}); +} + +/// @nodoc +class __$$SuccessImplCopyWithImpl<$Res> + extends _$ProfitLossStateCopyWithImpl<$Res, _$SuccessImpl> + implements _$$SuccessImplCopyWith<$Res> { + __$$SuccessImplCopyWithImpl( + _$SuccessImpl _value, $Res Function(_$SuccessImpl) _then) + : super(_value, _then); + + /// Create a copy of ProfitLossState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? data = null, + }) { + return _then(_$SuccessImpl( + null == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as ProfitLossData, + )); + } +} + +/// @nodoc + +class _$SuccessImpl implements _Success { + const _$SuccessImpl(this.data); + + @override + final ProfitLossData data; + + @override + String toString() { + return 'ProfitLossState.success(data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SuccessImpl && + (identical(other.data, data) || other.data == data)); + } + + @override + int get hashCode => Object.hash(runtimeType, data); + + /// Create a copy of ProfitLossState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SuccessImplCopyWith<_$SuccessImpl> get copyWith => + __$$SuccessImplCopyWithImpl<_$SuccessImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(ProfitLossData data) success, + required TResult Function(String message) error, + }) { + return success(data); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(ProfitLossData data)? success, + TResult? Function(String message)? error, + }) { + return success?.call(data); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(ProfitLossData data)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(data); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return success(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return success?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(this); + } + return orElse(); + } +} + +abstract class _Success implements ProfitLossState { + const factory _Success(final ProfitLossData data) = _$SuccessImpl; + + ProfitLossData get data; + + /// Create a copy of ProfitLossState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SuccessImplCopyWith<_$SuccessImpl> 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 _$ProfitLossStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of ProfitLossState + /// 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 'ProfitLossState.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 ProfitLossState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(ProfitLossData data) success, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(ProfitLossData data)? success, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(ProfitLossData data)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements ProfitLossState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of ProfitLossState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/report/blocs/profit_loss/profit_loss_event.dart b/lib/presentation/report/blocs/profit_loss/profit_loss_event.dart new file mode 100644 index 0000000..9a015b9 --- /dev/null +++ b/lib/presentation/report/blocs/profit_loss/profit_loss_event.dart @@ -0,0 +1,9 @@ +part of 'profit_loss_bloc.dart'; + +@freezed +class ProfitLossEvent with _$ProfitLossEvent { + const factory ProfitLossEvent.getProfitLoss( + DateTime startDate, + DateTime endDate, + ) = _GetProfitLoss; +} diff --git a/lib/presentation/report/blocs/profit_loss/profit_loss_state.dart b/lib/presentation/report/blocs/profit_loss/profit_loss_state.dart new file mode 100644 index 0000000..8388e6c --- /dev/null +++ b/lib/presentation/report/blocs/profit_loss/profit_loss_state.dart @@ -0,0 +1,9 @@ +part of 'profit_loss_bloc.dart'; + +@freezed +class ProfitLossState with _$ProfitLossState { + const factory ProfitLossState.initial() = _Initial; + const factory ProfitLossState.loading() = _Loading; + const factory ProfitLossState.success(ProfitLossData data) = _Success; + const factory ProfitLossState.error(String message) = _Error; +} diff --git a/lib/presentation/report/blocs/summary/summary_bloc.dart b/lib/presentation/report/blocs/summary/summary_bloc.dart index f48e5c9..fc3fee8 100644 --- a/lib/presentation/report/blocs/summary/summary_bloc.dart +++ b/lib/presentation/report/blocs/summary/summary_bloc.dart @@ -1,6 +1,6 @@ +import 'package:enaklo_pos/data/datasources/analytic_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/response/dashboard_analytic_response_model.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart'; -import 'package:enaklo_pos/data/models/response/summary_response_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'summary_event.dart'; @@ -8,15 +8,17 @@ part 'summary_state.dart'; part 'summary_bloc.freezed.dart'; class SummaryBloc extends Bloc { - final OrderRemoteDatasource datasource; + final AnalyticRemoteDatasource datasource; SummaryBloc( this.datasource, ) : super(const _Initial()) { on<_GetSummary>((event, emit) async { emit(const _Loading()); - final result = await datasource.getSummaryByRangeDate( - event.startDate, event.endDate); - result.fold((l) => emit(_Error(l)), (r) => emit(_Success(r.data!))); + final result = await datasource.getDashboard( + dateFrom: event.startDate, + dateTo: event.endDate, + ); + result.fold((l) => emit(_Error(l)), (r) => emit(_Success(r.data))); }); } } diff --git a/lib/presentation/report/blocs/summary/summary_bloc.freezed.dart b/lib/presentation/report/blocs/summary/summary_bloc.freezed.dart index 7bc2fe5..d6fefa0 100644 --- a/lib/presentation/report/blocs/summary/summary_bloc.freezed.dart +++ b/lib/presentation/report/blocs/summary/summary_bloc.freezed.dart @@ -19,19 +19,19 @@ mixin _$SummaryEvent { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(String startDate, String endDate) getSummary, + required TResult Function(DateTime startDate, DateTime endDate) getSummary, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(String startDate, String endDate)? getSummary, + TResult? Function(DateTime startDate, DateTime endDate)? getSummary, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(String startDate, String endDate)? getSummary, + TResult Function(DateTime startDate, DateTime endDate)? getSummary, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -119,7 +119,7 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(String startDate, String endDate) getSummary, + required TResult Function(DateTime startDate, DateTime endDate) getSummary, }) { return started(); } @@ -128,7 +128,7 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(String startDate, String endDate)? getSummary, + TResult? Function(DateTime startDate, DateTime endDate)? getSummary, }) { return started?.call(); } @@ -137,7 +137,7 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(String startDate, String endDate)? getSummary, + TResult Function(DateTime startDate, DateTime endDate)? getSummary, required TResult orElse(), }) { if (started != null) { @@ -188,7 +188,7 @@ abstract class _$$GetSummaryImplCopyWith<$Res> { _$GetSummaryImpl value, $Res Function(_$GetSummaryImpl) then) = __$$GetSummaryImplCopyWithImpl<$Res>; @useResult - $Res call({String startDate, String endDate}); + $Res call({DateTime startDate, DateTime endDate}); } /// @nodoc @@ -211,11 +211,11 @@ class __$$GetSummaryImplCopyWithImpl<$Res> null == startDate ? _value.startDate : startDate // ignore: cast_nullable_to_non_nullable - as String, + as DateTime, null == endDate ? _value.endDate : endDate // ignore: cast_nullable_to_non_nullable - as String, + as DateTime, )); } } @@ -226,9 +226,9 @@ class _$GetSummaryImpl implements _GetSummary { const _$GetSummaryImpl(this.startDate, this.endDate); @override - final String startDate; + final DateTime startDate; @override - final String endDate; + final DateTime endDate; @override String toString() { @@ -260,7 +260,7 @@ class _$GetSummaryImpl implements _GetSummary { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(String startDate, String endDate) getSummary, + required TResult Function(DateTime startDate, DateTime endDate) getSummary, }) { return getSummary(startDate, endDate); } @@ -269,7 +269,7 @@ class _$GetSummaryImpl implements _GetSummary { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(String startDate, String endDate)? getSummary, + TResult? Function(DateTime startDate, DateTime endDate)? getSummary, }) { return getSummary?.call(startDate, endDate); } @@ -278,7 +278,7 @@ class _$GetSummaryImpl implements _GetSummary { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(String startDate, String endDate)? getSummary, + TResult Function(DateTime startDate, DateTime endDate)? getSummary, required TResult orElse(), }) { if (getSummary != null) { @@ -320,11 +320,11 @@ class _$GetSummaryImpl implements _GetSummary { } abstract class _GetSummary implements SummaryEvent { - const factory _GetSummary(final String startDate, final String endDate) = + const factory _GetSummary(final DateTime startDate, final DateTime endDate) = _$GetSummaryImpl; - String get startDate; - String get endDate; + DateTime get startDate; + DateTime get endDate; /// Create a copy of SummaryEvent /// with the given fields replaced by the non-null parameter values. @@ -339,7 +339,7 @@ mixin _$SummaryState { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(SummaryModel summary) success, + required TResult Function(DashboardAnalyticData data) success, required TResult Function(String message) error, }) => throw _privateConstructorUsedError; @@ -347,7 +347,7 @@ mixin _$SummaryState { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(SummaryModel summary)? success, + TResult? Function(DashboardAnalyticData data)? success, TResult? Function(String message)? error, }) => throw _privateConstructorUsedError; @@ -355,7 +355,7 @@ mixin _$SummaryState { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(SummaryModel summary)? success, + TResult Function(DashboardAnalyticData data)? success, TResult Function(String message)? error, required TResult orElse(), }) => @@ -451,7 +451,7 @@ class _$InitialImpl implements _Initial { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(SummaryModel summary) success, + required TResult Function(DashboardAnalyticData data) success, required TResult Function(String message) error, }) { return initial(); @@ -462,7 +462,7 @@ class _$InitialImpl implements _Initial { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(SummaryModel summary)? success, + TResult? Function(DashboardAnalyticData data)? success, TResult? Function(String message)? error, }) { return initial?.call(); @@ -473,7 +473,7 @@ class _$InitialImpl implements _Initial { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(SummaryModel summary)? success, + TResult Function(DashboardAnalyticData data)? success, TResult Function(String message)? error, required TResult orElse(), }) { @@ -568,7 +568,7 @@ class _$LoadingImpl implements _Loading { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(SummaryModel summary) success, + required TResult Function(DashboardAnalyticData data) success, required TResult Function(String message) error, }) { return loading(); @@ -579,7 +579,7 @@ class _$LoadingImpl implements _Loading { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(SummaryModel summary)? success, + TResult? Function(DashboardAnalyticData data)? success, TResult? Function(String message)? error, }) { return loading?.call(); @@ -590,7 +590,7 @@ class _$LoadingImpl implements _Loading { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(SummaryModel summary)? success, + TResult Function(DashboardAnalyticData data)? success, TResult Function(String message)? error, required TResult orElse(), }) { @@ -648,7 +648,7 @@ abstract class _$$SuccessImplCopyWith<$Res> { _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = __$$SuccessImplCopyWithImpl<$Res>; @useResult - $Res call({SummaryModel summary}); + $Res call({DashboardAnalyticData data}); } /// @nodoc @@ -664,13 +664,13 @@ class __$$SuccessImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? summary = null, + Object? data = null, }) { return _then(_$SuccessImpl( - null == summary - ? _value.summary - : summary // ignore: cast_nullable_to_non_nullable - as SummaryModel, + null == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as DashboardAnalyticData, )); } } @@ -678,14 +678,14 @@ class __$$SuccessImplCopyWithImpl<$Res> /// @nodoc class _$SuccessImpl implements _Success { - const _$SuccessImpl(this.summary); + const _$SuccessImpl(this.data); @override - final SummaryModel summary; + final DashboardAnalyticData data; @override String toString() { - return 'SummaryState.success(summary: $summary)'; + return 'SummaryState.success(data: $data)'; } @override @@ -693,11 +693,11 @@ class _$SuccessImpl implements _Success { return identical(this, other) || (other.runtimeType == runtimeType && other is _$SuccessImpl && - (identical(other.summary, summary) || other.summary == summary)); + (identical(other.data, data) || other.data == data)); } @override - int get hashCode => Object.hash(runtimeType, summary); + int get hashCode => Object.hash(runtimeType, data); /// Create a copy of SummaryState /// with the given fields replaced by the non-null parameter values. @@ -712,10 +712,10 @@ class _$SuccessImpl implements _Success { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(SummaryModel summary) success, + required TResult Function(DashboardAnalyticData data) success, required TResult Function(String message) error, }) { - return success(summary); + return success(data); } @override @@ -723,10 +723,10 @@ class _$SuccessImpl implements _Success { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(SummaryModel summary)? success, + TResult? Function(DashboardAnalyticData data)? success, TResult? Function(String message)? error, }) { - return success?.call(summary); + return success?.call(data); } @override @@ -734,12 +734,12 @@ class _$SuccessImpl implements _Success { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(SummaryModel summary)? success, + TResult Function(DashboardAnalyticData data)? success, TResult Function(String message)? error, required TResult orElse(), }) { if (success != null) { - return success(summary); + return success(data); } return orElse(); } @@ -783,9 +783,9 @@ class _$SuccessImpl implements _Success { } abstract class _Success implements SummaryState { - const factory _Success(final SummaryModel summary) = _$SuccessImpl; + const factory _Success(final DashboardAnalyticData data) = _$SuccessImpl; - SummaryModel get summary; + DashboardAnalyticData get data; /// Create a copy of SummaryState /// with the given fields replaced by the non-null parameter values. @@ -864,7 +864,7 @@ class _$ErrorImpl implements _Error { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(SummaryModel summary) success, + required TResult Function(DashboardAnalyticData data) success, required TResult Function(String message) error, }) { return error(message); @@ -875,7 +875,7 @@ class _$ErrorImpl implements _Error { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(SummaryModel summary)? success, + TResult? Function(DashboardAnalyticData data)? success, TResult? Function(String message)? error, }) { return error?.call(message); @@ -886,7 +886,7 @@ class _$ErrorImpl implements _Error { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(SummaryModel summary)? success, + TResult Function(DashboardAnalyticData data)? success, TResult Function(String message)? error, required TResult orElse(), }) { diff --git a/lib/presentation/report/blocs/summary/summary_event.dart b/lib/presentation/report/blocs/summary/summary_event.dart index febbb48..e306a89 100644 --- a/lib/presentation/report/blocs/summary/summary_event.dart +++ b/lib/presentation/report/blocs/summary/summary_event.dart @@ -3,6 +3,6 @@ part of 'summary_bloc.dart'; @freezed class SummaryEvent with _$SummaryEvent { const factory SummaryEvent.started() = _Started; - const factory SummaryEvent.getSummary(String startDate, String endDate) = + const factory SummaryEvent.getSummary(DateTime startDate, DateTime endDate) = _GetSummary; } diff --git a/lib/presentation/report/blocs/summary/summary_state.dart b/lib/presentation/report/blocs/summary/summary_state.dart index 37dd6e8..e207d9e 100644 --- a/lib/presentation/report/blocs/summary/summary_state.dart +++ b/lib/presentation/report/blocs/summary/summary_state.dart @@ -4,6 +4,6 @@ part of 'summary_bloc.dart'; class SummaryState with _$SummaryState { const factory SummaryState.initial() = _Initial; const factory SummaryState.loading() = _Loading; - const factory SummaryState.success(SummaryModel summary) = _Success; + const factory SummaryState.success(DashboardAnalyticData data) = _Success; const factory SummaryState.error(String message) = _Error; } diff --git a/lib/presentation/report/blocs/transaction_report/transaction_report_bloc.dart b/lib/presentation/report/blocs/transaction_report/transaction_report_bloc.dart index 900ef15..26f28d9 100644 --- a/lib/presentation/report/blocs/transaction_report/transaction_report_bloc.dart +++ b/lib/presentation/report/blocs/transaction_report/transaction_report_bloc.dart @@ -1,7 +1,6 @@ - +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart'; -import 'package:enaklo_pos/data/models/response/order_remote_datasource.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'transaction_report_event.dart'; @@ -19,7 +18,8 @@ class TransactionReportBloc event.endDate, ); - result.fold((l) => emit(_Error(l)), (r) => emit(_Loaded(r.data!))); + result.fold( + (l) => emit(_Error(l)), (r) => emit(_Loaded(r.data!.orders!))); }); } } diff --git a/lib/presentation/report/blocs/transaction_report/transaction_report_bloc.freezed.dart b/lib/presentation/report/blocs/transaction_report/transaction_report_bloc.freezed.dart index 08db8b8..f2878d4 100644 --- a/lib/presentation/report/blocs/transaction_report/transaction_report_bloc.freezed.dart +++ b/lib/presentation/report/blocs/transaction_report/transaction_report_bloc.freezed.dart @@ -342,7 +342,7 @@ mixin _$TransactionReportState { required TResult Function() initial, required TResult Function() loading, required TResult Function(String message) error, - required TResult Function(List transactionReport) loaded, + required TResult Function(List transactionReport) loaded, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -350,7 +350,7 @@ mixin _$TransactionReportState { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(String message)? error, - TResult? Function(List transactionReport)? loaded, + TResult? Function(List transactionReport)? loaded, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -358,7 +358,7 @@ mixin _$TransactionReportState { TResult Function()? initial, TResult Function()? loading, TResult Function(String message)? error, - TResult Function(List transactionReport)? loaded, + TResult Function(List transactionReport)? loaded, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -455,7 +455,7 @@ class _$InitialImpl implements _Initial { required TResult Function() initial, required TResult Function() loading, required TResult Function(String message) error, - required TResult Function(List transactionReport) loaded, + required TResult Function(List transactionReport) loaded, }) { return initial(); } @@ -466,7 +466,7 @@ class _$InitialImpl implements _Initial { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(String message)? error, - TResult? Function(List transactionReport)? loaded, + TResult? Function(List transactionReport)? loaded, }) { return initial?.call(); } @@ -477,7 +477,7 @@ class _$InitialImpl implements _Initial { TResult Function()? initial, TResult Function()? loading, TResult Function(String message)? error, - TResult Function(List transactionReport)? loaded, + TResult Function(List transactionReport)? loaded, required TResult orElse(), }) { if (initial != null) { @@ -572,7 +572,7 @@ class _$LoadingImpl implements _Loading { required TResult Function() initial, required TResult Function() loading, required TResult Function(String message) error, - required TResult Function(List transactionReport) loaded, + required TResult Function(List transactionReport) loaded, }) { return loading(); } @@ -583,7 +583,7 @@ class _$LoadingImpl implements _Loading { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(String message)? error, - TResult? Function(List transactionReport)? loaded, + TResult? Function(List transactionReport)? loaded, }) { return loading?.call(); } @@ -594,7 +594,7 @@ class _$LoadingImpl implements _Loading { TResult Function()? initial, TResult Function()? loading, TResult Function(String message)? error, - TResult Function(List transactionReport)? loaded, + TResult Function(List transactionReport)? loaded, required TResult orElse(), }) { if (loading != null) { @@ -716,7 +716,7 @@ class _$ErrorImpl implements _Error { required TResult Function() initial, required TResult Function() loading, required TResult Function(String message) error, - required TResult Function(List transactionReport) loaded, + required TResult Function(List transactionReport) loaded, }) { return error(message); } @@ -727,7 +727,7 @@ class _$ErrorImpl implements _Error { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(String message)? error, - TResult? Function(List transactionReport)? loaded, + TResult? Function(List transactionReport)? loaded, }) { return error?.call(message); } @@ -738,7 +738,7 @@ class _$ErrorImpl implements _Error { TResult Function()? initial, TResult Function()? loading, TResult Function(String message)? error, - TResult Function(List transactionReport)? loaded, + TResult Function(List transactionReport)? loaded, required TResult orElse(), }) { if (error != null) { @@ -803,7 +803,7 @@ abstract class _$$LoadedImplCopyWith<$Res> { _$LoadedImpl value, $Res Function(_$LoadedImpl) then) = __$$LoadedImplCopyWithImpl<$Res>; @useResult - $Res call({List transactionReport}); + $Res call({List transactionReport}); } /// @nodoc @@ -825,7 +825,7 @@ class __$$LoadedImplCopyWithImpl<$Res> null == transactionReport ? _value._transactionReport : transactionReport // ignore: cast_nullable_to_non_nullable - as List, + as List, )); } } @@ -833,12 +833,12 @@ class __$$LoadedImplCopyWithImpl<$Res> /// @nodoc class _$LoadedImpl implements _Loaded { - const _$LoadedImpl(final List transactionReport) + const _$LoadedImpl(final List transactionReport) : _transactionReport = transactionReport; - final List _transactionReport; + final List _transactionReport; @override - List get transactionReport { + List get transactionReport { if (_transactionReport is EqualUnmodifiableListView) return _transactionReport; // ignore: implicit_dynamic_type @@ -877,7 +877,7 @@ class _$LoadedImpl implements _Loaded { required TResult Function() initial, required TResult Function() loading, required TResult Function(String message) error, - required TResult Function(List transactionReport) loaded, + required TResult Function(List transactionReport) loaded, }) { return loaded(transactionReport); } @@ -888,7 +888,7 @@ class _$LoadedImpl implements _Loaded { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(String message)? error, - TResult? Function(List transactionReport)? loaded, + TResult? Function(List transactionReport)? loaded, }) { return loaded?.call(transactionReport); } @@ -899,7 +899,7 @@ class _$LoadedImpl implements _Loaded { TResult Function()? initial, TResult Function()? loading, TResult Function(String message)? error, - TResult Function(List transactionReport)? loaded, + TResult Function(List transactionReport)? loaded, required TResult orElse(), }) { if (loaded != null) { @@ -947,9 +947,9 @@ class _$LoadedImpl implements _Loaded { } abstract class _Loaded implements TransactionReportState { - const factory _Loaded(final List transactionReport) = _$LoadedImpl; + const factory _Loaded(final List transactionReport) = _$LoadedImpl; - List get transactionReport; + List get transactionReport; /// Create a copy of TransactionReportState /// with the given fields replaced by the non-null parameter values. diff --git a/lib/presentation/report/blocs/transaction_report/transaction_report_state.dart b/lib/presentation/report/blocs/transaction_report/transaction_report_state.dart index 7f8f81d..fc870a5 100644 --- a/lib/presentation/report/blocs/transaction_report/transaction_report_state.dart +++ b/lib/presentation/report/blocs/transaction_report/transaction_report_state.dart @@ -5,6 +5,6 @@ class TransactionReportState with _$TransactionReportState { const factory TransactionReportState.initial() = _Initial; const factory TransactionReportState.loading() = _Loading; const factory TransactionReportState.error(String message) = _Error; - const factory TransactionReportState.loaded( - List transactionReport) = _Loaded; + const factory TransactionReportState.loaded(List transactionReport) = + _Loaded; } diff --git a/lib/presentation/report/pages/report_page.dart b/lib/presentation/report/pages/report_page.dart index 2bf9c10..4f1778c 100644 --- a/lib/presentation/report/pages/report_page.dart +++ b/lib/presentation/report/pages/report_page.dart @@ -1,5 +1,10 @@ import 'dart:developer'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/presentation/report/blocs/profit_loss/profit_loss_bloc.dart'; +import 'package:enaklo_pos/presentation/report/widgets/dashboard_analytic_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:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/core/components/custom_date_picker.dart'; import 'package:enaklo_pos/core/constants/colors.dart'; @@ -12,11 +17,10 @@ import 'package:enaklo_pos/presentation/report/blocs/summary/summary_bloc.dart'; import 'package:enaklo_pos/presentation/report/blocs/transaction_report/transaction_report_bloc.dart'; import 'package:enaklo_pos/presentation/report/widgets/item_sales_report_widget.dart'; import 'package:enaklo_pos/presentation/report/widgets/payment_method_report_widget.dart'; -import 'package:enaklo_pos/presentation/report/widgets/product_sales_chart_widget.dart'; +import 'package:enaklo_pos/presentation/report/widgets/product_analytic_widget.dart'; import 'package:enaklo_pos/presentation/report/widgets/report_menu.dart'; import 'package:enaklo_pos/presentation/report/widgets/report_title.dart'; import 'package:flutter/material.dart'; -import 'package:enaklo_pos/presentation/report/widgets/summary_report_widget.dart'; import 'package:enaklo_pos/presentation/report/widgets/transaction_report_widget.dart'; import '../../../core/components/spaces.dart'; @@ -30,268 +34,336 @@ class ReportPage extends StatefulWidget { class _ReportPageState extends State { int selectedMenu = 0; - String title = 'Transaction Report'; + String title = 'Ringkasan Laporan Penjualan'; DateTime fromDate = DateTime.now().subtract(const Duration(days: 30)); DateTime toDate = DateTime.now(); + @override + void initState() { + super.initState(); + context.read().add( + SummaryEvent.getSummary(fromDate, toDate), + ); + } + @override Widget build(BuildContext context) { String searchDateFormatted = '${fromDate.toFormattedDate2()} to ${toDate.toFormattedDate2()}'; return Scaffold( - body: Row( + backgroundColor: AppColors.background, + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - // LEFT CONTENT - Expanded( - flex: 2, - child: Align( - alignment: Alignment.topLeft, - child: SingleChildScrollView( - padding: const EdgeInsets.all(24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const ReportTitle(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: CustomDatePicker( - prefix: const Text('From: '), - initialDate: fromDate, - onDateSelected: (selectedDate) { - fromDate = selectedDate; + ReportTitle( + actionWidget: [ + SizedBox( + width: 300, + child: CustomDatePicker( + prefix: const Text('From: '), + initialDate: fromDate, + onDateSelected: (selectedDate) { + fromDate = selectedDate; - setState(() {}); - }, - ), - ), - const SpaceWidth(24.0), - Flexible( - child: CustomDatePicker( - prefix: const Text('To: '), - initialDate: toDate, - onDateSelected: (selectedDate) { - toDate = selectedDate; - setState(() {}); - // context.read().add( - // TransactionReportEvent.getReport( - // startDate: - // DateFormatter.formatDateTime( - // fromDate), - // endDate: DateFormatter.formatDateTime( - // toDate)), - // ); - // context.read().add( - // ItemSalesReportEvent.getItemSales( - // startDate: - // DateFormatter.formatDateTime( - // fromDate), - // endDate: DateFormatter.formatDateTime( - // toDate)), - // ); - }, - ), - ), - ], - ), - Padding( - padding: const EdgeInsets.all(15.0), - child: Wrap( - children: [ - ReportMenu( - label: 'Transaction Report', - onPressed: () { - selectedMenu = 0; - title = 'Transaction Report'; - setState(() {}); - //enddate is 1 month before the current date - context.read().add( - TransactionReportEvent.getReport( - startDate: DateFormatter.formatDateTime( - fromDate), - endDate: DateFormatter.formatDateTime( - toDate)), - ); - }, - isActive: selectedMenu == 0, - ), - ReportMenu( - label: 'Item Sales Report', - onPressed: () { - selectedMenu = 1; - title = 'Item Sales Report'; - setState(() {}); - context.read().add( - ItemSalesReportEvent.getItemSales( - startDate: DateFormatter.formatDateTime( - fromDate), - endDate: DateFormatter.formatDateTime( - toDate)), - ); - }, - isActive: selectedMenu == 1, - ), - ReportMenu( - label: 'Product Sales Chart', - onPressed: () { - selectedMenu = 2; - title = 'Product Sales Chart'; - setState(() {}); - context.read().add( - ProductSalesEvent.getProductSales( - DateFormatter.formatDateTime(fromDate), - DateFormatter.formatDateTime(toDate)), - ); - }, - isActive: selectedMenu == 2, - ), - ReportMenu( - label: 'Summary Sales Report', - onPressed: () { - selectedMenu = 3; - title = 'Summary Sales Report'; - setState(() {}); - context.read().add( - SummaryEvent.getSummary( - DateFormatter.formatDateTime(fromDate), - DateFormatter.formatDateTime(toDate)), - ); - - log("Date ${DateFormatter.formatDateTime(fromDate)}"); - }, - isActive: selectedMenu == 3, - ), - ReportMenu( - label: 'Payment Method Report', - onPressed: () { - selectedMenu = 4; - title = 'Payment Method Report'; - setState(() {}); - context.read().add( - PaymentMethodReportEvent.getPaymentMethodReport( - startDate: DateFormatter.formatDateTime(fromDate), - endDate: DateFormatter.formatDateTime(toDate)), - ); - }, - isActive: selectedMenu == 4, - ), - ], - ), - ), - ], + setState(() {}); + }, ), ), + const SpaceWidth(24.0), + SizedBox( + width: 300, + child: CustomDatePicker( + prefix: const Text('To: '), + initialDate: toDate, + onDateSelected: (selectedDate) { + toDate = selectedDate; + setState(() {}); + // context.read().add( + // TransactionReportEvent.getReport( + // startDate: + // DateFormatter.formatDateTime( + // fromDate), + // endDate: DateFormatter.formatDateTime( + // toDate)), + // ); + // context.read().add( + // ItemSalesReportEvent.getItemSales( + // startDate: + // DateFormatter.formatDateTime( + // fromDate), + // endDate: DateFormatter.formatDateTime( + // toDate)), + // ); + }, + ), + ), + ], + ), + Expanded( + child: Row( + children: [ + // LEFT CONTENT + Expanded( + flex: 2, + child: Material( + color: AppColors.white, + child: Align( + alignment: Alignment.topLeft, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ReportMenu( + label: 'Ringkasan Laporan Penjualan', + subtitle: + 'Ringkasan total penjualan dalam periode tertentu.', + icon: Icons.insert_drive_file_outlined, + onPressed: () { + selectedMenu = 0; + title = 'Ringkasan Laporan Penjualan'; + setState(() {}); + context.read().add( + SummaryEvent.getSummary(fromDate, toDate), + ); + + log("Date ${DateFormatter.formatDateTime(fromDate)}"); + }, + isActive: selectedMenu == 0, + ), + ReportMenu( + label: 'Laporan Transaksi', + subtitle: + 'Menampilkan riwayat lengkap semua transaksi yang telah dilakukan.', + icon: Icons.receipt_long_outlined, + onPressed: () { + context.push(SalesPage(status: 'completed')); + }, + isActive: selectedMenu == 1, + ), + ReportMenu( + label: 'Laporan Penjualan Item', + subtitle: + 'Laporan penjualan berdasarkan masing-masing item atau produk.', + icon: Icons.inventory_2_outlined, + onPressed: () { + selectedMenu = 2; + title = 'Laporan Penjualan Item'; + setState(() {}); + context.read().add( + ItemSalesReportEvent.getItemSales( + startDate: fromDate, endDate: toDate), + ); + }, + isActive: selectedMenu == 2, + ), + ReportMenu( + label: 'Laporan Penjualan Produk', + subtitle: + 'Laporan penjualan berdasarkan masing-masing produk.', + icon: Icons.bar_chart_outlined, + onPressed: () { + selectedMenu = 3; + title = 'Laporan Penjualan Produk'; + setState(() {}); + context.read().add( + ProductSalesEvent.getProductSales( + fromDate, + toDate, + ), + ); + }, + isActive: selectedMenu == 3, + ), + ReportMenu( + label: 'Laporan Metode Pembayaran', + subtitle: + 'Laporan metode pembayaran yang digunakan.', + icon: Icons.payment_outlined, + onPressed: () { + selectedMenu = 4; + title = 'Laporan Metode Pembayaran'; + setState(() {}); + context.read().add( + PaymentMethodReportEvent + .getPaymentMethodReport( + startDate: fromDate, + endDate: toDate, + ), + ); + }, + isActive: selectedMenu == 4, + ), + ReportMenu( + label: 'Laporan untung rugi', + subtitle: 'Laporan untung rugi penjualan.', + icon: Icons.trending_down, + onPressed: () { + selectedMenu = 5; + title = 'Laporan untung rugi'; + setState(() {}); + context.read().add( + ProfitLossEvent.getProfitLoss( + fromDate, + toDate, + ), + ); + }, + isActive: selectedMenu == 5, + ), + ], + ), + ), + ), + ), + ), + + // RIGHT CONTENT + Expanded( + flex: 4, + child: selectedMenu == 0 + ? BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const Center( + child: CircularProgressIndicator(), + ), + error: (message) { + return Text(message); + }, + success: (data) { + return DashboardAnalyticWidget( + data: data, + title: title, + searchDateFormatted: searchDateFormatted, + ); + }, + ); + }, + ) + : selectedMenu == 1 + ? BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const Center( + child: CircularProgressIndicator(), + ), + error: (message) { + return Text(message); + }, + loaded: (transactionReport) { + return TransactionReportWidget( + transactionReport: transactionReport, + title: title, + searchDateFormatted: + searchDateFormatted, + headerWidgets: + _getTitleReportPageWidget(), + ); + }, + ); + }, + ) + : selectedMenu == 2 + ? BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const Center( + child: CircularProgressIndicator(), + ), + error: (message) { + return Text(message); + }, + loaded: (itemSales) { + return ItemSalesReportWidget( + sales: itemSales, + title: title, + searchDateFormatted: + searchDateFormatted, + headerWidgets: + _getItemSalesPageWidget(), + ); + }, + ); + }, + ) + : selectedMenu == 3 + ? BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const Center( + child: + CircularProgressIndicator(), + ), + error: (message) { + return Text(message); + }, + success: (products) { + return ProductAnalyticsWidget( + title: title, + searchDateFormatted: + searchDateFormatted, + productData: products, + ); + }, + ); + }, + ) + : selectedMenu == 4 + ? BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const Center( + child: + CircularProgressIndicator(), + ), + error: (message) { + return Text(message); + }, + loaded: (paymentMethodData) { + return PaymentMethodReportWidget( + paymentMethodData: + paymentMethodData, + title: title, + searchDateFormatted: + searchDateFormatted, + headerWidgets: + _getPaymentMethodPageWidget(), + ); + }, + ); + }, + ) + : selectedMenu == 5 + ? BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const Center( + child: + CircularProgressIndicator(), + ), + error: (message) { + return Text(message); + }, + success: (data) { + return ProfitLossWidget( + data: data, + title: title, + searchDateFormatted: + searchDateFormatted, + ); + }, + ); + }, + ) + : const SizedBox.shrink()), + ], ), ), - - // RIGHT CONTENT - Expanded( - flex: 2, - child: selectedMenu == 0 - ? BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => const Center( - child: CircularProgressIndicator(), - ), - error: (message) { - return Text(message); - }, - loaded: (transactionReport) { - return TransactionReportWidget( - transactionReport: transactionReport, - title: title, - searchDateFormatted: searchDateFormatted, - headerWidgets: _getTitleReportPageWidget(), - ); - }, - ); - }, - ) - : selectedMenu == 1 - ? BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => const Center( - child: CircularProgressIndicator(), - ), - error: (message) { - return Text(message); - }, - loaded: (itemSales) { - return ItemSalesReportWidget( - itemSales: itemSales, - title: title, - searchDateFormatted: searchDateFormatted, - headerWidgets: _getItemSalesPageWidget(), - ); - }, - ); - }, - ) - : selectedMenu == 2 - ? BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => const Center( - child: CircularProgressIndicator(), - ), - error: (message) { - return Text(message); - }, - success: (productSales) { - return ProductSalesChartWidgets( - title: title, - searchDateFormatted: searchDateFormatted, - productSales: productSales, - ); - }, - ); - }, - ) - : selectedMenu == 3 - ? BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => const Center( - child: CircularProgressIndicator(), - ), - error: (message) { - return Text(message); - }, - success: (summary) { - return SummaryReportWidget( - summary: summary, - title: title, - searchDateFormatted: searchDateFormatted, - ); - }, - ); - }, - ) - : selectedMenu == 4 - ? BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => const Center( - child: CircularProgressIndicator(), - ), - error: (message) { - return Text(message); - }, - loaded: (paymentMethodData) { - return PaymentMethodReportWidget( - paymentMethodData: paymentMethodData, - title: title, - searchDateFormatted: searchDateFormatted, - headerWidgets: _getPaymentMethodPageWidget(), - ); - }, - ); - }, - ) - : const SizedBox.shrink()), ], ), ); @@ -314,11 +386,11 @@ class _ReportPageState extends State { List _getItemSalesPageWidget() { return [ _getTitleItemWidget('ID', 80), - _getTitleItemWidget('Order', 60), - _getTitleItemWidget('Product', 160), + _getTitleItemWidget('Order', 100), + _getTitleItemWidget('Product', 200), _getTitleItemWidget('Qty', 60), - _getTitleItemWidget('Price', 140), - _getTitleItemWidget('Total Price', 140), + _getTitleItemWidget('Price', 150), + _getTitleItemWidget('Total Price', 160), ]; } diff --git a/lib/presentation/report/widgets/dashboard_analytic_widget.dart b/lib/presentation/report/widgets/dashboard_analytic_widget.dart new file mode 100644 index 0000000..ac1a1f8 --- /dev/null +++ b/lib/presentation/report/widgets/dashboard_analytic_widget.dart @@ -0,0 +1,639 @@ +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/data/models/response/dashboard_analytic_response_model.dart'; +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:intl/intl.dart'; + +// App Colors +class AppColorDashboard { + static const secondary = Color(0xff7c3aed); + static const success = Color(0xff10b981); + static const warning = Color(0xfff59e0b); + static const danger = Color(0xffef4444); + static const info = Color(0xff3b82f6); +} + +class DashboardAnalyticWidget extends StatelessWidget { + final String title; + final String searchDateFormatted; + final DashboardAnalyticData data; + + const DashboardAnalyticWidget({ + super.key, + required this.data, + required this.title, + required this.searchDateFormatted, + }); + + @override + Widget build(BuildContext context) { + return Container( + color: const Color(0xFFF8FAFC), + padding: const EdgeInsets.all(24.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(), + const SizedBox(height: 24), + _buildKPICards(), + const SizedBox(height: 24), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(flex: 2, child: _buildSalesChart()), + const SizedBox(width: 16), + Expanded(flex: 1, child: _buildProductChart()), + ], + ), + const SizedBox(height: 24), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(flex: 2, child: _buildTopProductsList()), + const SizedBox(width: 16), + Expanded(flex: 1, child: _buildOrderSummary()), + ], + ), + ], + ), + ), + ); + } + + Widget _buildHeader() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF1F2937), + ), + ), + const SizedBox(height: 4), + Text( + 'Analisis performa penjualan outlet', + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ], + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: AppColors.primary, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.calendar_today, color: Colors.white, size: 16), + const SizedBox(width: 8), + Text( + searchDateFormatted, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildKPICards() { + final successfulOrders = data.overview.totalOrders - + data.overview.voidedOrders - + data.overview.refundedOrders; + + final kpiData = [ + { + 'title': 'Total Penjualan', + 'value': _formatCurrency(data.overview.totalSales), + 'icon': Icons.trending_up, + 'color': AppColorDashboard.success, + 'bgColor': AppColorDashboard.success.withOpacity(0.1), + }, + { + 'title': 'Total Pesanan', + 'value': '${data.overview.totalOrders}', + 'icon': Icons.shopping_cart, + 'color': AppColorDashboard.info, + 'bgColor': AppColorDashboard.info.withOpacity(0.1), + }, + { + 'title': 'Rata-rata Pesanan', + 'value': _formatCurrency(data.overview.averageOrderValue.toInt()), + 'icon': Icons.attach_money, + 'color': AppColorDashboard.warning, + 'bgColor': AppColorDashboard.warning.withOpacity(0.1), + }, + { + 'title': 'Pesanan Sukses', + 'value': '$successfulOrders', + 'icon': Icons.check_circle, + 'color': AppColors.primary, + 'bgColor': AppColors.primary.withOpacity(0.1), + }, + ]; + + return Row( + children: kpiData.map((kpi) { + return Expanded( + child: Container( + margin: const EdgeInsets.only(right: 16), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: kpi['bgColor'] as Color, + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + kpi['icon'] as IconData, + color: kpi['color'] as Color, + size: 20, + ), + ), + Icon( + Icons.trending_up, + color: Colors.grey[400], + size: 16, + ), + ], + ), + const SizedBox(height: 16), + Text( + kpi['value'] as String, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Color(0xFF1F2937), + ), + ), + const SizedBox(height: 4), + Text( + kpi['title'] as String, + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ); + }).toList(), + ); + } + + Widget _buildSalesChart() { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Tren Penjualan Harian', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF1F2937), + ), + ), + const SizedBox(height: 20), + SizedBox( + height: 200, + child: LineChart( + LineChartData( + gridData: FlGridData( + show: true, + drawHorizontalLine: true, + drawVerticalLine: false, + horizontalInterval: 200000, + getDrawingHorizontalLine: (value) { + return FlLine( + color: Colors.grey[200]!, + strokeWidth: 1, + ); + }, + ), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 60, + getTitlesWidget: (value, meta) { + return Text( + '${(value / 1000).toInt()}K', + style: TextStyle( + color: Colors.grey[600], + fontSize: 10, + ), + ); + }, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + final index = value.toInt(); + if (index >= 0 && index < data.recentSales.length) { + final date = + DateTime.parse(data.recentSales[index].date); + final formatter = DateFormat('dd MMM'); + return Padding( + padding: const EdgeInsets.only(top: 8), + child: Text( + formatter.format(date), + style: TextStyle( + color: Colors.grey[600], + fontSize: 10, + ), + ), + ); + } + return const SizedBox(); + }, + ), + ), + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false)), + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false)), + ), + borderData: FlBorderData(show: false), + lineBarsData: [ + LineChartBarData( + spots: data.recentSales.asMap().entries.map((entry) { + return FlSpot( + entry.key.toDouble(), entry.value.sales.toDouble()); + }).toList(), + isCurved: true, + color: AppColors.primary, + // strokeWidth: 3, + dotData: const FlDotData(show: true), + belowBarData: BarAreaData( + show: true, + color: AppColors.primary.withOpacity(0.1), + ), + ), + ], + ), + ), + ), + ], + ), + ); + } + + Widget _buildProductChart() { + final colors = [ + AppColors.primary, + AppColorDashboard.secondary, + AppColorDashboard.info, + AppColorDashboard.warning, + AppColorDashboard.success, + ]; + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Distribusi Produk', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF1F2937), + ), + ), + const SizedBox(height: 20), + SizedBox( + height: 160, + child: PieChart( + PieChartData( + sectionsSpace: 2, + centerSpaceRadius: 40, + sections: data.topProducts.asMap().entries.map((entry) { + return PieChartSectionData( + color: colors[entry.key % colors.length], + value: entry.value.quantitySold.toDouble(), + title: '${entry.value.quantitySold}', + radius: 60, + titleStyle: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ); + }).toList(), + ), + ), + ), + const SizedBox(height: 16), + Column( + children: data.topProducts.take(3).map((product) { + final index = data.topProducts.indexOf(product); + + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: colors[index % colors.length], + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + product.productName, + style: const TextStyle(fontSize: 12), + ), + ), + Text( + '${product.quantitySold}', + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + }).toList(), + ), + ], + ), + ); + } + + Widget _buildTopProductsList() { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Produk Terlaris', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF1F2937), + ), + ), + const SizedBox(height: 16), + Column( + children: data.topProducts.map((product) { + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFFF8FAFC), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey[200]!), + ), + child: Row( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon( + Icons.local_cafe, + color: AppColors.primary, + size: 20, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + product.productName, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + const SizedBox(height: 2), + Text( + product.categoryName, + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + ), + ], + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + '${product.quantitySold} unit', + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + const SizedBox(height: 2), + Text( + _formatCurrency(product.revenue), + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + ), + ], + ), + ], + ), + ); + }).toList(), + ), + ], + ), + ); + } + + Widget _buildOrderSummary() { + final successfulOrders = data.overview.totalOrders - + data.overview.voidedOrders - + data.overview.refundedOrders; + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Ringkasan Pesanan', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF1F2937), + ), + ), + const SizedBox(height: 20), + _buildSummaryItem('Total Pesanan', '${data.overview.totalOrders}', + Icons.shopping_cart, AppColorDashboard.info), + _buildSummaryItem('Pesanan Sukses', '$successfulOrders', + Icons.check_circle, AppColorDashboard.success), + _buildSummaryItem( + 'Pesanan Dibatalkan', + '${data.overview.voidedOrders}', + Icons.cancel, + AppColorDashboard.danger), + _buildSummaryItem('Pesanan Refund', '${data.overview.refundedOrders}', + Icons.refresh, AppColorDashboard.warning), + const SizedBox(height: 20), + // Payment Methods + if (data.paymentMethods.isNotEmpty) + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + const Text( + 'Metode Pembayaran', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF374151), + ), + ), + const SizedBox(height: 8), + ...data.paymentMethods + .map((method) => Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + method.paymentMethodType == 'cash' + ? Icons.payments + : Icons.credit_card, + color: AppColors.primary, + size: 16, + ), + const SizedBox(width: 8), + Text( + method.paymentMethodName, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + )) + .toList(), + ], + ), + ), + ], + ), + ); + } + + Widget _buildSummaryItem( + String title, String value, IconData icon, Color color) { + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon(icon, color: color, size: 16), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + title, + style: const TextStyle(fontSize: 12), + ), + ), + Text( + value, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + ], + ), + ); + } + + String _formatCurrency(int amount) { + final formatter = NumberFormat.currency( + locale: 'id_ID', + symbol: 'Rp ', + decimalDigits: 0, + ); + return formatter.format(amount); + } +} diff --git a/lib/presentation/report/widgets/item_sales_report_widget.dart b/lib/presentation/report/widgets/item_sales_report_widget.dart index be0fffc..e77c7a7 100644 --- a/lib/presentation/report/widgets/item_sales_report_widget.dart +++ b/lib/presentation/report/widgets/item_sales_report_widget.dart @@ -1,24 +1,16 @@ -import 'dart:developer'; - -import 'package:enaklo_pos/core/components/spaces.dart'; -import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/core/extensions/int_ext.dart'; -import 'package:enaklo_pos/core/utils/helper_pdf_service.dart'; +import 'package:enaklo_pos/data/models/response/sales_analytic_response_model.dart'; import 'package:flutter/material.dart'; -import 'package:enaklo_pos/core/utils/item_sales_invoice.dart'; -import 'package:enaklo_pos/core/utils/permession_handler.dart'; -import 'package:enaklo_pos/data/models/response/item_sales_response_model.dart'; -import 'package:horizontal_data_table/horizontal_data_table.dart'; -import 'package:permission_handler/permission_handler.dart'; +import 'package:intl/intl.dart'; class ItemSalesReportWidget extends StatelessWidget { final String title; final String searchDateFormatted; - final List itemSales; + final SalesAnalyticData sales; final List? headerWidgets; const ItemSalesReportWidget({ super.key, - required this.itemSales, + required this.sales, required this.title, required this.searchDateFormatted, required this.headerWidgets, @@ -26,161 +18,238 @@ class ItemSalesReportWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Card( - color: const Color.fromARGB(255, 255, 255, 255), - child: Column( - children: [ - const SpaceHeight(24.0), - Center( - child: Text( - title, - style: - const TextStyle(fontWeight: FontWeight.w800, fontSize: 16.0), + // Proses data untuk mendapatkan insights + final insights = _processSalesData(sales); + + return Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: Colors.white, + border: Border( + left: BorderSide( + color: const Color(0xFFE5E7EB), + width: 1, + ), + ), + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + _buildHeader(), + + const SizedBox(height: 24), + + // Metrics menggunakan data dari summary + _buildMetrics(), + + const SizedBox(height: 24), + + // Daily Performance Section + Text( + 'Kinerja Harian', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: const Color(0xFF111827), + ), ), + + const SizedBox(height: 16), + + // Daily Performance List dengan data dinamis + ListView.builder( + itemCount: insights.sortedDailyData.length, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + final dayData = insights.sortedDailyData[index]; + return _buildDailyPerformanceItem( + date: dayData.formattedDate, + sales: dayData.formattedSales, + orders: dayData.orders, + items: dayData.items, + isHighest: dayData == insights.highestRevenueDay, + ); + }, + ), + + const SizedBox(height: 16), + + // Summary Footer dengan data dinamis + _buildSummaryFooter(insights.highestRevenueDay), + ], + ), + ), + ); + } + + // Method untuk memproses data dan mendapatkan insights + SalesInsights _processSalesData(SalesAnalyticData data) { + // Sort data by sales (descending) untuk ranking + List sortedData = List.from(data.data); + sortedData.sort((a, b) => b.sales.compareTo(a.sales)); + + // Find highest revenue day + SalesAnalyticItem? highestRevenueDay; + if (sortedData.isNotEmpty) { + highestRevenueDay = sortedData.first; + } + + return SalesInsights( + originalData: data.data, + sortedDailyData: sortedData, + highestRevenueDay: highestRevenueDay, + summary: data.summary, + ); + } + + Widget _buildHeader() { + return Container( + padding: const EdgeInsets.only(bottom: 20), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: const Color(0xFFE5E7EB), + width: 1, ), - const SizedBox( - height: 8.0, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - searchDateFormatted, - style: const TextStyle(fontSize: 16.0), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: const Color(0xFF111827), ), - GestureDetector( - onTap: () async { - try { - final status = await PermessionHelper().checkPermission(); - if (status) { - final pdfFile = await ItemSalesInvoice.generate( - itemSales, searchDateFormatted); - 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: const Row( - children: [ - Text( - "PDF", - style: TextStyle( - fontSize: 14.0, - fontWeight: FontWeight.bold, - color: AppColors.primary, - ), - ), - Icon( - Icons.download_outlined, - color: AppColors.primary, - ) - ], + ), + const SizedBox(height: 4), + Text( + searchDateFormatted, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: const Color(0xFF6B7280), + ), + ), + ], + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: const Color(0xFF10B981), + borderRadius: BorderRadius.circular(24), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.trending_up, + size: 16, + color: Colors.white, + ), + const SizedBox(width: 6), + Text( + 'Growing', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.white, ), ), ], ), ), - const SpaceHeight(16.0), + ], + ), + ); + } + + Widget _buildMetrics() { + final summary = sales.summary; + + return Column( + children: [ + Row( + children: [ + Expanded( + child: _buildMetricCard( + title: 'Jumlah Penjualan', + value: summary.totalSales.currencyFormatRpV2, + subtitle: 'Penjualan Bersih', + color: const Color(0xFF3B82F6), + backgroundColor: const Color(0xFFEFF6FF), + ), + ), + const SizedBox(width: 16), + Expanded( + child: _buildMetricCard( + title: 'Jumlah Pesanan', + value: '${summary.totalOrders}', + subtitle: '${summary.totalItems} Item', + color: const Color(0xFF8B5CF6), + backgroundColor: const Color(0xFFF3E8FF), + ), + ), + ], + ), + const SizedBox(height: 16), + _buildFullWidthMetricCard( + title: 'Nilai Pesanan Rata-rata', + value: summary.averageOrderValue.round().currencyFormatRpV2, + subtitle: 'Per transaksi', + color: const Color(0xFFEF4444), + backgroundColor: const Color(0xFFFEF2F2), + ), + ], + ); + } + + Widget _buildSummaryFooter(SalesAnalyticItem? highestDay) { + if (highestDay == null) { + return Container(); + } + + final dateFormat = DateFormat('dd MMM'); + final formattedDate = dateFormat.format(highestDay.date); + + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFF9FAFB), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: const Color(0xFFE5E7EB), + width: 1, + ), + ), + child: Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: const Color(0xFF10B981), + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 12), Expanded( - child: Padding( - padding: const EdgeInsets.all(12), - child: ClipRRect( - borderRadius: BorderRadius.circular(12), - child: HorizontalDataTable( - leftHandSideColumnWidth: 80, - rightHandSideColumnWidth: 560, - isFixedHeader: true, - headerWidgets: headerWidgets, - - // isFixedFooter: true, - // footerWidgets: _getTitleWidget(), - leftSideItemBuilder: (context, index) { - return Container( - width: 80, - height: 52, - alignment: Alignment.centerLeft, - child: - Center(child: Text(itemSales[index].id.toString())), - ); - }, - rightSideItemBuilder: (context, index) { - return Row( - children: [ - Container( - width: 60, - height: 52, - alignment: Alignment.centerLeft, - child: Center( - child: Text(itemSales[index].orderId.toString())), - ), - Container( - width: 160, - height: 52, - alignment: Alignment.centerLeft, - child: Center( - child: Text(itemSales[index].productName!)), - ), - Container( - width: 60, - height: 52, - alignment: Alignment.centerLeft, - child: Center( - child: - Text(itemSales[index].quantity.toString())), - ), - Container( - width: 140, - height: 52, - padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), - alignment: Alignment.centerLeft, - child: Center( - child: Text( - itemSales[index].price!.currencyFormatRp, - )), - ), - Container( - width: 140, - height: 52, - padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), - alignment: Alignment.centerLeft, - child: Center( - child: Text( - (itemSales[index].price! * - itemSales[index].quantity!) - .currencyFormatRp, - )), - ), - ], - ); - }, - itemCount: itemSales.length, - rowSeparatorWidget: const Divider( - color: Colors.black38, - height: 1.0, - thickness: 0.0, - ), - leftHandSideColBackgroundColor: AppColors.white, - rightHandSideColBackgroundColor: AppColors.white, - - itemExtent: 55, - ), + child: Text( + 'Kinerja puncak pada $formattedDate dengan pendapatan ${_formatCurrency(highestDay.sales)} (pesanan ${highestDay.orders})', + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: const Color(0xFF374151), ), ), ), @@ -188,4 +257,283 @@ class ItemSalesReportWidget extends StatelessWidget { ), ); } + + // Helper method untuk format currency + String _formatCurrency(int amount) { + if (amount >= 1000000) { + return 'Rp ${(amount / 1000000).toStringAsFixed(1)}JT'; + } else if (amount >= 1000) { + return 'Rp ${(amount / 1000).toStringAsFixed(0)}RB'; + } else { + return 'Rp ${NumberFormat('#,###').format(amount)}'; + } + } + + Widget _buildMetricCard({ + required String title, + required String value, + required String subtitle, + required Color color, + required Color backgroundColor, + }) { + return Container( + padding: const EdgeInsets.all(18), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: color.withOpacity(0.2), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: color, + ), + ), + const SizedBox(height: 8), + Text( + value, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w700, + color: const Color(0xFF111827), + ), + ), + const SizedBox(height: 4), + Text( + subtitle, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + color: const Color(0xFF6B7280), + ), + ), + ], + ), + ); + } + + Widget _buildFullWidthMetricCard({ + required String title, + required String value, + required String subtitle, + required Color color, + required Color backgroundColor, + }) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(18), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: color.withOpacity(0.2), + width: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: color, + ), + ), + const SizedBox(height: 4), + Text( + subtitle, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + color: const Color(0xFF6B7280), + ), + ), + ], + ), + Text( + value, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: const Color(0xFF111827), + ), + ), + ], + ), + ); + } + + Widget _buildDailyPerformanceItem({ + required String date, + required String sales, + required int orders, + required int items, + required bool isHighest, + }) { + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: isHighest ? const Color(0xFF10B981) : const Color(0xFFE5E7EB), + width: isHighest ? 2 : 1, + ), + ), + child: Row( + children: [ + Container( + width: 60, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + date, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: const Color(0xFF111827), + ), + ), + if (isHighest) + Container( + margin: const EdgeInsets.only(top: 4), + padding: + const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: const Color(0xFF10B981), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + 'TOP', + style: TextStyle( + fontSize: 8, + fontWeight: FontWeight.w700, + color: Colors.white, + ), + ), + ), + ], + ), + ), + const SizedBox(width: 16), + Expanded( + flex: 2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + sales, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: const Color(0xFF111827), + ), + ), + Text( + 'Pendapatan', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + color: const Color(0xFF6B7280), + ), + ), + ], + ), + ), + Expanded( + flex: 1, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + '$orders pesanan', + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: const Color(0xFF374151), + ), + ), + Text( + '$items item', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + color: const Color(0xFF6B7280), + ), + ), + ], + ), + ), + ], + ), + ); + } } + +extension SalesAnalyticItemExtension on SalesAnalyticItem { + String get formattedDate { + final dateFormat = DateFormat('dd MMM'); + return dateFormat.format(date); + } + + String get formattedSales { + if (sales >= 1000000) { + return 'Rp ${(sales / 1000000).toStringAsFixed(1)}JT'; + } else if (sales >= 1000) { + return 'Rp ${(sales / 1000).toStringAsFixed(0)}RB'; + } else { + return 'Rp ${NumberFormat('#,###').format(sales)}'; + } + } + + double get averageOrderValue { + return orders > 0 ? sales / orders : 0.0; + } +} + + // ReportPageTitle( + // title: title, + // searchDateFormatted: searchDateFormatted, + // onExport: () async { + // try { + // final status = await PermessionHelper().checkPermission(); + // if (status) { + // final pdfFile = await ItemSalesInvoice.generate( + // itemSales, searchDateFormatted); + // 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, + // ), + // ); + // } + // }, + // ), \ No newline at end of file diff --git a/lib/presentation/report/widgets/payment_method_report_widget.dart b/lib/presentation/report/widgets/payment_method_report_widget.dart index 337fcb7..09e2e14 100644 --- a/lib/presentation/report/widgets/payment_method_report_widget.dart +++ b/lib/presentation/report/widgets/payment_method_report_widget.dart @@ -1,14 +1,12 @@ +import 'package:enaklo_pos/core/extensions/int_ext.dart'; +import 'package:enaklo_pos/data/models/response/payment_method_analytic_response_model.dart'; import 'package:flutter/material.dart'; import 'package:enaklo_pos/core/constants/colors.dart'; -import 'package:enaklo_pos/core/extensions/int_ext.dart'; -import 'package:enaklo_pos/core/extensions/string_ext.dart'; -import 'package:enaklo_pos/data/models/response/payment_method_response_model.dart'; -import 'package:enaklo_pos/presentation/report/widgets/report_title.dart'; import '../../../core/components/spaces.dart'; class PaymentMethodReportWidget extends StatelessWidget { - final PaymentMethodData paymentMethodData; + final PaymentMethodAnalyticData paymentMethodData; final String title; final String searchDateFormatted; final List headerWidgets; @@ -25,129 +23,345 @@ class PaymentMethodReportWidget extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.white, - body: Column( - children: [ - // HEADER - Container( - padding: const EdgeInsets.all(24.0), - decoration: const BoxDecoration( - color: AppColors.white, - border: Border( - bottom: BorderSide( - color: AppColors.stroke, - width: 1, - ), - ), + body: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + border: Border( + left: BorderSide( + color: Colors.grey.shade200, + width: 1, ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( + ), + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header Section + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.grey.shade800, + ), + ), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.blue.shade50, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: Colors.blue.shade200, + width: 1, + ), + ), + child: Text( + searchDateFormatted, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.blue.shade700, + ), + ), + ), + ], + ), + const SpaceHeight(24), // Summary Cards + Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.green.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.green.shade200, + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Pendapatan Total', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.green.shade700, + ), + ), + const SizedBox(height: 8), + Text( + paymentMethodData + .summary.totalAmount.currencyFormatRpV2, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.green.shade800, + ), + ), + ], + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.orange.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.orange.shade200, + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Jumlah Pesanan', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.orange.shade700, + ), + ), + const SizedBox(height: 8), + Text( + paymentMethodData.summary.totalOrders.toString(), + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.orange.shade800, + ), + ), + ], + ), + ), + ), + ], + ), + const SpaceHeight(16), + // Average Order Value Card + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.purple.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.purple.shade200, + width: 1, + ), + ), + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - title, - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.w600, + 'Nilai Pesanan Rata-rata', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.purple.shade700, ), ), - const SpaceHeight(8.0), + const SizedBox(height: 8), Text( - searchDateFormatted, - style: const TextStyle( - fontSize: 16, - color: AppColors.grey, + paymentMethodData.summary.averageOrderValue + .round() + .currencyFormatRpV2, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.purple.shade800, ), ), ], ), - Row( + ), + const SpaceHeight(24), + + // Payment Methods Section + Text( + 'Rincian Metode Pembayaran', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.grey.shade800, + ), + ), + + const SpaceHeight(16), + + // Payment Method Item + ...List.generate(paymentMethodData.data.length, (index) { + final item = paymentMethodData.data[index]; + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.grey.shade200, + width: 1, + ), + ), + child: Row( + children: [ + // Payment Method Icon + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: AppColors.primary, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: AppColors.primary, + width: 1, + ), + ), + child: Icon( + Icons.money, + color: AppColors.white, + size: 20, + ), + ), + + const SizedBox(width: 16), + + // Payment Method Details + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + item.paymentMethodName, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.grey.shade800, + ), + ), + Text( + "${item.percentage}%", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + + const SizedBox(height: 8), + + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + item.totalAmount.currencyFormatRpV2, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: Colors.grey.shade600, + ), + ), + Text( + '${item.orderCount} Pesanan', + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: Colors.grey.shade600, + ), + ), + ], + ), + + const SizedBox(height: 8), + + // Progress Bar + Container( + width: double.infinity, + height: 6, + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(3), + ), + child: FractionallySizedBox( + alignment: Alignment.centerLeft, + widthFactor: + (item.percentage / 100), // 100% + child: Container( + decoration: BoxDecoration( + color: AppColors.primary, + borderRadius: + BorderRadius.circular(3), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ); + }), + + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.blue.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.blue.shade200, + width: 1, + ), + ), + child: Row( children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 8.0, - ), - decoration: BoxDecoration( - color: AppColors.primary, - borderRadius: BorderRadius.circular(8.0), - ), + Icon( + Icons.info_outline, + color: Colors.blue.shade600, + size: 16, + ), + const SizedBox(width: 8), + Expanded( child: Text( - 'Total: ${paymentMethodData.total?.currencyFormatRpV2 ?? 'Rp 0'}', - style: const TextStyle( - color: AppColors.white, - fontWeight: FontWeight.w600, + 'All payments processed successfully with 100% cash transactions', + style: TextStyle( + fontSize: 12, + color: Colors.blue.shade700, ), ), ), ], ), - ], - ), - ), - - // CONTENT - Expanded( - child: SingleChildScrollView( - child: Column( - children: [ - // TABLE HEADER - Row( - children: headerWidgets, - ), - - // TABLE BODY - ...paymentMethodData.paymentMethods?.map((item) { - return Container( - decoration: const BoxDecoration( - border: Border( - bottom: BorderSide( - color: AppColors.stroke, - width: 1, - ), - ), - ), - child: Row( - children: [ - _getBodyItemWidget( - item.paymentMethod ?? '-', - 180, - ), - _getBodyItemWidget( - item.totalAmount?.currencyFormatRpV2 ?? 'Rp 0', - 180, - ), - _getBodyItemWidget( - item.transactionCount?.toString() ?? '0', - 180, - ), - ], - ), - ); - }).toList() ?? - [], - ], ), - ), + ], ), - ], - ), - ); - } - - Widget _getBodyItemWidget(String label, double width) { - return Container( - width: width, - height: 56, - alignment: Alignment.centerLeft, - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Text( - label, - style: const TextStyle( - fontSize: 14, ), ), ); } -} \ No newline at end of file +} diff --git a/lib/presentation/report/widgets/product_analytic_widget.dart b/lib/presentation/report/widgets/product_analytic_widget.dart new file mode 100644 index 0000000..6b27b67 --- /dev/null +++ b/lib/presentation/report/widgets/product_analytic_widget.dart @@ -0,0 +1,498 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first + +import 'package:enaklo_pos/data/models/response/product_analytic_response_model.dart'; +import 'package:flutter/material.dart'; + +import 'package:intl/intl.dart'; + +class ProductAnalyticsWidget extends StatelessWidget { + final ProductAnalyticData productData; + final String title; + final String searchDateFormatted; + + const ProductAnalyticsWidget( + {super.key, + required this.productData, + required this.title, + required this.searchDateFormatted}); + + @override + Widget build(BuildContext context) { + // Proses data untuk mendapatkan insights + final insights = _processProductData(productData); + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + border: Border( + left: BorderSide( + color: const Color(0xFFD1D5DB), + width: 1, + ), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header Section dengan Icon dan Stats + _buildHeader(insights), + + const SizedBox(height: 24), + + // Category Summary Cards (Horizontal Scroll) + SizedBox( + height: 80, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: insights.categorySummary.length, + itemBuilder: (context, index) { + final category = insights.categorySummary[index]; + return Padding( + padding: EdgeInsets.only( + right: index == insights.categorySummary.length - 1 + ? 0 + : 12), + child: _buildCategorySummaryCard( + categoryName: category.categoryName, + productCount: category.productCount, + totalRevenue: category.totalRevenue, + color: _getCategoryColor(category.categoryName), + ), + ); + }, + ), + ), + + const SizedBox(height: 24), + + // Top Products Section + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Produk Berkinerja Terbaik', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: const Color(0xFF111827), + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: const Color(0xFFF3F4F6), + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: const Color(0xFFD1D5DB), + width: 1, + ), + ), + child: Text( + 'Berdasarkan Pendapatan', + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w500, + color: const Color(0xFF6B7280), + ), + ), + ), + ], + ), + + const SizedBox(height: 16), + + // Product List dengan data dinamis + Expanded( + child: ListView.builder( + itemCount: insights.topProducts.length, + itemBuilder: (context, index) { + final product = insights.topProducts[index]; + return _buildProductItem( + rank: index + 1, + product: product, + isTopPerformer: product == insights.bestProduct, + categoryColor: _getCategoryColor(product.categoryName), + ); + }, + ), + ), + + const SizedBox(height: 16), + + // Bottom Summary dengan insights dinamis + _buildBottomSummary(insights.bestProduct), + ], + ), + ); + } + + // Method untuk memproses data dan mendapatkan insights + ProductInsights _processProductData(ProductAnalyticData data) { + // Sort products by revenue (descending) untuk ranking + List sortedProducts = List.from(data.data); + sortedProducts.sort((a, b) => b.revenue.compareTo(a.revenue)); + + // Best product adalah yang revenue tertinggi + ProductAnalyticItem? bestProduct; + if (sortedProducts.isNotEmpty) { + bestProduct = sortedProducts.first; + } + + // Group by category untuk summary + Map categoryMap = {}; + + for (var product in data.data) { + if (categoryMap.containsKey(product.categoryName)) { + categoryMap[product.categoryName]!.productCount++; + categoryMap[product.categoryName]!.totalRevenue += product.revenue; + } else { + categoryMap[product.categoryName] = CategorySummary( + categoryName: product.categoryName, + productCount: 1, + totalRevenue: product.revenue, + ); + } + } + + // Convert map to list dan sort by revenue + List categorySummary = categoryMap.values.toList(); + categorySummary.sort((a, b) => b.totalRevenue.compareTo(a.totalRevenue)); + + // Calculate total metrics + int totalProducts = data.data.length; + int totalRevenue = data.data.fold(0, (sum, item) => sum + item.revenue); + int totalQuantitySold = + data.data.fold(0, (sum, item) => sum + item.quantitySold); + + return ProductInsights( + topProducts: sortedProducts, + bestProduct: bestProduct, + categorySummary: categorySummary, + totalProducts: totalProducts, + totalRevenue: totalRevenue, + totalQuantitySold: totalQuantitySold, + ); + } + + Widget _buildHeader(ProductInsights insights) { + return Row( + children: [ + // Icon Container + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: const Color(0xFF3B82F6), + borderRadius: BorderRadius.circular(10), + ), + child: Icon( + Icons.inventory_2, + color: Colors.white, + size: 24, + ), + ), + + const SizedBox(width: 16), + + // Title and Period + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w700, + color: const Color(0xFF111827), + ), + ), + Text( + searchDateFormatted, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w400, + color: const Color(0xFF6B7280), + ), + ), + ], + ), + ), + + // Total Products Badge + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: const Color(0xFF059669), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + '${insights.totalProducts} Produk', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ], + ); + } + + Widget _buildBottomSummary(ProductAnalyticItem? bestProduct) { + if (bestProduct == null) return Container(); + + return Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: const Color(0xFFFEF3C7), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: const Color(0xFFD97706), + width: 1, + ), + ), + child: Row( + children: [ + Icon( + Icons.star, + color: const Color(0xFFD97706), + size: 16, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + '${bestProduct.productName} memimpin dengan ${bestProduct.quantitySold} unit terjual dan pendapatan ${_formatCurrency(bestProduct.revenue)}', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: const Color(0xff92400E), + ), + ), + ), + ], + ), + ); + } + + // Helper method untuk category color + Color _getCategoryColor(String categoryName) { + switch (categoryName.toLowerCase()) { + case 'minuman': + return const Color(0xFF06B6D4); + case 'makanan': + return const Color(0xFFEF4444); + case 'snack': + return const Color(0xFF8B5CF6); + default: + return const Color(0xFF6B7280); + } + } + + Widget _buildCategorySummaryCard({ + required String categoryName, + required int productCount, + required int totalRevenue, + required Color color, + }) { + return Container( + width: 140, + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: color.withOpacity(0.3), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + categoryName, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: color, + ), + ), + Text( + '$productCount items', + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w500, + color: const Color(0xFF6B7280), + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + _formatCurrency(totalRevenue), + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w700, + color: const Color(0xFF111827), + ), + ), + ], + ), + ); + } + + Widget _buildProductItem({ + required int rank, + required ProductAnalyticItem product, + required bool isTopPerformer, + required Color categoryColor, + }) { + return Container( + margin: const EdgeInsets.only(bottom: 10), + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: + isTopPerformer ? const Color(0xFFF0F9FF) : const Color(0xFFF9FAFB), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: isTopPerformer + ? const Color(0xFF3B82F6) + : const Color(0xFFE5E7EB), + width: isTopPerformer ? 2 : 1, + ), + ), + child: Row( + children: [ + // Rank Badge + Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: isTopPerformer + ? const Color(0xFF3B82F6) + : const Color(0xFF6B7280), + borderRadius: BorderRadius.circular(14), + ), + child: Center( + child: Text( + '$rank', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w700, + color: Colors.white, + ), + ), + ), + ), + + const SizedBox(width: 12), + + // Product Info + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + product.productName, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: const Color(0xFF111827), + ), + ), + ), + Row( + children: [ + if (isTopPerformer) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + margin: const EdgeInsets.only(right: 8), + decoration: BoxDecoration( + color: const Color(0xFF10B981), + borderRadius: BorderRadius.circular(3), + ), + child: Text( + 'BEST', + style: TextStyle( + fontSize: 8, + fontWeight: FontWeight.w700, + color: Colors.white, + ), + ), + ), + Text( + _formatCurrency(product.revenue), + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w700, + color: const Color(0xFF111827), + ), + ), + ], + ), + ], + ), + const SizedBox(height: 6), + Row( + children: [ + // Category Badge + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: categoryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + product.categoryName, + style: TextStyle( + fontSize: 9, + fontWeight: FontWeight.w500, + color: categoryColor, + ), + ), + ), + + const SizedBox(width: 8), + + // Stats + Expanded( + child: Text( + '${product.quantitySold} units • ${product.orderCount} orders • Avg ${_formatCurrency(product.averagePrice.round())}', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + color: const Color(0xFF6B7280), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ); + } + + // Helper method untuk format currency + String _formatCurrency(int amount) { + if (amount >= 1000000) { + return 'Rp ${(amount / 1000000).toStringAsFixed(1)}M'; + } else if (amount >= 1000) { + return 'Rp ${(amount / 1000).toStringAsFixed(0)}K'; + } else { + return 'Rp ${NumberFormat('#,###').format(amount)}'; + } + } +} diff --git a/lib/presentation/report/widgets/product_sales_chart_widget.dart b/lib/presentation/report/widgets/product_sales_chart_widget.dart deleted file mode 100644 index 9170729..0000000 --- a/lib/presentation/report/widgets/product_sales_chart_widget.dart +++ /dev/null @@ -1,125 +0,0 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first - -import 'package:enaklo_pos/core/components/spaces.dart'; -import 'package:flutter/material.dart'; -import 'package:pie_chart/pie_chart.dart'; - -import 'package:enaklo_pos/data/models/response/product_sales_response_model.dart'; - -class ProductSalesChartWidgets extends StatefulWidget { - final String title; - final String searchDateFormatted; - final List productSales; - const ProductSalesChartWidgets({ - super.key, - required this.title, - required this.searchDateFormatted, - required this.productSales, - }); - - @override - State createState() => - _ProductSalesChartWidgetsState(); -} - -class _ProductSalesChartWidgetsState extends State { - Map dataMap2 = {}; - - @override - void initState() { - loadData(); - super.initState(); - } - - loadData() { - for (var data in widget.productSales) { - dataMap2[data.productName ?? 'Unknown'] = - double.parse(data.totalQuantity!); - } - } - - final colorList = [ - const Color(0xfffdcb6e), - const Color(0xff0984e3), - const Color(0xfffd79a8), - const Color(0xffe17055), - const Color(0xff6c5ce7), - const Color(0xfff0932b), - const Color(0xff6ab04c), - const Color(0xfff8a5c2), - const Color(0xffe84393), - const Color(0xfffd79a8), - const Color(0xffa29bfe), - const Color(0xff00b894), - const Color(0xffe17055), - const Color(0xffd63031), - const Color(0xffa29bfe), - const Color(0xff6c5ce7), - const Color(0xff00cec9), - const Color(0xfffad390), - const Color(0xff686de0), - const Color(0xfffdcb6e), - const Color(0xff0984e3), - const Color(0xfffd79a8), - const Color(0xffe17055), - const Color(0xff6c5ce7), - ]; - - @override - Widget build(BuildContext context) { - return Card( - color: const Color.fromARGB(255, 255, 255, 255), - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16), - child: Column( - children: [ - const SpaceHeight(24.0), - Center( - child: Text( - widget.title, - style: const TextStyle( - fontWeight: FontWeight.w800, fontSize: 16.0), - ), - ), - Center( - child: Text( - widget.searchDateFormatted, - style: const TextStyle(fontSize: 16.0), - ), - ), - const SpaceHeight(16.0), - PieChart( - dataMap: dataMap2, - animationDuration: Duration(milliseconds: 800), - chartLegendSpacing: 32, - chartRadius: MediaQuery.of(context).size.width / 3.2, - colorList: colorList, - initialAngleInDegree: 0, - chartType: ChartType.disc, - ringStrokeWidth: 32, - // centerText: "HYBRID", - legendOptions: LegendOptions( - showLegendsInRow: false, - legendPosition: LegendPosition.right, - showLegends: true, - legendShape: BoxShape.circle, - legendTextStyle: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - chartValuesOptions: ChartValuesOptions( - showChartValueBackground: true, - showChartValues: true, - showChartValuesInPercentage: false, - showChartValuesOutside: false, - decimalPlaces: 0, - ), - // gradientList: ---To add gradient colors--- - // emptyColorGradient: ---Empty Color gradient--- - ) - ], - ), - ), - ); - } -} diff --git a/lib/presentation/report/widgets/profit_loss_widget.dart b/lib/presentation/report/widgets/profit_loss_widget.dart new file mode 100644 index 0000000..ac318b9 --- /dev/null +++ b/lib/presentation/report/widgets/profit_loss_widget.dart @@ -0,0 +1,1174 @@ +import 'package:enaklo_pos/data/models/response/profit_loss_response_model.dart'; +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:intl/intl.dart'; + +// Warna Aplikasi +class AppColorProfitLoss { + static const primary = Color(0xff36175e); + static const secondary = Color(0xff7c3aed); + static const success = Color(0xff10b981); + static const warning = Color(0xfff59e0b); + static const danger = Color(0xffef4444); + static const info = Color(0xff3b82f6); +} + +class ProfitLossWidget extends StatelessWidget { + final ProfitLossData data; + final String title; + final String searchDateFormatted; + + const ProfitLossWidget({ + super.key, + required this.data, + required this.title, + required this.searchDateFormatted, + }); + + @override + Widget build(BuildContext context) { + return Container( + color: const Color(0xFFF1F5F9), + padding: const EdgeInsets.all(20.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(), + const SizedBox(height: 20), + _buildSummaryCards(), + const SizedBox(height: 20), + _buildProfitTrendChart(), + const SizedBox(height: 20), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 3, + child: _buildProductProfitability(), + ), + const SizedBox(width: 16), + Expanded( + flex: 2, + child: _buildProfitBreakdown(), + ), + ], + ), + const SizedBox(height: 20), + _buildDetailedMetrics(), + ], + ), + ), + ); + } + + Widget _buildHeader() { + return Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.grey[300]!), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF0F172A), + ), + ), + const SizedBox(height: 6), + Text( + 'Analisis profitabilitas dan margin keuntungan', + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ], + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColorProfitLoss.primary, + AppColorProfitLoss.secondary + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(10), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.date_range, color: Colors.white, size: 18), + const SizedBox(width: 8), + Text( + searchDateFormatted, + style: const TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ], + ), + ); + } + + // Fungsi untuk menghitung tren berdasarkan data historis + String _calculateTrend(String metric) { + if (data.data.length < 2) return '+0.0%'; + + double current = 0; + double previous = 0; + + // Ambil data dari beberapa hari terakhir untuk perhitungan tren + final dataLength = data.data.length; + final splitPoint = + (dataLength * 0.6).round(); // 60% data lama, 40% data baru + + final recentData = data.data.skip(splitPoint).toList(); + final olderData = data.data.take(splitPoint).toList(); + + if (recentData.isEmpty || olderData.isEmpty) return '+0.0%'; + + switch (metric) { + case 'revenue': + current = recentData.fold(0.0, (sum, item) => sum + item.revenue) / + recentData.length; + previous = olderData.fold(0.0, (sum, item) => sum + item.revenue) / + olderData.length; + break; + case 'cost': + current = recentData.fold(0.0, (sum, item) => sum + item.cost) / + recentData.length; + previous = olderData.fold(0.0, (sum, item) => sum + item.cost) / + olderData.length; + break; + case 'grossProfit': + current = recentData.fold(0.0, (sum, item) => sum + item.grossProfit) / + recentData.length; + previous = olderData.fold(0.0, (sum, item) => sum + item.grossProfit) / + olderData.length; + break; + case 'netProfit': + current = recentData.fold(0.0, (sum, item) => sum + item.netProfit) / + recentData.length; + previous = olderData.fold(0.0, (sum, item) => sum + item.netProfit) / + olderData.length; + break; + } + + // Handle division by zero and invalid values + if (previous == 0 || previous.isNaN || previous.isInfinite) return '+0.0%'; + if (current.isNaN || current.isInfinite) return '+0.0%'; + + final trendPercentage = ((current - previous) / previous) * 100; + + // Check if trendPercentage is valid + if (trendPercentage.isNaN || trendPercentage.isInfinite) return '+0.0%'; + + final sign = trendPercentage >= 0 ? '+' : ''; + return '$sign${trendPercentage.round()}%'; + } + + Widget _buildSummaryCards() { + final summaryItems = [ + { + 'title': 'Jumlah Pendapatan', + 'value': _formatCurrency(data.summary.totalRevenue), + 'subtitle': '${data.summary.totalOrders} pesanan', + 'icon': Icons.attach_money, + 'color': AppColorProfitLoss.success, + 'trend': _calculateTrend('revenue'), + }, + { + 'title': 'Jumlah Biaya', + 'value': _formatCurrency(data.summary.totalCost), + 'subtitle': 'HPP & Biaya', + 'icon': Icons.receipt, + 'color': AppColorProfitLoss.danger, + 'trend': _calculateTrend('cost'), + }, + { + 'title': 'Laba Kotor', + 'value': _formatCurrency(data.summary.grossProfit), + 'subtitle': '${_safeRound(data.summary.grossProfitMargin)}% margin', + 'icon': Icons.trending_up, + 'color': AppColorProfitLoss.primary, + 'trend': _calculateTrend('grossProfit'), + }, + { + 'title': 'Laba Bersih', + 'value': _formatCurrency(data.summary.netProfit), + 'subtitle': '${_safeRound(data.summary.netProfitMargin)}% margin', + 'icon': Icons.account_balance, + 'color': AppColorProfitLoss.info, + 'trend': _calculateTrend('netProfit'), + }, + ]; + + return Row( + children: summaryItems.map((item) { + final trendValue = item['trend'] as String; + final isPositive = !trendValue.startsWith('-'); + final trendColor = + isPositive ? AppColorProfitLoss.success : AppColorProfitLoss.danger; + + return Expanded( + child: Container( + margin: const EdgeInsets.only(right: 12), + padding: const EdgeInsets.all(18), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: (item['color'] as Color).withOpacity(0.12), + borderRadius: BorderRadius.circular(10), + ), + child: Icon( + item['icon'] as IconData, + color: item['color'] as Color, + size: 22, + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: trendColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + trendValue, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: trendColor, + ), + ), + ), + ], + ), + const SizedBox(height: 14), + Text( + item['value'] as String, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Color(0xFF0F172A), + ), + ), + const SizedBox(height: 4), + Text( + item['title'] as String, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF475569), + ), + ), + const SizedBox(height: 2), + Text( + item['subtitle'] as String, + style: TextStyle( + fontSize: 11, + color: Colors.grey[500], + ), + ), + ], + ), + ), + ); + }).toList(), + ); + } + + Widget _buildProfitTrendChart() { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Analisis Tren Keuntungan', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF0F172A), + ), + ), + Row( + children: [ + _buildLegendItem('Pendapatan', AppColorProfitLoss.info), + const SizedBox(width: 16), + _buildLegendItem('Biaya', AppColorProfitLoss.danger), + const SizedBox(width: 16), + _buildLegendItem('Laba Bersih', AppColorProfitLoss.success), + ], + ), + ], + ), + const SizedBox(height: 20), + SizedBox( + height: 220, + child: LineChart( + LineChartData( + gridData: FlGridData( + show: true, + drawHorizontalLine: true, + drawVerticalLine: false, + horizontalInterval: 100000, + getDrawingHorizontalLine: (value) { + return FlLine( + color: Colors.grey[100]!, + strokeWidth: 1, + ); + }, + ), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 50, + getTitlesWidget: (value, meta) { + final kValue = (value / 1000); + if (kValue.isFinite) { + return Text( + '${kValue.toInt()}K', + style: TextStyle( + color: Colors.grey[600], + fontSize: 10, + ), + ); + } + return const SizedBox(); + }, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + final index = value.toInt(); + if (index >= 0 && index < data.data.length) { + final date = DateTime.parse(data.data[index].date); + return Padding( + padding: const EdgeInsets.only(top: 8), + child: Text( + '${date.day}/${date.month}', + style: TextStyle( + color: Colors.grey[600], + fontSize: 10, + ), + ), + ); + } + return const SizedBox(); + }, + ), + ), + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false)), + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false)), + ), + borderData: FlBorderData(show: false), + lineBarsData: [ + // Garis Pendapatan + LineChartBarData( + spots: data.data.asMap().entries.map((entry) { + final revenue = entry.value.revenue.toDouble(); + return FlSpot( + entry.key.toDouble(), revenue.isFinite ? revenue : 0); + }).toList(), + isCurved: true, + color: AppColorProfitLoss.info, + dotData: const FlDotData(show: false), + ), + // Garis Biaya + LineChartBarData( + spots: data.data.asMap().entries.map((entry) { + final cost = entry.value.cost.toDouble(); + return FlSpot( + entry.key.toDouble(), cost.isFinite ? cost : 0); + }).toList(), + isCurved: true, + color: AppColorProfitLoss.danger, + dotData: const FlDotData(show: false), + ), + // Garis Laba Bersih + LineChartBarData( + spots: data.data.asMap().entries.map((entry) { + final netProfit = entry.value.netProfit.toDouble(); + return FlSpot(entry.key.toDouble(), + netProfit.isFinite ? netProfit : 0); + }).toList(), + isCurved: true, + color: AppColorProfitLoss.success, + dotData: const FlDotData(show: true), + belowBarData: BarAreaData( + show: true, + color: AppColorProfitLoss.success.withOpacity(0.1), + ), + ), + ], + ), + ), + ), + ], + ), + ); + } + + Widget _buildLegendItem(String label, Color color) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 12, + height: 12, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 6), + Text( + label, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Color(0xFF64748B), + ), + ), + ], + ); + } + + Widget _buildProductProfitability() { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Profitabilitas Produk', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF0F172A), + ), + ), + const SizedBox(height: 4), + Text( + 'Analisis margin keuntungan per produk', + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + ), + const SizedBox(height: 16), + Column( + children: data.productData.take(4).map((product) { + return _buildProductItem(product); + }).toList(), + ), + ], + ), + ); + } + + Widget _buildProductItem(ProfitLossProduct product) { + final profitMargin = _safeDouble(product.grossProfitMargin); + final profitColor = profitMargin >= 35 + ? AppColorProfitLoss.success + : profitMargin >= 25 + ? AppColorProfitLoss.warning + : AppColorProfitLoss.danger; + + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFF8FAFC), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey[100]!), + ), + child: Column( + children: [ + Row( + children: [ + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: AppColorProfitLoss.primary.withOpacity(0.12), + borderRadius: BorderRadius.circular(10), + ), + child: Icon( + _getProductIcon(product.categoryName), + color: AppColorProfitLoss.primary, + size: 22, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + product.productName, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 15, + color: Color(0xFF0F172A), + ), + ), + const SizedBox(height: 2), + Text( + '${product.quantitySold} unit • ${product.categoryName}', + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + ), + ], + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: profitColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + '${_safeRound(profitMargin)}%', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: profitColor, + ), + ), + ), + const SizedBox(height: 4), + Text( + _formatCurrency(product.grossProfit), + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Color(0xFF0F172A), + ), + ), + ], + ), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: _buildProductMetric( + 'Pendapatan', product.revenue, AppColorProfitLoss.info), + ), + const SizedBox(width: 8), + Expanded( + child: _buildProductMetric( + 'Biaya', product.cost, AppColorProfitLoss.danger), + ), + const SizedBox(width: 8), + Expanded( + child: _buildProductMetric('Laba/Unit', product.profitPerUnit, + AppColorProfitLoss.success), + ), + ], + ), + ], + ), + ); + } + + Widget _buildProductMetric(String label, int value, Color color) { + return Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: color.withOpacity(0.08), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + Text( + label, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w500, + color: Colors.grey[600], + ), + ), + const SizedBox(height: 2), + Text( + _formatCurrency(value), + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.bold, + color: color, + ), + ), + ], + ), + ); + } + + Widget _buildProfitBreakdown() { + final breakdownData = [ + { + 'label': 'Laba Kotor', + 'value': data.summary.grossProfit, + 'color': AppColorProfitLoss.success + }, + { + 'label': 'Pajak', + 'value': data.summary.totalTax, + 'color': AppColorProfitLoss.warning + }, + { + 'label': 'Diskon', + 'value': data.summary.totalDiscount, + 'color': AppColorProfitLoss.info + }, + { + 'label': 'Laba Bersih', + 'value': data.summary.netProfit, + 'color': AppColorProfitLoss.primary + }, + ]; + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Rincian Keuntungan', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF0F172A), + ), + ), + const SizedBox(height: 4), + Text( + 'Komponen pembentuk profit', + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + ), + const SizedBox(height: 20), + + // Grafik Donut + SizedBox( + height: 160, + child: PieChart( + PieChartData( + sectionsSpace: 3, + centerSpaceRadius: 50, + startDegreeOffset: -90, + sections: breakdownData.asMap().entries.map((entry) { + final item = entry.value; + final grossProfit = data.summary.grossProfit; + final value = item['value'] as int; + + // Handle division by zero + final percentage = + grossProfit > 0 ? (value / grossProfit * 100) : 0.0; + + return PieChartSectionData( + color: item['color'] as Color, + value: value.toDouble(), + title: '${_safeRound(percentage)}%', + radius: 40, + titleStyle: const TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ); + }).toList(), + ), + ), + ), + + const SizedBox(height: 16), + + // Legenda + Column( + children: breakdownData.map((item) { + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Row( + children: [ + Container( + width: 12, + height: 12, + decoration: BoxDecoration( + color: item['color'] as Color, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + item['label'] as String, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + Text( + _formatCurrency(item['value'] as int), + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Color(0xFF0F172A), + ), + ), + ], + ), + ); + }).toList(), + ), + + const SizedBox(height: 16), + + // Rasio Profitabilitas + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColorProfitLoss.primary.withOpacity(0.1), + AppColorProfitLoss.secondary.withOpacity(0.1), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + const Text( + 'Rasio Profitabilitas', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF475569), + ), + ), + const SizedBox(height: 8), + Text( + '${_safeRound(data.summary.profitabilityRatio)}%', + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColorProfitLoss.primary, + ), + ), + const SizedBox(height: 4), + Text( + 'Tingkat Pengembalian Pendapatan', + style: TextStyle( + fontSize: 10, + color: Colors.grey[600], + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildDetailedMetrics() { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Metrik Kinerja Terperinci', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF0F172A), + ), + ), + const SizedBox(height: 16), + + Row( + children: [ + Expanded( + child: _buildMetricCard( + 'Nilai Rata-rata Pesanan', + _formatCurrency(_safeCalculateAverageOrder()), + 'Per transaksi', + Icons.shopping_cart_outlined, + AppColorProfitLoss.info, + ), + ), + const SizedBox(width: 12), + Expanded( + child: _buildMetricCard( + 'Keuntungan Rata-rata', + _formatCurrency(data.summary.averageProfit), + 'Per pesanan', + Icons.trending_up, + AppColorProfitLoss.success, + ), + ), + const SizedBox(width: 12), + Expanded( + child: _buildMetricCard( + 'Rasio Biaya', + '${_safeCalculateCostRatio()}%', + 'Dari total pendapatan', + Icons.pie_chart, + AppColorProfitLoss.danger, + ), + ), + ], + ), + + const SizedBox(height: 16), + + // Tabel rincian harian + Container( + decoration: BoxDecoration( + color: const Color(0xFFF8FAFC), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey[100]!), + ), + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColorProfitLoss.primary.withOpacity(0.05), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + ), + ), + child: const Row( + children: [ + Expanded( + flex: 2, + child: Text( + 'Tanggal', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Color(0xFF475569), + ), + ), + ), + Expanded( + child: Text( + 'Pendapatan', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Color(0xFF475569), + ), + ), + ), + Expanded( + child: Text( + 'Biaya', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Color(0xFF475569), + ), + ), + ), + Expanded( + child: Text( + 'Laba Bersih', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Color(0xFF475569), + ), + ), + ), + Expanded( + child: Text( + 'Margin', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Color(0xFF475569), + ), + ), + ), + ], + ), + ), + ...data.data.map((item) { + final date = DateTime.parse(item.date); + final dateStr = DateFormat('dd MMM').format(date); + + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: Colors.grey[100]!), + ), + ), + child: Row( + children: [ + Expanded( + flex: 2, + child: Text( + dateStr, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF0F172A), + ), + ), + ), + Expanded( + child: Text( + _formatCurrencyShort(item.revenue), + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 11, + fontWeight: FontWeight.w500, + color: AppColorProfitLoss.info, + ), + ), + ), + Expanded( + child: Text( + _formatCurrencyShort(item.cost), + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 11, + fontWeight: FontWeight.w500, + color: AppColorProfitLoss.danger, + ), + ), + ), + Expanded( + child: Text( + _formatCurrencyShort(item.netProfit), + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: AppColorProfitLoss.success, + ), + ), + ), + Expanded( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 3), + decoration: BoxDecoration( + color: _getMarginColor(item.netProfitMargin) + .withOpacity(0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + '${_safeRound(item.netProfitMargin)}%', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: _getMarginColor(item.netProfitMargin), + ), + ), + ), + ), + ], + ), + ); + }).toList(), + ], + ), + ), + ], + ), + ); + } + + Widget _buildMetricCard( + String title, String value, String subtitle, IconData icon, Color color) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: color.withOpacity(0.06), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: color.withOpacity(0.2)), + ), + child: Column( + children: [ + Icon(icon, color: color, size: 24), + const SizedBox(height: 8), + Text( + value, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: color, + ), + ), + const SizedBox(height: 4), + Text( + title, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF475569), + ), + ), + const SizedBox(height: 2), + Text( + subtitle, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 10, + color: Colors.grey[600], + ), + ), + ], + ), + ); + } + + // Helper methods for safe calculations + int _safeRound(double value) { + if (value.isNaN || value.isInfinite) return 0; + return value.round(); + } + + double _safeDouble(double value) { + if (value.isNaN || value.isInfinite) return 0.0; + return value; + } + + int _safeCalculateAverageOrder() { + if (data.summary.totalOrders == 0) return 0; + final average = data.summary.totalRevenue / data.summary.totalOrders; + if (average.isNaN || average.isInfinite) return 0; + return average.round(); + } + + int _safeCalculateCostRatio() { + if (data.summary.totalRevenue == 0) return 0; + final ratio = (data.summary.totalCost / data.summary.totalRevenue) * 100; + if (ratio.isNaN || ratio.isInfinite) return 0; + return ratio.round(); + } + + IconData _getProductIcon(String category) { + switch (category.toLowerCase()) { + case 'coffee': + case 'kopi': + return Icons.local_cafe; + case 'pastry': + case 'kue': + return Icons.cake; + case 'food': + case 'makanan': + return Icons.restaurant; + default: + return Icons.inventory; + } + } + + Color _getMarginColor(double margin) { + final safeMargin = _safeDouble(margin); + if (safeMargin >= 25) return AppColorProfitLoss.success; + if (safeMargin >= 15) return AppColorProfitLoss.warning; + return AppColorProfitLoss.danger; + } + + String _formatCurrency(int amount) { + final formatter = NumberFormat.currency( + locale: 'id_ID', + symbol: 'Rp ', + decimalDigits: 0, + ); + return formatter.format(amount); + } + + String _formatCurrencyShort(int amount) { + if (amount >= 1000000) { + return 'Rp ${(amount / 1000000).round()}M'; + } else if (amount >= 1000) { + return 'Rp ${(amount / 1000).toStringAsFixed(0)}K'; + } + return 'Rp $amount'; + } +} diff --git a/lib/presentation/report/widgets/report_menu.dart b/lib/presentation/report/widgets/report_menu.dart index 9f36a7d..27eb85f 100644 --- a/lib/presentation/report/widgets/report_menu.dart +++ b/lib/presentation/report/widgets/report_menu.dart @@ -1,13 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:enaklo_pos/core/assets/assets.gen.dart'; import '../../../core/components/spaces.dart'; import '../../../core/constants/colors.dart'; - - class ReportMenu extends StatelessWidget { final String label; + final String subtitle; + final IconData icon; final VoidCallback onPressed; final bool isActive; @@ -16,6 +15,8 @@ class ReportMenu extends StatelessWidget { required this.label, required this.onPressed, required this.isActive, + required this.subtitle, + required this.icon, }); @override @@ -23,38 +24,43 @@ class ReportMenu extends StatelessWidget { return GestureDetector( onTap: onPressed, child: Container( - margin: const EdgeInsets.all(5.0), - width: 180.0, - height: 160.0, - alignment: Alignment.center, + padding: const EdgeInsets.all(12.0), decoration: BoxDecoration( - color: - isActive ? AppColors.primary.withOpacity(0.13) : AppColors.white, - borderRadius: BorderRadius.circular(18.0), - border: Border.all( - color: isActive ? AppColors.primary : AppColors.stroke, + border: Border( + right: BorderSide( + color: isActive ? AppColors.primary : Colors.transparent, + width: 4.0, + ), ), ), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, + child: Row( children: [ - Assets.icons.report.svg( - colorFilter: isActive - ? const ColorFilter.mode( - AppColors.primary, - BlendMode.srcIn, - ) - : null, + Icon( + icon, + size: 24.0, + color: isActive ? AppColors.primary : AppColors.grey, ), - const SpaceHeight(28.0), - Text( - label, - style: TextStyle( - color: isActive ? AppColors.primary : AppColors.black, - fontWeight: FontWeight.w600, + const SpaceWidth(12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4.0), + Text( + subtitle, + style: + const TextStyle(fontSize: 14.0, color: AppColors.grey), + ), + ], ), ), - const SpaceHeight(24.0), ], ), ), diff --git a/lib/presentation/report/widgets/report_page_title.dart b/lib/presentation/report/widgets/report_page_title.dart new file mode 100644 index 0000000..ffe78d6 --- /dev/null +++ b/lib/presentation/report/widgets/report_page_title.dart @@ -0,0 +1,65 @@ +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:flutter/material.dart'; + +class ReportPageTitle extends StatelessWidget { + final String title; + final String searchDateFormatted; + final Function() onExport; + final bool isExport; + const ReportPageTitle( + {super.key, + required this.title, + required this.searchDateFormatted, + required this.onExport, + this.isExport = true}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration(color: AppColors.white), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontWeight: FontWeight.w800, fontSize: 16.0), + ), + const SizedBox( + height: 8.0, + ), + Text( + searchDateFormatted, + style: const TextStyle(fontSize: 16.0), + ), + ], + ), + if (isExport) + GestureDetector( + onTap: onExport, + child: const Row( + children: [ + Text( + "PDF", + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + Icon( + Icons.download_outlined, + color: AppColors.primary, + ) + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/report/widgets/report_title.dart b/lib/presentation/report/widgets/report_title.dart index 06e3f21..30ab197 100644 --- a/lib/presentation/report/widgets/report_title.dart +++ b/lib/presentation/report/widgets/report_title.dart @@ -1,38 +1,60 @@ +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:flutter/material.dart'; -import 'package:enaklo_pos/core/components/components.dart'; import 'package:enaklo_pos/core/extensions/date_time_ext.dart'; import '../../../core/constants/colors.dart'; - - class ReportTitle extends StatelessWidget { - const ReportTitle({super.key}); + final List? actionWidget; + const ReportTitle({super.key, this.actionWidget}); @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Report', - style: TextStyle( - color: AppColors.primary, - fontSize: 28, - fontWeight: FontWeight.w600, + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 10.0, + ), + width: double.infinity, + height: context.deviceHeight * 0.1, + decoration: const BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.background, + width: 1.0, ), ), - const SpaceHeight(4.0), - Text( - DateTime.now().toFormattedDate(), - style: const TextStyle( - color: AppColors.subtitle, - fontSize: 16, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Laporan', + style: TextStyle( + color: AppColors.black, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + Text( + DateTime.now().toFormattedDate2(), + style: TextStyle( + color: AppColors.grey, + fontSize: 14, + ), + ), + ], ), - ), - const SpaceHeight(20.0), - const Divider(), - ], + if (actionWidget != null) + Row( + children: actionWidget!, + ), + ], + ), ); } } diff --git a/lib/presentation/report/widgets/summary_report_widget.dart b/lib/presentation/report/widgets/summary_report_widget.dart index 677f51e..cdf77a3 100644 --- a/lib/presentation/report/widgets/summary_report_widget.dart +++ b/lib/presentation/report/widgets/summary_report_widget.dart @@ -9,7 +9,6 @@ import 'package:enaklo_pos/core/utils/helper_pdf_service.dart'; import 'package:enaklo_pos/core/utils/permession_handler.dart'; import 'package:enaklo_pos/data/models/response/summary_response_model.dart'; import 'package:flutter/material.dart'; -import 'package:permission_handler/permission_handler.dart'; import '../../../core/utils/revenue_invoice.dart'; @@ -55,27 +54,29 @@ class SummaryReportWidget extends StatelessWidget { log("PDF button clicked for summary report"); try { log("Checking permissions..."); - final status = await PermessionHelper().checkPermission(); + final status = + await PermessionHelper().checkPermission(); log("Permission status: $status"); - + if (status) { log("Permission granted, starting PDF generation..."); log("Summary data: ${summary.toMap()}"); log("Search date: $searchDateFormatted"); - + final pdfFile = await RevenueInvoice.generate( summary, searchDateFormatted); log("PDF file generated: ${pdfFile.path}"); log("PDF file exists: ${await pdfFile.exists()}"); log("PDF file size: ${await pdfFile.length()} bytes"); - + log("Attempting to open PDF file..."); await HelperPdfService.openFile(pdfFile); log("PDF file opened successfully"); - + ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text('PDF saved successfully: ${pdfFile.path}'), + content: Text( + 'PDF saved successfully: ${pdfFile.path}'), backgroundColor: Colors.green, ), ); @@ -83,7 +84,8 @@ class SummaryReportWidget extends StatelessWidget { log("Permission denied"); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: Text('Storage permission is required to save PDF'), + content: Text( + 'Storage permission is required to save PDF'), backgroundColor: Colors.red, ), ); diff --git a/lib/presentation/report/widgets/transaction_report_widget.dart b/lib/presentation/report/widgets/transaction_report_widget.dart index 9665ab1..46add37 100644 --- a/lib/presentation/report/widgets/transaction_report_widget.dart +++ b/lib/presentation/report/widgets/transaction_report_widget.dart @@ -2,20 +2,19 @@ import 'dart:developer'; import 'package:enaklo_pos/core/components/spaces.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/int_ext.dart'; import 'package:enaklo_pos/core/utils/helper_pdf_service.dart'; +import 'package:enaklo_pos/presentation/report/widgets/report_page_title.dart'; import 'package:flutter/material.dart'; import 'package:enaklo_pos/core/utils/permession_handler.dart'; import 'package:enaklo_pos/core/utils/transaction_sales_invoice.dart'; -import 'package:enaklo_pos/data/models/response/order_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; import 'package:horizontal_data_table/horizontal_data_table.dart'; -import 'package:permission_handler/permission_handler.dart'; class TransactionReportWidget extends StatelessWidget { final String title; final String searchDateFormatted; - final List transactionReport; + final List transactionReport; final List? headerWidgets; const TransactionReportWidget({ super.key, @@ -27,208 +26,170 @@ class TransactionReportWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Card( - color: const Color.fromARGB(255, 255, 255, 255), - child: Column( - children: [ - const SpaceHeight(24.0), - Center( - child: Text( - title, - style: - const TextStyle(fontWeight: FontWeight.w800, fontSize: 16.0), - ), - ), - const SizedBox( - height: 8.0, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - searchDateFormatted, - style: const TextStyle(fontSize: 16.0), + return Column( + children: [ + ReportPageTitle( + title: title, + searchDateFormatted: searchDateFormatted, + onExport: () async { + try { + final status = await PermessionHelper().checkPermission(); + if (status) { + final pdfFile = await TransactionSalesInvoice.generate( + transactionReport, searchDateFormatted); + 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, ), - GestureDetector( - onTap: () async { - try { - final status = await PermessionHelper().checkPermission(); - if (status) { - final pdfFile = await TransactionSalesInvoice.generate( - transactionReport, searchDateFormatted); - 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: const Row( - children: [ - Text( - "PDF", - style: TextStyle( - fontSize: 14.0, - fontWeight: FontWeight.bold, - color: AppColors.primary, - ), + ); + } + }, + ), + const SpaceHeight(16.0), + Expanded( + child: Padding( + padding: const EdgeInsets.all(12), + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: HorizontalDataTable( + leftHandSideColumnWidth: 50, + rightHandSideColumnWidth: 1020, + isFixedHeader: true, + headerWidgets: headerWidgets, + // isFixedFooter: true, + // footerWidgets: _getTitleWidget(), + leftSideItemBuilder: (context, index) { + return Container( + width: 40, + height: 52, + alignment: Alignment.centerLeft, + child: Center( + child: Text(transactionReport[index].id.toString())), + ); + }, + rightSideItemBuilder: (context, index) { + return Row( + children: [ + Container( + width: 120, + height: 52, + padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), + alignment: Alignment.centerLeft, + child: Center( + child: Text( + transactionReport[index] + .totalAmount! + .currencyFormatRp, + )), ), - Icon( - Icons.download_outlined, - color: AppColors.primary, - ) + // Container( + // width: 120, + // height: 52, + // padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), + // alignment: Alignment.centerLeft, + // child: Center( + // child: Text( + // transactionReport[index].subTotal!.currencyFormatRp, + // )), + // ), + // Container( + // width: 100, + // height: 52, + // padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), + // alignment: Alignment.centerLeft, + // child: Center( + // child: Text( + // transactionReport[index].tax!.currencyFormatRp, + // )), + // ), + // Container( + // width: 100, + // height: 52, + // padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), + // alignment: Alignment.centerLeft, + // child: Center( + // child: Text( + // int.parse(transactionReport[index] + // .discountAmount! + // .replaceAll('.00', '')) + // .currencyFormatRp, + // ), + // ), + // ), + // Container( + // width: 100, + // height: 52, + // padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), + // alignment: Alignment.centerLeft, + // child: Center( + // child: Text( + // transactionReport[index] + // .serviceCharge! + // .currencyFormatRp, + // ), + // ), + // ), + // Container( + // width: 100, + // height: 52, + // padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), + // alignment: Alignment.centerLeft, + // child: Center( + // child: Text( + // transactionReport[index].totalItem.toString()), + // ), + // ), + // Container( + // width: 150, + // height: 52, + // padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), + // alignment: Alignment.centerLeft, + // child: Center( + // child: Text(transactionReport[index].namaKasir!), + // ), + // ), + // Container( + // width: 230, + // height: 52, + // padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), + // alignment: Alignment.centerLeft, + // child: Center( + // child: Text(transactionReport[index] + // .transactionTime! + // .toFormattedDate()), + // ), + // ), ], - ), + ); + }, + itemCount: transactionReport.length, + rowSeparatorWidget: const Divider( + color: Colors.black38, + height: 1.0, + thickness: 0.0, ), - ], - ), - ), - const SpaceHeight(16.0), - Expanded( - child: Padding( - padding: const EdgeInsets.all(12), - child: ClipRRect( - borderRadius: BorderRadius.circular(12), - child: HorizontalDataTable( - leftHandSideColumnWidth: 50, - rightHandSideColumnWidth: 1020, - isFixedHeader: true, - headerWidgets: headerWidgets, - // isFixedFooter: true, - // footerWidgets: _getTitleWidget(), - leftSideItemBuilder: (context, index) { - return Container( - width: 40, - height: 52, - alignment: Alignment.centerLeft, - child: Center( - child: Text(transactionReport[index].id.toString())), - ); - }, - rightSideItemBuilder: (context, index) { - return Row( - children: [ - Container( - width: 120, - height: 52, - padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), - alignment: Alignment.centerLeft, - child: Center( - child: Text( - transactionReport[index].total!.currencyFormatRp, - )), - ), - Container( - width: 120, - height: 52, - padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), - alignment: Alignment.centerLeft, - child: Center( - child: Text( - transactionReport[index].subTotal!.currencyFormatRp, - )), - ), - Container( - width: 100, - height: 52, - padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), - alignment: Alignment.centerLeft, - child: Center( - child: Text( - transactionReport[index].tax!.currencyFormatRp, - )), - ), - Container( - width: 100, - height: 52, - padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), - alignment: Alignment.centerLeft, - child: Center( - child: Text( - int.parse(transactionReport[index] - .discountAmount! - .replaceAll('.00', '')) - .currencyFormatRp, - ), - ), - ), - Container( - width: 100, - height: 52, - padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), - alignment: Alignment.centerLeft, - child: Center( - child: Text( - transactionReport[index] - .serviceCharge! - .currencyFormatRp, - ), - ), - ), - Container( - width: 100, - height: 52, - padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), - alignment: Alignment.centerLeft, - child: Center( - child: Text( - transactionReport[index].totalItem.toString()), - ), - ), - Container( - width: 150, - height: 52, - padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), - alignment: Alignment.centerLeft, - child: Center( - child: Text(transactionReport[index].namaKasir!), - ), - ), - Container( - width: 230, - height: 52, - padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), - alignment: Alignment.centerLeft, - child: Center( - child: Text(transactionReport[index] - .transactionTime! - .toFormattedDate()), - ), - ), - ], - ); - }, - itemCount: transactionReport.length, - rowSeparatorWidget: const Divider( - color: Colors.black38, - height: 1.0, - thickness: 0.0, - ), - leftHandSideColBackgroundColor: AppColors.white, - rightHandSideColBackgroundColor: AppColors.white, + leftHandSideColBackgroundColor: AppColors.white, + rightHandSideColBackgroundColor: AppColors.white, - itemExtent: 55, - ), + itemExtent: 55, ), ), ), - ], - ), + ), + ], ); } } diff --git a/lib/presentation/sales/blocs/day_sales/day_sales_bloc.dart b/lib/presentation/sales/blocs/day_sales/day_sales_bloc.dart index d8f045b..a186f88 100644 --- a/lib/presentation/sales/blocs/day_sales/day_sales_bloc.dart +++ b/lib/presentation/sales/blocs/day_sales/day_sales_bloc.dart @@ -1,4 +1,3 @@ - import 'package:bloc/bloc.dart'; import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; import 'package:enaklo_pos/presentation/home/models/order_model.dart'; @@ -12,9 +11,14 @@ class DaySalesBloc extends Bloc { final ProductLocalDatasource datasource; DaySalesBloc(this.datasource) : super(const _Initial()) { on<_GetDaySales>((event, emit) async { + emit(const _Loading()); + final result = await datasource.getAllOrder(event.date); + emit(_Loaded(result)); + }); + on<_GetRangeDateSales>((event, emit) async { emit(const _Loading()); final result = - await datasource.getAllOrder(event.date); + await datasource.getAllOrderByRange(event.startDate, event.endDate); emit(_Loaded(result)); }); } diff --git a/lib/presentation/sales/blocs/day_sales/day_sales_bloc.freezed.dart b/lib/presentation/sales/blocs/day_sales/day_sales_bloc.freezed.dart index 75464d8..1081e35 100644 --- a/lib/presentation/sales/blocs/day_sales/day_sales_bloc.freezed.dart +++ b/lib/presentation/sales/blocs/day_sales/day_sales_bloc.freezed.dart @@ -20,18 +20,22 @@ mixin _$DaySalesEvent { TResult when({ required TResult Function() started, required TResult Function(DateTime date) getDaySales, + required TResult Function(DateTime startDate, DateTime endDate) + getRangeDateSales, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, TResult? Function(DateTime date)? getDaySales, + TResult? Function(DateTime startDate, DateTime endDate)? getRangeDateSales, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, TResult Function(DateTime date)? getDaySales, + TResult Function(DateTime startDate, DateTime endDate)? getRangeDateSales, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -39,18 +43,21 @@ mixin _$DaySalesEvent { TResult map({ required TResult Function(_Started value) started, required TResult Function(_GetDaySales value) getDaySales, + required TResult Function(_GetRangeDateSales value) getRangeDateSales, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? mapOrNull({ TResult? Function(_Started value)? started, TResult? Function(_GetDaySales value)? getDaySales, + TResult? Function(_GetRangeDateSales value)? getRangeDateSales, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeMap({ TResult Function(_Started value)? started, TResult Function(_GetDaySales value)? getDaySales, + TResult Function(_GetRangeDateSales value)? getRangeDateSales, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -120,6 +127,8 @@ class _$StartedImpl implements _Started { TResult when({ required TResult Function() started, required TResult Function(DateTime date) getDaySales, + required TResult Function(DateTime startDate, DateTime endDate) + getRangeDateSales, }) { return started(); } @@ -129,6 +138,7 @@ class _$StartedImpl implements _Started { TResult? whenOrNull({ TResult? Function()? started, TResult? Function(DateTime date)? getDaySales, + TResult? Function(DateTime startDate, DateTime endDate)? getRangeDateSales, }) { return started?.call(); } @@ -138,6 +148,7 @@ class _$StartedImpl implements _Started { TResult maybeWhen({ TResult Function()? started, TResult Function(DateTime date)? getDaySales, + TResult Function(DateTime startDate, DateTime endDate)? getRangeDateSales, required TResult orElse(), }) { if (started != null) { @@ -151,6 +162,7 @@ class _$StartedImpl implements _Started { TResult map({ required TResult Function(_Started value) started, required TResult Function(_GetDaySales value) getDaySales, + required TResult Function(_GetRangeDateSales value) getRangeDateSales, }) { return started(this); } @@ -160,6 +172,7 @@ class _$StartedImpl implements _Started { TResult? mapOrNull({ TResult? Function(_Started value)? started, TResult? Function(_GetDaySales value)? getDaySales, + TResult? Function(_GetRangeDateSales value)? getRangeDateSales, }) { return started?.call(this); } @@ -169,6 +182,7 @@ class _$StartedImpl implements _Started { TResult maybeMap({ TResult Function(_Started value)? started, TResult Function(_GetDaySales value)? getDaySales, + TResult Function(_GetRangeDateSales value)? getRangeDateSales, required TResult orElse(), }) { if (started != null) { @@ -252,6 +266,8 @@ class _$GetDaySalesImpl implements _GetDaySales { TResult when({ required TResult Function() started, required TResult Function(DateTime date) getDaySales, + required TResult Function(DateTime startDate, DateTime endDate) + getRangeDateSales, }) { return getDaySales(date); } @@ -261,6 +277,7 @@ class _$GetDaySalesImpl implements _GetDaySales { TResult? whenOrNull({ TResult? Function()? started, TResult? Function(DateTime date)? getDaySales, + TResult? Function(DateTime startDate, DateTime endDate)? getRangeDateSales, }) { return getDaySales?.call(date); } @@ -270,6 +287,7 @@ class _$GetDaySalesImpl implements _GetDaySales { TResult maybeWhen({ TResult Function()? started, TResult Function(DateTime date)? getDaySales, + TResult Function(DateTime startDate, DateTime endDate)? getRangeDateSales, required TResult orElse(), }) { if (getDaySales != null) { @@ -283,6 +301,7 @@ class _$GetDaySalesImpl implements _GetDaySales { TResult map({ required TResult Function(_Started value) started, required TResult Function(_GetDaySales value) getDaySales, + required TResult Function(_GetRangeDateSales value) getRangeDateSales, }) { return getDaySales(this); } @@ -292,6 +311,7 @@ class _$GetDaySalesImpl implements _GetDaySales { TResult? mapOrNull({ TResult? Function(_Started value)? started, TResult? Function(_GetDaySales value)? getDaySales, + TResult? Function(_GetRangeDateSales value)? getRangeDateSales, }) { return getDaySales?.call(this); } @@ -301,6 +321,7 @@ class _$GetDaySalesImpl implements _GetDaySales { TResult maybeMap({ TResult Function(_Started value)? started, TResult Function(_GetDaySales value)? getDaySales, + TResult Function(_GetRangeDateSales value)? getRangeDateSales, required TResult orElse(), }) { if (getDaySales != null) { @@ -322,6 +343,166 @@ abstract class _GetDaySales implements DaySalesEvent { throw _privateConstructorUsedError; } +/// @nodoc +abstract class _$$GetRangeDateSalesImplCopyWith<$Res> { + factory _$$GetRangeDateSalesImplCopyWith(_$GetRangeDateSalesImpl value, + $Res Function(_$GetRangeDateSalesImpl) then) = + __$$GetRangeDateSalesImplCopyWithImpl<$Res>; + @useResult + $Res call({DateTime startDate, DateTime endDate}); +} + +/// @nodoc +class __$$GetRangeDateSalesImplCopyWithImpl<$Res> + extends _$DaySalesEventCopyWithImpl<$Res, _$GetRangeDateSalesImpl> + implements _$$GetRangeDateSalesImplCopyWith<$Res> { + __$$GetRangeDateSalesImplCopyWithImpl(_$GetRangeDateSalesImpl _value, + $Res Function(_$GetRangeDateSalesImpl) _then) + : super(_value, _then); + + /// Create a copy of DaySalesEvent + /// 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(_$GetRangeDateSalesImpl( + null == startDate + ? _value.startDate + : startDate // ignore: cast_nullable_to_non_nullable + as DateTime, + null == endDate + ? _value.endDate + : endDate // ignore: cast_nullable_to_non_nullable + as DateTime, + )); + } +} + +/// @nodoc + +class _$GetRangeDateSalesImpl implements _GetRangeDateSales { + const _$GetRangeDateSalesImpl(this.startDate, this.endDate); + + @override + final DateTime startDate; + @override + final DateTime endDate; + + @override + String toString() { + return 'DaySalesEvent.getRangeDateSales(startDate: $startDate, endDate: $endDate)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$GetRangeDateSalesImpl && + (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 DaySalesEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$GetRangeDateSalesImplCopyWith<_$GetRangeDateSalesImpl> get copyWith => + __$$GetRangeDateSalesImplCopyWithImpl<_$GetRangeDateSalesImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() started, + required TResult Function(DateTime date) getDaySales, + required TResult Function(DateTime startDate, DateTime endDate) + getRangeDateSales, + }) { + return getRangeDateSales(startDate, endDate); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? started, + TResult? Function(DateTime date)? getDaySales, + TResult? Function(DateTime startDate, DateTime endDate)? getRangeDateSales, + }) { + return getRangeDateSales?.call(startDate, endDate); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? started, + TResult Function(DateTime date)? getDaySales, + TResult Function(DateTime startDate, DateTime endDate)? getRangeDateSales, + required TResult orElse(), + }) { + if (getRangeDateSales != null) { + return getRangeDateSales(startDate, endDate); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Started value) started, + required TResult Function(_GetDaySales value) getDaySales, + required TResult Function(_GetRangeDateSales value) getRangeDateSales, + }) { + return getRangeDateSales(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Started value)? started, + TResult? Function(_GetDaySales value)? getDaySales, + TResult? Function(_GetRangeDateSales value)? getRangeDateSales, + }) { + return getRangeDateSales?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Started value)? started, + TResult Function(_GetDaySales value)? getDaySales, + TResult Function(_GetRangeDateSales value)? getRangeDateSales, + required TResult orElse(), + }) { + if (getRangeDateSales != null) { + return getRangeDateSales(this); + } + return orElse(); + } +} + +abstract class _GetRangeDateSales implements DaySalesEvent { + const factory _GetRangeDateSales( + final DateTime startDate, final DateTime endDate) = + _$GetRangeDateSalesImpl; + + DateTime get startDate; + DateTime get endDate; + + /// Create a copy of DaySalesEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GetRangeDateSalesImplCopyWith<_$GetRangeDateSalesImpl> get copyWith => + throw _privateConstructorUsedError; +} + /// @nodoc mixin _$DaySalesState { @optionalTypeArgs diff --git a/lib/presentation/sales/blocs/day_sales/day_sales_event.dart b/lib/presentation/sales/blocs/day_sales/day_sales_event.dart index 05b1184..b8889fd 100644 --- a/lib/presentation/sales/blocs/day_sales/day_sales_event.dart +++ b/lib/presentation/sales/blocs/day_sales/day_sales_event.dart @@ -6,4 +6,8 @@ class DaySalesEvent with _$DaySalesEvent { const factory DaySalesEvent.getDaySales( DateTime date, ) = _GetDaySales; + const factory DaySalesEvent.getRangeDateSales( + DateTime startDate, + DateTime endDate, + ) = _GetRangeDateSales; } diff --git a/lib/presentation/sales/blocs/order_loader/order_loader_bloc.dart b/lib/presentation/sales/blocs/order_loader/order_loader_bloc.dart new file mode 100644 index 0000000..681ed2a --- /dev/null +++ b/lib/presentation/sales/blocs/order_loader/order_loader_bloc.dart @@ -0,0 +1,169 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'order_loader_event.dart'; +part 'order_loader_state.dart'; +part 'order_loader_bloc.freezed.dart'; + +class OrderLoaderBloc extends Bloc { + final OrderRemoteDatasource _orderRemoteDatasource; + + Timer? _loadMoreDebounce; + bool _isLoadingMore = false; + + OrderLoaderBloc(this._orderRemoteDatasource) + : super(OrderLoaderState.initial()) { + on<_GetByStatus>(_onGetOrderByStatus); + on<_LoadMore>(_onLoadMore); + on<_Refresh>(_onRefresh); + on<_GetById>((event, emit) async { + emit(const _Loading()); + final result = + await _orderRemoteDatasource.getOrderById(orderId: event.id); + result.fold( + (l) => emit(_Error(l)), + (r) => emit(_LoadedDetail(r.data!)), + ); + }); + } + + @override + Future close() { + _loadMoreDebounce?.cancel(); + return super.close(); + } + + // Debounce transformer untuk load more + // EventTransformer _debounceTransformer() { + // return (events, mapper) { + // return events + // .debounceTime(const Duration(milliseconds: 300)) + // .asyncExpand(mapper); + // }; + // } + + // Initial load + Future _onGetOrderByStatus( + _GetByStatus event, + Emitter emit, + ) async { + emit(const _Loading()); + _isLoadingMore = false; // Reset loading state + + final result = await _orderRemoteDatasource.getOrder( + page: 1, + limit: event.limit ?? 10, + status: event.status, + dateFrom: event.dateFrom, + dateTo: event.dateTo, + search: event.search, + ); + + await result.fold( + (failure) async => emit(_Error(failure)), + (response) async { + final orders = response.data?.orders ?? []; + final hasReachedMax = orders.length < 10; + + emit(_Loaded( + orders: orders, + totalOrder: response.data?.totalCount ?? 0, + hasReachedMax: hasReachedMax, + currentPage: 1, + isLoadingMore: false, + )); + }, + ); + } + + // Load more with enhanced debouncing + Future _onLoadMore( + _LoadMore event, + Emitter emit, + ) async { + final currentState = state; + + // Enhanced validation + if (currentState is! _Loaded || + currentState.hasReachedMax || + _isLoadingMore || + currentState.isLoadingMore) { + return; + } + + _isLoadingMore = true; + + // Emit loading more state + emit(currentState.copyWith(isLoadingMore: true)); + + final nextPage = currentState.currentPage + 1; + + try { + final result = await _orderRemoteDatasource.getOrder( + page: nextPage, + limit: 10, + status: event.status, + dateFrom: event.dateFrom, + dateTo: event.dateTo, + search: event.search, + ); + + await result.fold( + (failure) async { + // On error, revert loading state but don't show error + // Just silently fail and allow retry + emit(currentState.copyWith(isLoadingMore: false)); + _isLoadingMore = false; + }, + (response) async { + final newOrders = response.data?.orders ?? []; + + // Prevent duplicate orders + final currentOrderIds = currentState.orders.map((p) => p.id).toSet(); + final filteredNewOrders = newOrders + .where((order) => !currentOrderIds.contains(order.id)) + .toList(); + + final allOrders = List.from(currentState.orders) + ..addAll(filteredNewOrders); + + final hasReachedMax = newOrders.length < 10; + + emit(_Loaded( + orders: allOrders, + totalOrder: response.data?.totalCount ?? 0, + hasReachedMax: hasReachedMax, + currentPage: nextPage, + isLoadingMore: false, + )); + + _isLoadingMore = false; + }, + ); + } catch (e) { + // Handle unexpected errors + emit(currentState.copyWith(isLoadingMore: false)); + _isLoadingMore = false; + } + } + + // Refresh data + Future _onRefresh( + _Refresh event, + Emitter emit, + ) async { + _isLoadingMore = false; + _loadMoreDebounce?.cancel(); + add( + _GetByStatus( + event.status, + dateFrom: DateTime.now(), + dateTo: DateTime.now(), + ), + ); + } +} diff --git a/lib/presentation/sales/blocs/order_loader/order_loader_bloc.freezed.dart b/lib/presentation/sales/blocs/order_loader/order_loader_bloc.freezed.dart new file mode 100644 index 0000000..96b233d --- /dev/null +++ b/lib/presentation/sales/blocs/order_loader/order_loader_bloc.freezed.dart @@ -0,0 +1,1748 @@ +// 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 'order_loader_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$OrderLoaderEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(String status, DateTime dateFrom, DateTime dateTo, + int? limit, String? search) + getByStatus, + required TResult Function(String id) getById, + required TResult Function( + String status, DateTime dateFrom, DateTime dateTo, String? search) + loadMore, + required TResult Function(String status) refresh, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String status, DateTime dateFrom, DateTime dateTo, + int? limit, String? search)? + getByStatus, + TResult? Function(String id)? getById, + TResult? Function( + String status, DateTime dateFrom, DateTime dateTo, String? search)? + loadMore, + TResult? Function(String status)? refresh, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String status, DateTime dateFrom, DateTime dateTo, + int? limit, String? search)? + getByStatus, + TResult Function(String id)? getById, + TResult Function( + String status, DateTime dateFrom, DateTime dateTo, String? search)? + loadMore, + TResult Function(String status)? refresh, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_GetByStatus value) getByStatus, + required TResult Function(_GetById value) getById, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetByStatus value)? getByStatus, + TResult? Function(_GetById value)? getById, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetByStatus value)? getByStatus, + TResult Function(_GetById value)? getById, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $OrderLoaderEventCopyWith<$Res> { + factory $OrderLoaderEventCopyWith( + OrderLoaderEvent value, $Res Function(OrderLoaderEvent) then) = + _$OrderLoaderEventCopyWithImpl<$Res, OrderLoaderEvent>; +} + +/// @nodoc +class _$OrderLoaderEventCopyWithImpl<$Res, $Val extends OrderLoaderEvent> + implements $OrderLoaderEventCopyWith<$Res> { + _$OrderLoaderEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$GetByStatusImplCopyWith<$Res> { + factory _$$GetByStatusImplCopyWith( + _$GetByStatusImpl value, $Res Function(_$GetByStatusImpl) then) = + __$$GetByStatusImplCopyWithImpl<$Res>; + @useResult + $Res call( + {String status, + DateTime dateFrom, + DateTime dateTo, + int? limit, + String? search}); +} + +/// @nodoc +class __$$GetByStatusImplCopyWithImpl<$Res> + extends _$OrderLoaderEventCopyWithImpl<$Res, _$GetByStatusImpl> + implements _$$GetByStatusImplCopyWith<$Res> { + __$$GetByStatusImplCopyWithImpl( + _$GetByStatusImpl _value, $Res Function(_$GetByStatusImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? dateFrom = null, + Object? dateTo = null, + Object? limit = freezed, + Object? search = freezed, + }) { + return _then(_$GetByStatusImpl( + null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String, + dateFrom: null == dateFrom + ? _value.dateFrom + : dateFrom // ignore: cast_nullable_to_non_nullable + as DateTime, + dateTo: null == dateTo + ? _value.dateTo + : dateTo // ignore: cast_nullable_to_non_nullable + as DateTime, + limit: freezed == limit + ? _value.limit + : limit // ignore: cast_nullable_to_non_nullable + as int?, + search: freezed == search + ? _value.search + : search // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$GetByStatusImpl implements _GetByStatus { + const _$GetByStatusImpl(this.status, + {required this.dateFrom, required this.dateTo, this.limit, this.search}); + + @override + final String status; + @override + final DateTime dateFrom; + @override + final DateTime dateTo; + @override + final int? limit; + @override + final String? search; + + @override + String toString() { + return 'OrderLoaderEvent.getByStatus(status: $status, dateFrom: $dateFrom, dateTo: $dateTo, limit: $limit, search: $search)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$GetByStatusImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.dateFrom, dateFrom) || + other.dateFrom == dateFrom) && + (identical(other.dateTo, dateTo) || other.dateTo == dateTo) && + (identical(other.limit, limit) || other.limit == limit) && + (identical(other.search, search) || other.search == search)); + } + + @override + int get hashCode => + Object.hash(runtimeType, status, dateFrom, dateTo, limit, search); + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$GetByStatusImplCopyWith<_$GetByStatusImpl> get copyWith => + __$$GetByStatusImplCopyWithImpl<_$GetByStatusImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String status, DateTime dateFrom, DateTime dateTo, + int? limit, String? search) + getByStatus, + required TResult Function(String id) getById, + required TResult Function( + String status, DateTime dateFrom, DateTime dateTo, String? search) + loadMore, + required TResult Function(String status) refresh, + }) { + return getByStatus(status, dateFrom, dateTo, limit, search); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String status, DateTime dateFrom, DateTime dateTo, + int? limit, String? search)? + getByStatus, + TResult? Function(String id)? getById, + TResult? Function( + String status, DateTime dateFrom, DateTime dateTo, String? search)? + loadMore, + TResult? Function(String status)? refresh, + }) { + return getByStatus?.call(status, dateFrom, dateTo, limit, search); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String status, DateTime dateFrom, DateTime dateTo, + int? limit, String? search)? + getByStatus, + TResult Function(String id)? getById, + TResult Function( + String status, DateTime dateFrom, DateTime dateTo, String? search)? + loadMore, + TResult Function(String status)? refresh, + required TResult orElse(), + }) { + if (getByStatus != null) { + return getByStatus(status, dateFrom, dateTo, limit, search); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetByStatus value) getByStatus, + required TResult Function(_GetById value) getById, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + }) { + return getByStatus(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetByStatus value)? getByStatus, + TResult? Function(_GetById value)? getById, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + }) { + return getByStatus?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetByStatus value)? getByStatus, + TResult Function(_GetById value)? getById, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + required TResult orElse(), + }) { + if (getByStatus != null) { + return getByStatus(this); + } + return orElse(); + } +} + +abstract class _GetByStatus implements OrderLoaderEvent { + const factory _GetByStatus(final String status, + {required final DateTime dateFrom, + required final DateTime dateTo, + final int? limit, + final String? search}) = _$GetByStatusImpl; + + String get status; + DateTime get dateFrom; + DateTime get dateTo; + int? get limit; + String? get search; + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GetByStatusImplCopyWith<_$GetByStatusImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$GetByIdImplCopyWith<$Res> { + factory _$$GetByIdImplCopyWith( + _$GetByIdImpl value, $Res Function(_$GetByIdImpl) then) = + __$$GetByIdImplCopyWithImpl<$Res>; + @useResult + $Res call({String id}); +} + +/// @nodoc +class __$$GetByIdImplCopyWithImpl<$Res> + extends _$OrderLoaderEventCopyWithImpl<$Res, _$GetByIdImpl> + implements _$$GetByIdImplCopyWith<$Res> { + __$$GetByIdImplCopyWithImpl( + _$GetByIdImpl _value, $Res Function(_$GetByIdImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + }) { + return _then(_$GetByIdImpl( + null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$GetByIdImpl implements _GetById { + const _$GetByIdImpl(this.id); + + @override + final String id; + + @override + String toString() { + return 'OrderLoaderEvent.getById(id: $id)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$GetByIdImpl && + (identical(other.id, id) || other.id == id)); + } + + @override + int get hashCode => Object.hash(runtimeType, id); + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$GetByIdImplCopyWith<_$GetByIdImpl> get copyWith => + __$$GetByIdImplCopyWithImpl<_$GetByIdImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String status, DateTime dateFrom, DateTime dateTo, + int? limit, String? search) + getByStatus, + required TResult Function(String id) getById, + required TResult Function( + String status, DateTime dateFrom, DateTime dateTo, String? search) + loadMore, + required TResult Function(String status) refresh, + }) { + return getById(id); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String status, DateTime dateFrom, DateTime dateTo, + int? limit, String? search)? + getByStatus, + TResult? Function(String id)? getById, + TResult? Function( + String status, DateTime dateFrom, DateTime dateTo, String? search)? + loadMore, + TResult? Function(String status)? refresh, + }) { + return getById?.call(id); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String status, DateTime dateFrom, DateTime dateTo, + int? limit, String? search)? + getByStatus, + TResult Function(String id)? getById, + TResult Function( + String status, DateTime dateFrom, DateTime dateTo, String? search)? + loadMore, + TResult Function(String status)? refresh, + required TResult orElse(), + }) { + if (getById != null) { + return getById(id); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetByStatus value) getByStatus, + required TResult Function(_GetById value) getById, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + }) { + return getById(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetByStatus value)? getByStatus, + TResult? Function(_GetById value)? getById, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + }) { + return getById?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetByStatus value)? getByStatus, + TResult Function(_GetById value)? getById, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + required TResult orElse(), + }) { + if (getById != null) { + return getById(this); + } + return orElse(); + } +} + +abstract class _GetById implements OrderLoaderEvent { + const factory _GetById(final String id) = _$GetByIdImpl; + + String get id; + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GetByIdImplCopyWith<_$GetByIdImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$LoadMoreImplCopyWith<$Res> { + factory _$$LoadMoreImplCopyWith( + _$LoadMoreImpl value, $Res Function(_$LoadMoreImpl) then) = + __$$LoadMoreImplCopyWithImpl<$Res>; + @useResult + $Res call( + {String status, DateTime dateFrom, DateTime dateTo, String? search}); +} + +/// @nodoc +class __$$LoadMoreImplCopyWithImpl<$Res> + extends _$OrderLoaderEventCopyWithImpl<$Res, _$LoadMoreImpl> + implements _$$LoadMoreImplCopyWith<$Res> { + __$$LoadMoreImplCopyWithImpl( + _$LoadMoreImpl _value, $Res Function(_$LoadMoreImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? dateFrom = null, + Object? dateTo = null, + Object? search = freezed, + }) { + return _then(_$LoadMoreImpl( + null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String, + dateFrom: null == dateFrom + ? _value.dateFrom + : dateFrom // ignore: cast_nullable_to_non_nullable + as DateTime, + dateTo: null == dateTo + ? _value.dateTo + : dateTo // ignore: cast_nullable_to_non_nullable + as DateTime, + search: freezed == search + ? _value.search + : search // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$LoadMoreImpl implements _LoadMore { + const _$LoadMoreImpl(this.status, + {required this.dateFrom, required this.dateTo, this.search}); + + @override + final String status; + @override + final DateTime dateFrom; + @override + final DateTime dateTo; + @override + final String? search; + + @override + String toString() { + return 'OrderLoaderEvent.loadMore(status: $status, dateFrom: $dateFrom, dateTo: $dateTo, search: $search)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoadMoreImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.dateFrom, dateFrom) || + other.dateFrom == dateFrom) && + (identical(other.dateTo, dateTo) || other.dateTo == dateTo) && + (identical(other.search, search) || other.search == search)); + } + + @override + int get hashCode => + Object.hash(runtimeType, status, dateFrom, dateTo, search); + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LoadMoreImplCopyWith<_$LoadMoreImpl> get copyWith => + __$$LoadMoreImplCopyWithImpl<_$LoadMoreImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String status, DateTime dateFrom, DateTime dateTo, + int? limit, String? search) + getByStatus, + required TResult Function(String id) getById, + required TResult Function( + String status, DateTime dateFrom, DateTime dateTo, String? search) + loadMore, + required TResult Function(String status) refresh, + }) { + return loadMore(status, dateFrom, dateTo, search); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String status, DateTime dateFrom, DateTime dateTo, + int? limit, String? search)? + getByStatus, + TResult? Function(String id)? getById, + TResult? Function( + String status, DateTime dateFrom, DateTime dateTo, String? search)? + loadMore, + TResult? Function(String status)? refresh, + }) { + return loadMore?.call(status, dateFrom, dateTo, search); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String status, DateTime dateFrom, DateTime dateTo, + int? limit, String? search)? + getByStatus, + TResult Function(String id)? getById, + TResult Function( + String status, DateTime dateFrom, DateTime dateTo, String? search)? + loadMore, + TResult Function(String status)? refresh, + required TResult orElse(), + }) { + if (loadMore != null) { + return loadMore(status, dateFrom, dateTo, search); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetByStatus value) getByStatus, + required TResult Function(_GetById value) getById, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + }) { + return loadMore(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetByStatus value)? getByStatus, + TResult? Function(_GetById value)? getById, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + }) { + return loadMore?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetByStatus value)? getByStatus, + TResult Function(_GetById value)? getById, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + required TResult orElse(), + }) { + if (loadMore != null) { + return loadMore(this); + } + return orElse(); + } +} + +abstract class _LoadMore implements OrderLoaderEvent { + const factory _LoadMore(final String status, + {required final DateTime dateFrom, + required final DateTime dateTo, + final String? search}) = _$LoadMoreImpl; + + String get status; + DateTime get dateFrom; + DateTime get dateTo; + String? get search; + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LoadMoreImplCopyWith<_$LoadMoreImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$RefreshImplCopyWith<$Res> { + factory _$$RefreshImplCopyWith( + _$RefreshImpl value, $Res Function(_$RefreshImpl) then) = + __$$RefreshImplCopyWithImpl<$Res>; + @useResult + $Res call({String status}); +} + +/// @nodoc +class __$$RefreshImplCopyWithImpl<$Res> + extends _$OrderLoaderEventCopyWithImpl<$Res, _$RefreshImpl> + implements _$$RefreshImplCopyWith<$Res> { + __$$RefreshImplCopyWithImpl( + _$RefreshImpl _value, $Res Function(_$RefreshImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + }) { + return _then(_$RefreshImpl( + null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$RefreshImpl implements _Refresh { + const _$RefreshImpl(this.status); + + @override + final String status; + + @override + String toString() { + return 'OrderLoaderEvent.refresh(status: $status)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RefreshImpl && + (identical(other.status, status) || other.status == status)); + } + + @override + int get hashCode => Object.hash(runtimeType, status); + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$RefreshImplCopyWith<_$RefreshImpl> get copyWith => + __$$RefreshImplCopyWithImpl<_$RefreshImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String status, DateTime dateFrom, DateTime dateTo, + int? limit, String? search) + getByStatus, + required TResult Function(String id) getById, + required TResult Function( + String status, DateTime dateFrom, DateTime dateTo, String? search) + loadMore, + required TResult Function(String status) refresh, + }) { + return refresh(status); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String status, DateTime dateFrom, DateTime dateTo, + int? limit, String? search)? + getByStatus, + TResult? Function(String id)? getById, + TResult? Function( + String status, DateTime dateFrom, DateTime dateTo, String? search)? + loadMore, + TResult? Function(String status)? refresh, + }) { + return refresh?.call(status); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String status, DateTime dateFrom, DateTime dateTo, + int? limit, String? search)? + getByStatus, + TResult Function(String id)? getById, + TResult Function( + String status, DateTime dateFrom, DateTime dateTo, String? search)? + loadMore, + TResult Function(String status)? refresh, + required TResult orElse(), + }) { + if (refresh != null) { + return refresh(status); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetByStatus value) getByStatus, + required TResult Function(_GetById value) getById, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + }) { + return refresh(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetByStatus value)? getByStatus, + TResult? Function(_GetById value)? getById, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + }) { + return refresh?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetByStatus value)? getByStatus, + TResult Function(_GetById value)? getById, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + required TResult orElse(), + }) { + if (refresh != null) { + return refresh(this); + } + return orElse(); + } +} + +abstract class _Refresh implements OrderLoaderEvent { + const factory _Refresh(final String status) = _$RefreshImpl; + + String get status; + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RefreshImplCopyWith<_$RefreshImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$OrderLoaderState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List orders, int totalOrder, + bool hasReachedMax, int currentPage, bool isLoadingMore) + loaded, + required TResult Function(String message) error, + required TResult Function(Order order) loadedDetail, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult? Function(String message)? error, + TResult? Function(Order order)? loadedDetail, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult Function(String message)? error, + TResult Function(Order order)? loadedDetail, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Error value) error, + required TResult Function(_LoadedDetail value) loadedDetail, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Error value)? error, + TResult? Function(_LoadedDetail value)? loadedDetail, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Error value)? error, + TResult Function(_LoadedDetail value)? loadedDetail, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $OrderLoaderStateCopyWith<$Res> { + factory $OrderLoaderStateCopyWith( + OrderLoaderState value, $Res Function(OrderLoaderState) then) = + _$OrderLoaderStateCopyWithImpl<$Res, OrderLoaderState>; +} + +/// @nodoc +class _$OrderLoaderStateCopyWithImpl<$Res, $Val extends OrderLoaderState> + implements $OrderLoaderStateCopyWith<$Res> { + _$OrderLoaderStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of OrderLoaderState + /// 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 _$OrderLoaderStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'OrderLoaderState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List orders, int totalOrder, + bool hasReachedMax, int currentPage, bool isLoadingMore) + loaded, + required TResult Function(String message) error, + required TResult Function(Order order) loadedDetail, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult? Function(String message)? error, + TResult? Function(Order order)? loadedDetail, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult Function(String message)? error, + TResult Function(Order order)? loadedDetail, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Error value) error, + required TResult Function(_LoadedDetail value) loadedDetail, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Error value)? error, + TResult? Function(_LoadedDetail value)? loadedDetail, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Error value)? error, + TResult Function(_LoadedDetail value)? loadedDetail, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements OrderLoaderState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$OrderLoaderStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'OrderLoaderState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List orders, int totalOrder, + bool hasReachedMax, int currentPage, bool isLoadingMore) + loaded, + required TResult Function(String message) error, + required TResult Function(Order order) loadedDetail, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult? Function(String message)? error, + TResult? Function(Order order)? loadedDetail, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult Function(String message)? error, + TResult Function(Order order)? loadedDetail, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Error value) error, + required TResult Function(_LoadedDetail value) loadedDetail, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Error value)? error, + TResult? Function(_LoadedDetail value)? loadedDetail, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Error value)? error, + TResult Function(_LoadedDetail value)? loadedDetail, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class _Loading implements OrderLoaderState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$LoadedImplCopyWith<$Res> { + factory _$$LoadedImplCopyWith( + _$LoadedImpl value, $Res Function(_$LoadedImpl) then) = + __$$LoadedImplCopyWithImpl<$Res>; + @useResult + $Res call( + {List orders, + int totalOrder, + bool hasReachedMax, + int currentPage, + bool isLoadingMore}); +} + +/// @nodoc +class __$$LoadedImplCopyWithImpl<$Res> + extends _$OrderLoaderStateCopyWithImpl<$Res, _$LoadedImpl> + implements _$$LoadedImplCopyWith<$Res> { + __$$LoadedImplCopyWithImpl( + _$LoadedImpl _value, $Res Function(_$LoadedImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? orders = null, + Object? totalOrder = null, + Object? hasReachedMax = null, + Object? currentPage = null, + Object? isLoadingMore = null, + }) { + return _then(_$LoadedImpl( + orders: null == orders + ? _value._orders + : orders // ignore: cast_nullable_to_non_nullable + as List, + totalOrder: null == totalOrder + ? _value.totalOrder + : totalOrder // ignore: cast_nullable_to_non_nullable + as int, + hasReachedMax: null == hasReachedMax + ? _value.hasReachedMax + : hasReachedMax // ignore: cast_nullable_to_non_nullable + as bool, + currentPage: null == currentPage + ? _value.currentPage + : currentPage // ignore: cast_nullable_to_non_nullable + as int, + isLoadingMore: null == isLoadingMore + ? _value.isLoadingMore + : isLoadingMore // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$LoadedImpl implements _Loaded { + const _$LoadedImpl( + {required final List orders, + required this.totalOrder, + required this.hasReachedMax, + required this.currentPage, + required this.isLoadingMore}) + : _orders = orders; + + final List _orders; + @override + List get orders { + if (_orders is EqualUnmodifiableListView) return _orders; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_orders); + } + + @override + final int totalOrder; + @override + final bool hasReachedMax; + @override + final int currentPage; + @override + final bool isLoadingMore; + + @override + String toString() { + return 'OrderLoaderState.loaded(orders: $orders, totalOrder: $totalOrder, hasReachedMax: $hasReachedMax, currentPage: $currentPage, isLoadingMore: $isLoadingMore)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoadedImpl && + const DeepCollectionEquality().equals(other._orders, _orders) && + (identical(other.totalOrder, totalOrder) || + other.totalOrder == totalOrder) && + (identical(other.hasReachedMax, hasReachedMax) || + other.hasReachedMax == hasReachedMax) && + (identical(other.currentPage, currentPage) || + other.currentPage == currentPage) && + (identical(other.isLoadingMore, isLoadingMore) || + other.isLoadingMore == isLoadingMore)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_orders), + totalOrder, + hasReachedMax, + currentPage, + isLoadingMore); + + /// Create a copy of OrderLoaderState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List orders, int totalOrder, + bool hasReachedMax, int currentPage, bool isLoadingMore) + loaded, + required TResult Function(String message) error, + required TResult Function(Order order) loadedDetail, + }) { + return loaded( + orders, totalOrder, hasReachedMax, currentPage, isLoadingMore); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult? Function(String message)? error, + TResult? Function(Order order)? loadedDetail, + }) { + return loaded?.call( + orders, totalOrder, hasReachedMax, currentPage, isLoadingMore); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult Function(String message)? error, + TResult Function(Order order)? loadedDetail, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded( + orders, totalOrder, hasReachedMax, currentPage, isLoadingMore); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Error value) error, + required TResult Function(_LoadedDetail value) loadedDetail, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Error value)? error, + TResult? Function(_LoadedDetail value)? loadedDetail, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Error value)? error, + TResult Function(_LoadedDetail value)? loadedDetail, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class _Loaded implements OrderLoaderState { + const factory _Loaded( + {required final List orders, + required final int totalOrder, + required final bool hasReachedMax, + required final int currentPage, + required final bool isLoadingMore}) = _$LoadedImpl; + + List get orders; + int get totalOrder; + bool get hasReachedMax; + int get currentPage; + bool get isLoadingMore; + + /// Create a copy of OrderLoaderState + /// 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 _$OrderLoaderStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderLoaderState + /// 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 'OrderLoaderState.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 OrderLoaderState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List orders, int totalOrder, + bool hasReachedMax, int currentPage, bool isLoadingMore) + loaded, + required TResult Function(String message) error, + required TResult Function(Order order) loadedDetail, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult? Function(String message)? error, + TResult? Function(Order order)? loadedDetail, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult Function(String message)? error, + TResult Function(Order order)? loadedDetail, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Error value) error, + required TResult Function(_LoadedDetail value) loadedDetail, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Error value)? error, + TResult? Function(_LoadedDetail value)? loadedDetail, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Error value)? error, + TResult Function(_LoadedDetail value)? loadedDetail, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements OrderLoaderState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of OrderLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$LoadedDetailImplCopyWith<$Res> { + factory _$$LoadedDetailImplCopyWith( + _$LoadedDetailImpl value, $Res Function(_$LoadedDetailImpl) then) = + __$$LoadedDetailImplCopyWithImpl<$Res>; + @useResult + $Res call({Order order}); +} + +/// @nodoc +class __$$LoadedDetailImplCopyWithImpl<$Res> + extends _$OrderLoaderStateCopyWithImpl<$Res, _$LoadedDetailImpl> + implements _$$LoadedDetailImplCopyWith<$Res> { + __$$LoadedDetailImplCopyWithImpl( + _$LoadedDetailImpl _value, $Res Function(_$LoadedDetailImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? order = null, + }) { + return _then(_$LoadedDetailImpl( + null == order + ? _value.order + : order // ignore: cast_nullable_to_non_nullable + as Order, + )); + } +} + +/// @nodoc + +class _$LoadedDetailImpl implements _LoadedDetail { + const _$LoadedDetailImpl(this.order); + + @override + final Order order; + + @override + String toString() { + return 'OrderLoaderState.loadedDetail(order: $order)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoadedDetailImpl && + (identical(other.order, order) || other.order == order)); + } + + @override + int get hashCode => Object.hash(runtimeType, order); + + /// Create a copy of OrderLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LoadedDetailImplCopyWith<_$LoadedDetailImpl> get copyWith => + __$$LoadedDetailImplCopyWithImpl<_$LoadedDetailImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List orders, int totalOrder, + bool hasReachedMax, int currentPage, bool isLoadingMore) + loaded, + required TResult Function(String message) error, + required TResult Function(Order order) loadedDetail, + }) { + return loadedDetail(order); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult? Function(String message)? error, + TResult? Function(Order order)? loadedDetail, + }) { + return loadedDetail?.call(order); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, + TResult Function(String message)? error, + TResult Function(Order order)? loadedDetail, + required TResult orElse(), + }) { + if (loadedDetail != null) { + return loadedDetail(order); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Error value) error, + required TResult Function(_LoadedDetail value) loadedDetail, + }) { + return loadedDetail(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Error value)? error, + TResult? Function(_LoadedDetail value)? loadedDetail, + }) { + return loadedDetail?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Error value)? error, + TResult Function(_LoadedDetail value)? loadedDetail, + required TResult orElse(), + }) { + if (loadedDetail != null) { + return loadedDetail(this); + } + return orElse(); + } +} + +abstract class _LoadedDetail implements OrderLoaderState { + const factory _LoadedDetail(final Order order) = _$LoadedDetailImpl; + + Order get order; + + /// Create a copy of OrderLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LoadedDetailImplCopyWith<_$LoadedDetailImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/sales/blocs/order_loader/order_loader_event.dart b/lib/presentation/sales/blocs/order_loader/order_loader_event.dart new file mode 100644 index 0000000..3d9e886 --- /dev/null +++ b/lib/presentation/sales/blocs/order_loader/order_loader_event.dart @@ -0,0 +1,20 @@ +part of 'order_loader_bloc.dart'; + +@freezed +class OrderLoaderEvent with _$OrderLoaderEvent { + const factory OrderLoaderEvent.getByStatus( + String status, { + required DateTime dateFrom, + required DateTime dateTo, + int? limit, + String? search, + }) = _GetByStatus; + const factory OrderLoaderEvent.getById(String id) = _GetById; + const factory OrderLoaderEvent.loadMore( + String status, { + required DateTime dateFrom, + required DateTime dateTo, + String? search, + }) = _LoadMore; + const factory OrderLoaderEvent.refresh(String status) = _Refresh; +} diff --git a/lib/presentation/sales/blocs/order_loader/order_loader_state.dart b/lib/presentation/sales/blocs/order_loader/order_loader_state.dart new file mode 100644 index 0000000..5d868bf --- /dev/null +++ b/lib/presentation/sales/blocs/order_loader/order_loader_state.dart @@ -0,0 +1,16 @@ +part of 'order_loader_bloc.dart'; + +@freezed +class OrderLoaderState with _$OrderLoaderState { + const factory OrderLoaderState.initial() = _Initial; + const factory OrderLoaderState.loading() = _Loading; + const factory OrderLoaderState.loaded({ + required List orders, + required int totalOrder, + required bool hasReachedMax, + required int currentPage, + required bool isLoadingMore, + }) = _Loaded; + const factory OrderLoaderState.error(String message) = _Error; + const factory OrderLoaderState.loadedDetail(Order order) = _LoadedDetail; +} diff --git a/lib/presentation/sales/blocs/payment_form/payment_form_bloc.dart b/lib/presentation/sales/blocs/payment_form/payment_form_bloc.dart new file mode 100644 index 0000000..2eb42c5 --- /dev/null +++ b/lib/presentation/sales/blocs/payment_form/payment_form_bloc.dart @@ -0,0 +1,50 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/request/payment_request.dart'; +import 'package:enaklo_pos/data/models/response/payment_response_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'payment_form_event.dart'; +part 'payment_form_state.dart'; +part 'payment_form_bloc.freezed.dart'; + +class PaymentFormBloc extends Bloc { + final OrderRemoteDatasource _orderRemoteDatasource; + PaymentFormBloc(this._orderRemoteDatasource) + : super(PaymentFormState.initial()) { + on<_Create>( + (event, emit) async { + emit(const _Loading()); + + try { + final result = + await _orderRemoteDatasource.createPayment(event.payment); + + result.fold( + (error) => emit(_Error(error)), + (success) => emit(_Success(success.data!)), + ); + } catch (e) { + emit(_Error("Failed to create payment: $e")); + } + }, + ); + on<_CreateSplitBill>( + (event, emit) async { + emit(const _Loading()); + + try { + final result = await _orderRemoteDatasource + .createPaymentSplitBill(event.payment); + + result.fold( + (error) => emit(_Error(error)), + (success) => emit(_Success(success.data!)), + ); + } catch (e) { + emit(_Error("Failed to create payment split bill: $e")); + } + }, + ); + } +} diff --git a/lib/presentation/sales/blocs/payment_form/payment_form_bloc.freezed.dart b/lib/presentation/sales/blocs/payment_form/payment_form_bloc.freezed.dart new file mode 100644 index 0000000..01cf3ce --- /dev/null +++ b/lib/presentation/sales/blocs/payment_form/payment_form_bloc.freezed.dart @@ -0,0 +1,976 @@ +// 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 'payment_form_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$PaymentFormEvent { + Object get payment => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(PaymentRequestModel payment) create, + required TResult Function(PaymentSplitBillRequest payment) createSplitBill, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(PaymentRequestModel payment)? create, + TResult? Function(PaymentSplitBillRequest payment)? createSplitBill, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(PaymentRequestModel payment)? create, + TResult Function(PaymentSplitBillRequest payment)? createSplitBill, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Create value) create, + required TResult Function(_CreateSplitBill value) createSplitBill, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Create value)? create, + TResult? Function(_CreateSplitBill value)? createSplitBill, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Create value)? create, + TResult Function(_CreateSplitBill value)? createSplitBill, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PaymentFormEventCopyWith<$Res> { + factory $PaymentFormEventCopyWith( + PaymentFormEvent value, $Res Function(PaymentFormEvent) then) = + _$PaymentFormEventCopyWithImpl<$Res, PaymentFormEvent>; +} + +/// @nodoc +class _$PaymentFormEventCopyWithImpl<$Res, $Val extends PaymentFormEvent> + implements $PaymentFormEventCopyWith<$Res> { + _$PaymentFormEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$CreateImplCopyWith<$Res> { + factory _$$CreateImplCopyWith( + _$CreateImpl value, $Res Function(_$CreateImpl) then) = + __$$CreateImplCopyWithImpl<$Res>; + @useResult + $Res call({PaymentRequestModel payment}); +} + +/// @nodoc +class __$$CreateImplCopyWithImpl<$Res> + extends _$PaymentFormEventCopyWithImpl<$Res, _$CreateImpl> + implements _$$CreateImplCopyWith<$Res> { + __$$CreateImplCopyWithImpl( + _$CreateImpl _value, $Res Function(_$CreateImpl) _then) + : super(_value, _then); + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? payment = null, + }) { + return _then(_$CreateImpl( + null == payment + ? _value.payment + : payment // ignore: cast_nullable_to_non_nullable + as PaymentRequestModel, + )); + } +} + +/// @nodoc + +class _$CreateImpl implements _Create { + const _$CreateImpl(this.payment); + + @override + final PaymentRequestModel payment; + + @override + String toString() { + return 'PaymentFormEvent.create(payment: $payment)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CreateImpl && + (identical(other.payment, payment) || other.payment == payment)); + } + + @override + int get hashCode => Object.hash(runtimeType, payment); + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CreateImplCopyWith<_$CreateImpl> get copyWith => + __$$CreateImplCopyWithImpl<_$CreateImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(PaymentRequestModel payment) create, + required TResult Function(PaymentSplitBillRequest payment) createSplitBill, + }) { + return create(payment); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(PaymentRequestModel payment)? create, + TResult? Function(PaymentSplitBillRequest payment)? createSplitBill, + }) { + return create?.call(payment); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(PaymentRequestModel payment)? create, + TResult Function(PaymentSplitBillRequest payment)? createSplitBill, + required TResult orElse(), + }) { + if (create != null) { + return create(payment); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Create value) create, + required TResult Function(_CreateSplitBill value) createSplitBill, + }) { + return create(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Create value)? create, + TResult? Function(_CreateSplitBill value)? createSplitBill, + }) { + return create?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Create value)? create, + TResult Function(_CreateSplitBill value)? createSplitBill, + required TResult orElse(), + }) { + if (create != null) { + return create(this); + } + return orElse(); + } +} + +abstract class _Create implements PaymentFormEvent { + const factory _Create(final PaymentRequestModel payment) = _$CreateImpl; + + @override + PaymentRequestModel get payment; + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CreateImplCopyWith<_$CreateImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$CreateSplitBillImplCopyWith<$Res> { + factory _$$CreateSplitBillImplCopyWith(_$CreateSplitBillImpl value, + $Res Function(_$CreateSplitBillImpl) then) = + __$$CreateSplitBillImplCopyWithImpl<$Res>; + @useResult + $Res call({PaymentSplitBillRequest payment}); +} + +/// @nodoc +class __$$CreateSplitBillImplCopyWithImpl<$Res> + extends _$PaymentFormEventCopyWithImpl<$Res, _$CreateSplitBillImpl> + implements _$$CreateSplitBillImplCopyWith<$Res> { + __$$CreateSplitBillImplCopyWithImpl( + _$CreateSplitBillImpl _value, $Res Function(_$CreateSplitBillImpl) _then) + : super(_value, _then); + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? payment = null, + }) { + return _then(_$CreateSplitBillImpl( + null == payment + ? _value.payment + : payment // ignore: cast_nullable_to_non_nullable + as PaymentSplitBillRequest, + )); + } +} + +/// @nodoc + +class _$CreateSplitBillImpl implements _CreateSplitBill { + const _$CreateSplitBillImpl(this.payment); + + @override + final PaymentSplitBillRequest payment; + + @override + String toString() { + return 'PaymentFormEvent.createSplitBill(payment: $payment)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CreateSplitBillImpl && + (identical(other.payment, payment) || other.payment == payment)); + } + + @override + int get hashCode => Object.hash(runtimeType, payment); + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CreateSplitBillImplCopyWith<_$CreateSplitBillImpl> get copyWith => + __$$CreateSplitBillImplCopyWithImpl<_$CreateSplitBillImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(PaymentRequestModel payment) create, + required TResult Function(PaymentSplitBillRequest payment) createSplitBill, + }) { + return createSplitBill(payment); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(PaymentRequestModel payment)? create, + TResult? Function(PaymentSplitBillRequest payment)? createSplitBill, + }) { + return createSplitBill?.call(payment); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(PaymentRequestModel payment)? create, + TResult Function(PaymentSplitBillRequest payment)? createSplitBill, + required TResult orElse(), + }) { + if (createSplitBill != null) { + return createSplitBill(payment); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Create value) create, + required TResult Function(_CreateSplitBill value) createSplitBill, + }) { + return createSplitBill(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Create value)? create, + TResult? Function(_CreateSplitBill value)? createSplitBill, + }) { + return createSplitBill?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Create value)? create, + TResult Function(_CreateSplitBill value)? createSplitBill, + required TResult orElse(), + }) { + if (createSplitBill != null) { + return createSplitBill(this); + } + return orElse(); + } +} + +abstract class _CreateSplitBill implements PaymentFormEvent { + const factory _CreateSplitBill(final PaymentSplitBillRequest payment) = + _$CreateSplitBillImpl; + + @override + PaymentSplitBillRequest get payment; + + /// Create a copy of PaymentFormEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CreateSplitBillImplCopyWith<_$CreateSplitBillImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$PaymentFormState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(PaymentData data) success, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(PaymentData data)? success, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(PaymentData data)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PaymentFormStateCopyWith<$Res> { + factory $PaymentFormStateCopyWith( + PaymentFormState value, $Res Function(PaymentFormState) then) = + _$PaymentFormStateCopyWithImpl<$Res, PaymentFormState>; +} + +/// @nodoc +class _$PaymentFormStateCopyWithImpl<$Res, $Val extends PaymentFormState> + implements $PaymentFormStateCopyWith<$Res> { + _$PaymentFormStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of PaymentFormState + /// 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 _$PaymentFormStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of PaymentFormState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'PaymentFormState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(PaymentData data) success, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(PaymentData data)? success, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(PaymentData data)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements PaymentFormState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$PaymentFormStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of PaymentFormState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'PaymentFormState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(PaymentData data) success, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(PaymentData data)? success, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(PaymentData data)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class _Loading implements PaymentFormState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$SuccessImplCopyWith<$Res> { + factory _$$SuccessImplCopyWith( + _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = + __$$SuccessImplCopyWithImpl<$Res>; + @useResult + $Res call({PaymentData data}); +} + +/// @nodoc +class __$$SuccessImplCopyWithImpl<$Res> + extends _$PaymentFormStateCopyWithImpl<$Res, _$SuccessImpl> + implements _$$SuccessImplCopyWith<$Res> { + __$$SuccessImplCopyWithImpl( + _$SuccessImpl _value, $Res Function(_$SuccessImpl) _then) + : super(_value, _then); + + /// Create a copy of PaymentFormState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? data = null, + }) { + return _then(_$SuccessImpl( + null == data + ? _value.data + : data // ignore: cast_nullable_to_non_nullable + as PaymentData, + )); + } +} + +/// @nodoc + +class _$SuccessImpl implements _Success { + const _$SuccessImpl(this.data); + + @override + final PaymentData data; + + @override + String toString() { + return 'PaymentFormState.success(data: $data)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SuccessImpl && + (identical(other.data, data) || other.data == data)); + } + + @override + int get hashCode => Object.hash(runtimeType, data); + + /// Create a copy of PaymentFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SuccessImplCopyWith<_$SuccessImpl> get copyWith => + __$$SuccessImplCopyWithImpl<_$SuccessImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(PaymentData data) success, + required TResult Function(String message) error, + }) { + return success(data); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(PaymentData data)? success, + TResult? Function(String message)? error, + }) { + return success?.call(data); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(PaymentData data)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(data); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return success(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return success?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(this); + } + return orElse(); + } +} + +abstract class _Success implements PaymentFormState { + const factory _Success(final PaymentData data) = _$SuccessImpl; + + PaymentData get data; + + /// Create a copy of PaymentFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SuccessImplCopyWith<_$SuccessImpl> 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 _$PaymentFormStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of PaymentFormState + /// 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 'PaymentFormState.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 PaymentFormState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(PaymentData data) success, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(PaymentData data)? success, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(PaymentData data)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements PaymentFormState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of PaymentFormState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/sales/blocs/payment_form/payment_form_event.dart b/lib/presentation/sales/blocs/payment_form/payment_form_event.dart new file mode 100644 index 0000000..117209e --- /dev/null +++ b/lib/presentation/sales/blocs/payment_form/payment_form_event.dart @@ -0,0 +1,11 @@ +part of 'payment_form_bloc.dart'; + +@freezed +class PaymentFormEvent with _$PaymentFormEvent { + const factory PaymentFormEvent.create( + PaymentRequestModel payment, + ) = _Create; + const factory PaymentFormEvent.createSplitBill( + PaymentSplitBillRequest payment, + ) = _CreateSplitBill; +} diff --git a/lib/presentation/sales/blocs/payment_form/payment_form_state.dart b/lib/presentation/sales/blocs/payment_form/payment_form_state.dart new file mode 100644 index 0000000..db33745 --- /dev/null +++ b/lib/presentation/sales/blocs/payment_form/payment_form_state.dart @@ -0,0 +1,9 @@ +part of 'payment_form_bloc.dart'; + +@freezed +class PaymentFormState with _$PaymentFormState { + const factory PaymentFormState.initial() = _Initial; + const factory PaymentFormState.loading() = _Loading; + const factory PaymentFormState.success(PaymentData data) = _Success; + const factory PaymentFormState.error(String message) = _Error; +} diff --git a/lib/presentation/sales/dialog/filter_dialog.dart b/lib/presentation/sales/dialog/filter_dialog.dart new file mode 100644 index 0000000..91d74f3 --- /dev/null +++ b/lib/presentation/sales/dialog/filter_dialog.dart @@ -0,0 +1,123 @@ +import 'package:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/components/spaces.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/date_time_ext.dart'; +import 'package:flutter/material.dart'; + +class SalesFilterDialog extends StatefulWidget { + final DateTime startDate; + final DateTime endDate; + final void Function(DateTime start, DateTime end) onDateRangeChanged; + const SalesFilterDialog( + {super.key, + required this.startDate, + required this.endDate, + required this.onDateRangeChanged}); + + @override + State createState() => _SalesFilterDialogState(); +} + +class _SalesFilterDialogState extends State { + late DateTimeRange selectedDateRange; + + @override + void initState() { + selectedDateRange = + DateTimeRange(start: widget.startDate, end: widget.endDate); + super.initState(); + } + + Future _selectDateRange(BuildContext context) async { + final DateTimeRange? picked = await showDateRangePicker( + context: context, + initialDateRange: selectedDateRange, + firstDate: DateTime(2020), + lastDate: DateTime(2100), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: ColorScheme.light( + primary: Colors.blue, // Header color + onPrimary: Colors.white, // Header text color + onSurface: Colors.black, // Body text color + ), + ), + child: child!, + ); + }, + ); + + if (picked != null) { + setState(() { + selectedDateRange = picked; + }); + } + } + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Filter', + contentPadding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Periode', + style: TextStyle( + color: AppColors.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + GestureDetector( + onTap: () async => await _selectDateRange(context), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + margin: EdgeInsets.only(top: 8), + decoration: BoxDecoration( + border: Border.all( + color: AppColors.primary, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Expanded( + child: Text( + '${selectedDateRange.start.toFormattedDate2()} - ${selectedDateRange.end.toFormattedDate2()}', + style: TextStyle( + color: AppColors.black, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + Icon( + Icons.calendar_month_outlined, + color: AppColors.primary, + ), + ], + ), + ), + ), + SpaceHeight(24), + Button.filled( + onPressed: () { + context.pop(); + widget.onDateRangeChanged( + selectedDateRange.start, selectedDateRange.end); + }, + label: 'Terapkan'), + ], + ), + ], + ), + ); + } +} diff --git a/lib/presentation/sales/dialog/payment_dialog.dart b/lib/presentation/sales/dialog/payment_dialog.dart new file mode 100644 index 0000000..d035ae7 --- /dev/null +++ b/lib/presentation/sales/dialog/payment_dialog.dart @@ -0,0 +1,409 @@ +import 'dart:developer'; + +import 'package:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/components/flushbar.dart'; +import 'package:enaklo_pos/core/components/spaces.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/int_ext.dart'; +import 'package:enaklo_pos/core/extensions/string_ext.dart'; +import 'package:enaklo_pos/data/models/request/payment_request.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:enaklo_pos/data/models/response/payment_methods_response_model.dart'; +import 'package:enaklo_pos/data/models/response/product_response_model.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/sales/blocs/payment_form/payment_form_bloc.dart'; +import 'package:enaklo_pos/presentation/success/pages/success_payment_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class PaymentDialog extends StatefulWidget { + final Order order; + const PaymentDialog({super.key, required this.order}); + + @override + State createState() => _PaymentDialogState(); +} + +class _PaymentDialogState extends State { + PaymentMethod? selectedPaymentMethod; + final totalPriceController = TextEditingController(); + int priceValue = 0; + int uangPas = 0; + int uangPas2 = 0; + int uangPas3 = 0; + + init() { + setState(() { + uangPas = widget.order.totalAmount ?? 0; + uangPas2 = 50000; + uangPas3 = 100000; + }); + } + + @override + void initState() { + super.initState(); + context + .read() + .add(PaymentMethodsEvent.fetchPaymentMethods()); + init(); + } + + @override + void dispose() { + super.dispose(); + totalPriceController.dispose(); + } + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Pembayaran', + subtitle: 'Silahkan lakukan pembayaran', + minWidth: context.deviceWidth * 0.6, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: AppColors.grey, + ), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Metode Pembayaran', + style: TextStyle( + color: AppColors.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SpaceHeight(12.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('No. Pesanan'), + Text( + widget.order.orderNumber ?? "", + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + const SpaceHeight(6), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Tagihan'), + Text( + (widget.order.totalAmount ?? 0).currencyFormatRpV2, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + ], + ), + ), + Container( + padding: const EdgeInsets.all(16), + width: double.infinity, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: AppColors.grey, + width: 1.0, + ), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Metode Pembayaran', + style: TextStyle( + color: AppColors.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SpaceHeight(12.0), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const Center( + child: CircularProgressIndicator(), + ), + loading: () => const Center( + child: Column( + children: [ + CircularProgressIndicator(), + SizedBox(height: 8.0), + Text('Loading payment methods...'), + ], + ), + ), + error: (message) => Column( + children: [ + Center( + child: + Text('Error loading payment methods: $message'), + ), + const SpaceHeight(16.0), + Button.filled( + onPressed: () { + context.read().add( + PaymentMethodsEvent.fetchPaymentMethods()); + }, + label: 'Retry', + ), + ], + ), + loaded: (paymentMethods) { + log("Loaded ${paymentMethods.length} payment methods"); + paymentMethods.forEach((method) { + log("Payment method: ${method.name} (ID: ${method.id})"); + }); + if (paymentMethods.isEmpty) { + return Column( + children: [ + const Center( + child: Text('No payment methods available'), + ), + const SpaceHeight(16.0), + Button.filled( + onPressed: () { + context.read().add( + PaymentMethodsEvent + .fetchPaymentMethods()); + }, + label: 'Retry', + ), + ], + ); + } + + return Wrap( + spacing: 12.0, + runSpacing: 8.0, + children: paymentMethods.map((method) { + final isSelected = + selectedPaymentMethod?.id == method.id; + return GestureDetector( + onTap: () { + setState(() { + selectedPaymentMethod = method; + }); + }, + child: Container( + height: 60, + width: 80, + alignment: Alignment.center, + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: isSelected + ? AppColors.primary + : AppColors.white, + border: Border.all( + color: AppColors.primary, + width: 1.0, + ), + borderRadius: BorderRadius.circular(8.0), + ), + child: Text( + method.name ?? "", + style: TextStyle( + color: isSelected + ? AppColors.white + : AppColors.primary, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + ); + }).toList(), + ); + }, + ); + }, + ), + ], + ), + ), + if (selectedPaymentMethod?.type == "cash") + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: AppColors.grey, + width: 1.0, + ), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Total Bayar', + style: TextStyle( + color: AppColors.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SpaceHeight(8.0), + TextFormField( + controller: totalPriceController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + ), + hintText: 'Total harga', + ), + onChanged: (value) { + priceValue = value.toIntegerFromText; + final int newValue = value.toIntegerFromText; + totalPriceController.text = newValue.currencyFormatRp; + totalPriceController.selection = + TextSelection.fromPosition(TextPosition( + offset: totalPriceController.text.length)); + }, + ), + const SpaceHeight(20.0), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + Button.outlined( + width: 150.0, + onPressed: () { + totalPriceController.text = + uangPas.toString().currencyFormatRpV2; + priceValue = uangPas; + }, + label: 'UANG PAS', + ), + const SpaceWidth(20.0), + Button.outlined( + width: 150.0, + onPressed: () { + totalPriceController.text = + uangPas2.toString().currencyFormatRpV2; + priceValue = uangPas2; + }, + label: uangPas2.toString().currencyFormatRpV2, + ), + const SpaceWidth(20.0), + Button.outlined( + width: 150.0, + onPressed: () { + totalPriceController.text = + uangPas3.toString().currencyFormatRpV2; + priceValue = uangPas3; + }, + label: uangPas3.toString().currencyFormatRpV2, + ), + ], + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 24, + horizontal: 16, + ), + child: BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: (data) { + context.pushReplacement(SuccessPaymentPage( + productQuantity: widget.order.orderItems + ?.map( + (item) => ProductQuantity( + product: Product( + name: item.productName, + price: item.unitPrice, + ), + quantity: item.quantity ?? 0, + ), + ) + .toList() ?? + [], + payment: data, + paymentMethod: selectedPaymentMethod?.name ?? "", + nominalBayar: totalPriceController.text.toIntegerFromText, + )); + }, + error: (message) { + AppFlushbar.showError(context, message); + }, + ); + }, + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Button.filled( + onPressed: () { + if (selectedPaymentMethod == null) { + AppFlushbar.showError(context, + 'Pilih metode pembayaran terlebih dahulu'); + return; + } + + if (selectedPaymentMethod?.type == "cash") { + if (priceValue == 0) { + AppFlushbar.showError( + context, 'Total bayar tidak boleh 0'); + return; + } + } + final itemPending = widget.order.orderItems + ?.where((item) => item.status == "pending") + .toList(); + final request = PaymentRequestModel( + amount: widget.order.totalAmount ?? 0, + orderId: widget.order.id, + paymentMethodId: selectedPaymentMethod?.id, + splitDescription: '', + splitNumber: 1, + splitTotal: 1, + transactionId: '', + paymentOrderItems: itemPending + ?.map((item) => PaymentOrderItemModel( + orderItemId: item.id, + amount: item.totalPrice, + )) + .toList()); + + context + .read() + .add(PaymentFormEvent.create(request)); + }, + label: 'Bayar', + ), + loading: () => Center( + child: const CircularProgressIndicator(), + ), + ); + }, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/sales/dialog/refund_dialog.dart b/lib/presentation/sales/dialog/refund_dialog.dart new file mode 100644 index 0000000..6332b74 --- /dev/null +++ b/lib/presentation/sales/dialog/refund_dialog.dart @@ -0,0 +1,211 @@ +import 'package:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/components/custom_text_field.dart'; +import 'package:enaklo_pos/core/components/flushbar.dart'; +import 'package:enaklo_pos/core/components/spaces.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/date_time_ext.dart'; +import 'package:enaklo_pos/core/extensions/string_ext.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/sales/blocs/order_loader/order_loader_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class RefundDialog extends StatefulWidget { + final Order order; + final List selectedItems; + const RefundDialog( + {super.key, required this.order, required this.selectedItems}); + + @override + State createState() => _RefundDialogState(); +} + +class _RefundDialogState extends State { + final TextEditingController _reasonController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Refund', + subtitle: 'Pengembalian dana', + minWidth: context.deviceWidth * 0.5, + contentPadding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('No. Pesanan'), + Text( + widget.order.orderNumber ?? '', + style: TextStyle( + color: AppColors.black, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + SpaceHeight(8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Tanggal'), + Text( + (widget.order.createdAt ?? DateTime.now()).toFormattedDate2(), + style: TextStyle( + color: AppColors.black, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + SpaceHeight(8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Tagihan'), + Text( + widget.selectedItems.isEmpty + ? widget.order.totalAmount.toString().currencyFormatRpV2 + : widget.selectedItems + .fold( + 0, + (sum, item) => + sum + + ((item.unitPrice ?? 0) * (item.quantity ?? 0)), + ) + .toString() + .currencyFormatRpV2, + style: TextStyle( + color: AppColors.black, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.only(top: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Pesanan yang akan di refund', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColors.black, + ), + ), + SpaceHeight(12), + Column( + children: widget.selectedItems + .map((item) => _item(context, item)) + .toList(), + ), + ], + ), + ), + SpaceHeight(16), + CustomTextField( + controller: _reasonController, + label: 'Alasan', + ), + SpaceHeight(24), + BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + successMsg: () { + context.pop(); + AppFlushbar.showSuccess(context, 'Refund Berhasil!'); + context.read().add( + OrderLoaderEvent.getByStatus( + 'completed', + dateFrom: DateTime.now(), + dateTo: DateTime.now(), + ), + ); + }, + error: (msg) { + AppFlushbar.showError(context, msg); + }, + ); + }, + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Button.filled( + onPressed: () { + context.read().add( + OrderFormEvent.refund( + orderId: widget.order.id ?? '', + reason: _reasonController.text, + items: widget.selectedItems, + ), + ); + }, + label: 'Refund', + ), + loading: () => Center( + child: const CircularProgressIndicator(), + ), + ); + }, + ), + ), + ], + ), + ); + } + + Row _item( + BuildContext context, + OrderItem product, + ) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: context.deviceWidth * 0.1, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + product.productName ?? '', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + Text( + (product.unitPrice ?? 0).toString().currencyFormatRpV2, + style: const TextStyle( + fontSize: 14, + ), + ), + ], + ), + ), + Text( + 'X${product.quantity}', + style: const TextStyle( + fontSize: 14, + ), + ), + Text( + (product.totalPrice ?? 0).toString().currencyFormatRpV2, + style: const TextStyle( + fontSize: 14, + ), + ), + ], + ); + } +} diff --git a/lib/presentation/sales/dialog/void_dialog.dart b/lib/presentation/sales/dialog/void_dialog.dart new file mode 100644 index 0000000..ff51e98 --- /dev/null +++ b/lib/presentation/sales/dialog/void_dialog.dart @@ -0,0 +1,211 @@ +import 'package:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/components/custom_text_field.dart'; +import 'package:enaklo_pos/core/components/flushbar.dart'; +import 'package:enaklo_pos/core/components/spaces.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/date_time_ext.dart'; +import 'package:enaklo_pos/core/extensions/string_ext.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/sales/blocs/order_loader/order_loader_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class VoidDialog extends StatefulWidget { + final Order order; + final List selectedItems; + const VoidDialog( + {super.key, required this.order, required this.selectedItems}); + + @override + State createState() => _VoidDialogState(); +} + +class _VoidDialogState extends State { + final TextEditingController _reasonController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Void', + minWidth: context.deviceWidth * 0.5, + contentPadding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('No. Pesanan'), + Text( + widget.order.orderNumber ?? '', + style: TextStyle( + color: AppColors.black, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + SpaceHeight(8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Tanggal'), + Text( + (widget.order.createdAt ?? DateTime.now()).toFormattedDate2(), + style: TextStyle( + color: AppColors.black, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + SpaceHeight(8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Tagihan'), + Text( + widget.selectedItems.isEmpty + ? widget.order.totalAmount.toString().currencyFormatRpV2 + : widget.selectedItems + .fold( + 0, + (sum, item) => + sum + + ((item.unitPrice ?? 0) * (item.quantity ?? 0)), + ) + .toString() + .currencyFormatRpV2, + style: TextStyle( + color: AppColors.black, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + if (widget.selectedItems.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Pesanan yang akan di void', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColors.black, + ), + ), + SpaceHeight(12), + Column( + children: widget.selectedItems + .map((item) => _item(context, item)) + .toList(), + ), + ], + ), + ), + SpaceHeight(16), + CustomTextField( + controller: _reasonController, + label: 'Alasan', + ), + SpaceHeight(24), + BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + successMsg: () { + context.pop(); + AppFlushbar.showSuccess(context, 'Void berhasil!'); + context.read().add( + OrderLoaderEvent.getByStatus( + 'pending', + dateFrom: DateTime.now(), + dateTo: DateTime.now(), + ), + ); + }, + error: (msg) { + AppFlushbar.showError(context, msg); + }, + ); + }, + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Button.filled( + onPressed: () { + context.read().add( + OrderFormEvent.voidOrder( + orderId: widget.order.id ?? '', + reason: _reasonController.text, + items: widget.selectedItems, + ), + ); + }, + label: 'Refund', + ), + loading: () => Center( + child: const CircularProgressIndicator(), + ), + ); + }, + ), + ), + ], + ), + ); + } + + Row _item( + BuildContext context, + OrderItem product, + ) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: context.deviceWidth * 0.1, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + product.productName ?? '', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + Text( + (product.unitPrice ?? 0).toString().currencyFormatRpV2, + style: const TextStyle( + fontSize: 14, + ), + ), + ], + ), + ), + Text( + 'X${product.quantity}', + style: const TextStyle( + fontSize: 14, + ), + ), + Text( + (product.totalPrice ?? 0).toString().currencyFormatRpV2, + style: const TextStyle( + fontSize: 14, + ), + ), + ], + ); + } +} diff --git a/lib/presentation/sales/pages/sales_page.dart b/lib/presentation/sales/pages/sales_page.dart index 648ef0a..5198f4a 100644 --- a/lib/presentation/sales/pages/sales_page.dart +++ b/lib/presentation/sales/pages/sales_page.dart @@ -1,125 +1,297 @@ -import 'dart:developer'; - +import 'package:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.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/payment/pages/payment_page.dart'; +import 'package:enaklo_pos/presentation/refund/pages/refund_page.dart'; +import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart'; +import 'package:enaklo_pos/presentation/sales/widgets/sales_payment_summary.dart'; +import 'package:enaklo_pos/presentation/split_bill/pages/split_bill_page.dart'; +import 'package:enaklo_pos/presentation/void/pages/void_page.dart'; +import 'package:enaklo_pos/presentation/sales/widgets/sales_list_order.dart'; +import 'package:enaklo_pos/presentation/sales/widgets/sales_order_information.dart'; +import 'package:enaklo_pos/presentation/sales/widgets/sales_payment.dart'; +import 'package:enaklo_pos/presentation/sales/widgets/sales_right_title.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:enaklo_pos/core/constants/colors.dart'; -import 'package:enaklo_pos/core/extensions/date_time_ext.dart'; -import 'package:enaklo_pos/presentation/sales/blocs/day_sales/day_sales_bloc.dart'; -import '../widgets/sales_widget.dart'; +import '../../../core/constants/colors.dart'; +import '../widgets/sales_card.dart'; +import '../widgets/sales_title.dart'; class SalesPage extends StatefulWidget { - const SalesPage({super.key}); + final String status; + const SalesPage({super.key, required this.status}); @override State createState() => _SalesPageState(); } class _SalesPageState extends State { + ScrollController scrollController = ScrollController(); + DateTime startDate = DateTime.now(); + DateTime endDate = DateTime.now(); + Order? orderDetail; + + String searchQuery = ''; + @override void initState() { - context.read().add(DaySalesEvent.getDaySales(DateTime.now())); + context.read().add(OrderLoaderEvent.getByStatus( + widget.status, + dateFrom: startDate, + dateTo: endDate)); super.initState(); } @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(32), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Enaklo POS ', - style: TextStyle( - color: AppColors.primary, - fontSize: 22, - fontWeight: FontWeight.w600, - ), - ), - Text( - "${DateTime.now().toFormattedDate()}", - style: const TextStyle( - color: AppColors.subtitle, - fontSize: 16, - ), - ), - ], - ), - const SizedBox( - height: 12.0, - ), - Expanded( - child: BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => const Center( - child: CircularProgressIndicator(), + return SafeArea( + child: Scaffold( + backgroundColor: AppColors.background, + body: Row( + children: [ + Expanded( + flex: 2, + child: NotificationListener( + onNotification: (notification) { + if (notification is ScrollEndNotification && + scrollController.position.extentAfter == 0) { + context.read().add( + OrderLoaderEvent.loadMore( + widget.status, + dateFrom: startDate, + dateTo: endDate, + search: searchQuery, + ), + ); + return true; + } + + return true; + }, + child: Material( + color: AppColors.white, + child: Column( + children: [ + SalesTitle( + title: widget.status == 'pending' + ? "Pending Pesanan" + : "Daftar Pesanan", + startDate: startDate, + endDate: endDate, + onChanged: (value) { + setState(() { + searchQuery = value; + }); + Future.delayed(const Duration(milliseconds: 800), () { + context.read().add( + OrderLoaderEvent.getByStatus( + widget.status, + dateFrom: startDate, + dateTo: endDate, + search: searchQuery, + ), + ); + }); + }, + onDateRangeChanged: (start, end) { + setState(() { + startDate = start; + endDate = end; + }); + + context.read().add( + OrderLoaderEvent.getByStatus(widget.status, + dateFrom: startDate, dateTo: endDate)); + }, + ), + Expanded( + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const Center( + child: CircularProgressIndicator(), + ), + loading: () => const Center( + child: CircularProgressIndicator(), + ), + error: (message) => Center( + child: Text( + message, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + loaded: (orders, totalOrder, hasReachedMax, + currentPage, isLoadingMore) { + if (orders.isEmpty) { + return Center( + child: Text( + "Belum ada transaksi saat ini. ", + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ); + } else { + return ListView.builder( + itemCount: orders.length, + controller: scrollController, + itemBuilder: (context, index) { + return GestureDetector( + onTap: () { + setState(() { + orderDetail = orders[index]; + }); + context.read().add( + OrderFormEvent.started( + orders[index])); + }, + child: SalesCard( + order: orders[index], + isActive: + orders[index] == orderDetail, + ), + ); + }, + ); + } + }, + ); + }, + ), + ), + ], ), - loaded: (orders) { - log("message: ${orders.length}"); - if (orders.isEmpty) { - return Center( - child: Text( - "Belum ada transaksi saat ini. ", - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.bold, + ), + ), + ), + Expanded( + flex: 4, + child: orderDetail == null + ? Center( + child: Text( + "Belum ada order yang dipilih.", + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ) + : Column( + children: [ + SalesRightTitle( + order: orderDetail, + actionWidget: [ + if (widget.status == 'pending') ...[ + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Button.outlined( + onPressed: () {}, + label: 'Void', + icon: Icon(Icons.undo), + ), + loaded: (order, selectedItems, + totalVoidOrRefund, isAllSelected) => + Button.outlined( + onPressed: () { + context.push(VoidPage( + selectedOrder: order, + )); + // showDialog( + // context: context, + // builder: (context) => VoidDialog( + // order: orderDetail!, + // selectedItems: selectedItems, + // ), + // ); + }, + label: 'Void', + icon: Icon(Icons.undo), + ), + ); + }), + SpaceWidth(8), + Button.outlined( + onPressed: () { + context.push( + SplitBillPage( + order: orderDetail!, + ), + ); + }, + label: 'Split Bill', + icon: Icon( + Icons.calculate_outlined, + ), + ), + SpaceWidth(8), + Button.filled( + width: 120, + onPressed: () { + context.push( + PaymentPage( + order: orderDetail!, + ), + ); + }, + label: 'Bayar', + icon: Icon(Icons.payment, color: Colors.white), + ), + ], + if (widget.status == 'completed') + Button.outlined( + onPressed: () { + context.push(RefundPage( + selectedOrder: orderDetail!, + )); + }, + label: 'Refund', + icon: Icon(Icons.autorenew), + ), + ], + ), + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Order Header + SalesOrderInformation( + order: orderDetail, + ), + // Order Items + SalesListOrder(order: orderDetail), + const SpaceHeight(16), + + // Payment Summary + SalesPaymentSummary(order: orderDetail), + const SpaceHeight(16), + + // Payment Information + if (orderDetail?.payments != null && + orderDetail?.payments!.isNotEmpty == + true) ...[ + SalesPayment(order: orderDetail), + const SpaceHeight(20), + ], + ], + ), + ), ), ), - ); - } else { - return SalesWidget( - headerWidgets: _getTitleHeaderWidget(), - orders: orders, - ); - } - }, - ); - }, + ], + ), ), - ), - ], - ), - ); - } - - List _getTitleHeaderWidget() { - return [ - _getTitleItemWidget('ID', 40), - _getTitleItemWidget('Customer', 120), - _getTitleItemWidget('Status', 120), - _getTitleItemWidget('Sync', 60), - _getTitleItemWidget('Payment Status', 120), - _getTitleItemWidget('Payment Method', 120), - _getTitleItemWidget('Payment Amount', 120), - _getTitleItemWidget('Sub Total', 120), - _getTitleItemWidget('Tax', 120), - _getTitleItemWidget('Discount', 60), - _getTitleItemWidget('Service Charge', 120), - _getTitleItemWidget('Total', 120), - _getTitleItemWidget('Payment', 60), - _getTitleItemWidget('Item', 60), - _getTitleItemWidget('Cashier', 150), - _getTitleItemWidget('Time', 230), - _getTitleItemWidget('Action', 230), - ]; - } - - Widget _getTitleItemWidget(String label, double width) { - return Container( - width: width, - height: 56, - color: AppColors.primary, - alignment: Alignment.centerLeft, - child: Center( - child: Text( - label, - style: const TextStyle( - color: Colors.white, - ), + ], ), ), ); diff --git a/lib/presentation/sales/pages/sales_page.dart.backup b/lib/presentation/sales/pages/sales_page.dart.backup new file mode 100644 index 0000000..30b14d5 --- /dev/null +++ b/lib/presentation/sales/pages/sales_page.dart.backup @@ -0,0 +1,127 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/date_time_ext.dart'; +import 'package:enaklo_pos/presentation/sales/blocs/day_sales/day_sales_bloc.dart'; + +import '../widgets/sales_widget.dart'; + +class SalesPage extends StatefulWidget { + const SalesPage({super.key}); + + @override + State createState() => _SalesPageState(); +} + +class _SalesPageState extends State { + @override + void initState() { + context.read().add(DaySalesEvent.getDaySales(DateTime.now())); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(32), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Apskel POS ', + style: TextStyle( + color: AppColors.primary, + fontSize: 22, + fontWeight: FontWeight.w600, + ), + ), + Text( + "${DateTime.now().toFormattedDate()}", + style: const TextStyle( + color: AppColors.subtitle, + fontSize: 16, + ), + ), + ], + ), + const SizedBox( + height: 12.0, + ), + Expanded( + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const Center( + child: CircularProgressIndicator(), + ), + loaded: (orders) { + log("message: ${orders.length}"); + if (orders.isEmpty) { + return Center( + child: Text( + "Belum ada transaksi saat ini. ", + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ); + } else { + return SalesWidget( + headerWidgets: _getTitleHeaderWidget(), + orders: orders, + ); + } + }, + ); + }, + ), + ), + ], + ), + ); + } + + List _getTitleHeaderWidget() { + return [ + _getTitleItemWidget('ID', 40), + _getTitleItemWidget('Customer', 120), + _getTitleItemWidget('Status', 120), + _getTitleItemWidget('Sync', 60), + _getTitleItemWidget('Payment Status', 120), + _getTitleItemWidget('Payment Method', 120), + _getTitleItemWidget('Payment Amount', 120), + _getTitleItemWidget('Sub Total', 120), + _getTitleItemWidget('Tax', 120), + _getTitleItemWidget('Discount', 60), + _getTitleItemWidget('Service Charge', 120), + _getTitleItemWidget('Total', 120), + _getTitleItemWidget('Payment', 60), + _getTitleItemWidget('Item', 60), + _getTitleItemWidget('Cashier', 150), + _getTitleItemWidget('Time', 230), + _getTitleItemWidget('Action', 230), + ]; + } + + Widget _getTitleItemWidget(String label, double width) { + return Container( + width: width, + height: 56, + color: AppColors.primary, + alignment: Alignment.centerLeft, + child: Center( + child: Text( + label, + style: const TextStyle( + color: Colors.white, + ), + ), + ), + ); + } +} diff --git a/lib/presentation/sales/widgets/sales_card.dart b/lib/presentation/sales/widgets/sales_card.dart new file mode 100644 index 0000000..9e9fcae --- /dev/null +++ b/lib/presentation/sales/widgets/sales_card.dart @@ -0,0 +1,206 @@ +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/date_time_ext.dart'; +import 'package:enaklo_pos/core/extensions/int_ext.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:flutter/material.dart'; + +class SalesCard extends StatelessWidget { + final Order order; + final bool isActive; + + const SalesCard({ + super.key, + required this.order, + required this.isActive, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: BoxDecoration( + color: isActive ? AppColors.primary.withOpacity(0.1) : AppColors.white, + border: + Border.all(color: isActive ? AppColors.primary : AppColors.stroke), + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + '${order.orderNumber}', + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ), + if (order.isRefund == true) + Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.15), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + 'Refund', + style: TextStyle( + color: Colors.red, + fontWeight: FontWeight.w600, + fontSize: 10, + letterSpacing: 0.5, + ), + ), + ), + if (order.isVoid == true) + Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.15), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + 'Void', + style: TextStyle( + color: Colors.red, + fontWeight: FontWeight.w600, + fontSize: 10, + letterSpacing: 0.5, + ), + ), + ), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + CircleAvatar( + radius: 22, + backgroundColor: AppColors.primary, + child: Icon(Icons.person, color: Colors.white), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + order.metadata?['customer_name'] == "" + ? "Anonim" + : order.metadata?['customer_name'], + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + if (order.orderType == "dineIn") ...[ + const SizedBox(height: 4), + Row( + children: [ + Icon(Icons.table_bar, size: 16, color: Colors.grey), + const SizedBox(width: 4), + Text( + 'Meja ${order.tableNumber}', + style: TextStyle(color: Colors.grey[600]), + ), + ], + ), + ] + ], + ), + ), + _buildStatus(), + ], + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + order.status == 'pending' + ? ((order.totalAmount ?? 0) - (order.totalPaid ?? 0)) + .currencyFormatRpV2 + : (order.totalAmount ?? 0).currencyFormatRpV2, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + Text( + (order.createdAt ?? DateTime.now()).toFormattedDate3(), + style: TextStyle( + color: AppColors.black, + ), + ), + ], + ), + ], + ), + ), + ); + } + + Widget _buildStatus() { + switch (order.status) { + case 'pending': + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.amber.withOpacity(0.15), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + (order.status ?? "").toUpperCase(), + style: TextStyle( + color: Colors.amber, + fontWeight: FontWeight.w600, + fontSize: 12, + letterSpacing: 0.5, + ), + ), + ); + case 'completed': + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.green.withOpacity(0.15), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + (order.status ?? "").toUpperCase(), + style: TextStyle( + color: Colors.green, + fontWeight: FontWeight.w600, + fontSize: 12, + letterSpacing: 0.5, + ), + ), + ); + default: + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.grey.withOpacity(0.15), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + (order.status ?? "").toUpperCase(), + style: TextStyle( + color: Colors.grey, + fontWeight: FontWeight.w600, + fontSize: 12, + letterSpacing: 0.5, + ), + ), + ); + } + } +} diff --git a/lib/presentation/sales/widgets/sales_list_order.dart b/lib/presentation/sales/widgets/sales_list_order.dart new file mode 100644 index 0000000..8cc32ce --- /dev/null +++ b/lib/presentation/sales/widgets/sales_list_order.dart @@ -0,0 +1,393 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/string_ext.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:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SalesListOrder extends StatelessWidget { + final Order? order; + const SalesListOrder({super.key, this.order}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(top: 16), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(16), + ), + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const SizedBox.shrink(), + loaded: (orderX, selectedItems, totalVoidOrRefund, isAllSelected) => + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(context, isAllSelected), + const SpaceHeight(8), + _buildItemsList(context, selectedItems), + const SpaceHeight(8), + ], + ), + ); + }, + ), + ); + } + + Widget _buildHeader(BuildContext context, bool isAllSelected) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.05), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + border: Border( + bottom: BorderSide( + color: AppColors.primary.withOpacity(0.1), + width: 1, + ), + ), + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Daftar Pembelian', + style: TextStyle( + color: AppColors.black, + fontSize: 18, + fontWeight: FontWeight.w700, + letterSpacing: -0.5, + ), + ), + const SizedBox(height: 4), + Text( + '${order?.orderItems?.length ?? 0} item', + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: AppColors.primary.withOpacity(0.2), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.shopping_cart_outlined, + size: 16, + color: AppColors.primary, + ), + const SizedBox(width: 4), + Text( + 'Order', + style: TextStyle( + color: AppColors.primary, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildItemsList(BuildContext context, List selectedItems) { + return Column( + children: List.generate( + order?.orderItems?.length ?? 0, + (index) { + final item = order!.orderItems![index]; + final isSelected = selectedItems.any((e) => e.id == item.id); + return _buildItem(context, isSelected, item, index); + }, + ).toList(), + ); + } + + Widget _buildItem( + BuildContext context, bool isSelected, OrderItem product, int index) { + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + decoration: BoxDecoration( + color: + isSelected ? AppColors.primary.withOpacity(0.05) : AppColors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: isSelected + ? AppColors.primary.withOpacity(0.3) + : Colors.grey.shade200, + width: isSelected ? 2 : 1, + ), + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + product.productName ?? '', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + letterSpacing: -0.2, + ), + ), + ), + _buildStatusBadge(product.status), + ], + ), + if (product.productVariantName != null) ...[ + const SizedBox(height: 4), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(6), + ), + child: Text( + product.productVariantName ?? '', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.grey.shade700, + ), + ), + ), + ], + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Harga Satuan', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 2), + Text( + (product.unitPrice ?? 0) + .toString() + .currencyFormatRpV2, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + 'x${product.quantity}', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColors.primary, + ), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + 'Total', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 2), + Text( + (product.totalPrice ?? 0) + .toString() + .currencyFormatRpV2, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: AppColors.primary, + ), + ), + ], + ), + ], + ), + ], + ), + ), + ], + ), + if (order?.splitType == 'ITEM' && order?.status == 'pending') ...[ + SpaceHeight(6), + Align( + alignment: Alignment.centerRight, + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: AppColors.primary), + ), + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: '${product.paidQuantity} ', + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: AppColors.primary, + ), + ), + TextSpan( + text: 'dari ', + style: const TextStyle( + fontSize: 12, + color: AppColors.primary, + ), + ), + TextSpan( + text: '${product.quantity} ', + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: AppColors.primary, + ), + ), + TextSpan( + text: 'kuantiti telah dibayar.', + style: const TextStyle( + fontSize: 12, + color: AppColors.primary, + ), + ), + ], + ), + ), + ), + ) + ], + ], + ), + ), + ); + } + + Widget _buildStatusBadge(String? status) { + Color backgroundColor; + Color textColor; + String displayText; + IconData icon; + + switch (status) { + case "pending": + backgroundColor = Colors.white; + textColor = Colors.white; + displayText = "Pending"; + icon = Icons.access_time; + break; + case "cancelled": + backgroundColor = Colors.red.withOpacity(0.1); + textColor = Colors.red.shade700; + displayText = "Batal"; + icon = Icons.cancel_outlined; + break; + case "refund": + backgroundColor = Colors.purple.withOpacity(0.1); + textColor = Colors.purple.shade700; + displayText = "Refund"; + icon = Icons.undo; + break; + default: + backgroundColor = Colors.green.withOpacity(0.1); + textColor = Colors.green.shade700; + displayText = "Selesai"; + icon = Icons.check_circle_outline; + } + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: textColor.withOpacity(0.2), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + icon, + size: 14, + color: textColor, + ), + const SizedBox(width: 4), + Text( + displayText, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: textColor, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/sales/widgets/sales_order_information.dart b/lib/presentation/sales/widgets/sales_order_information.dart new file mode 100644 index 0000000..61f4437 --- /dev/null +++ b/lib/presentation/sales/widgets/sales_order_information.dart @@ -0,0 +1,131 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/date_time_ext.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:flutter/material.dart'; + +class SalesOrderInformation extends StatelessWidget { + final Order? order; + const SalesOrderInformation({super.key, this.order}); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColors.primary, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + order?.orderNumber ?? "", + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + _buildStatus(), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + if (order?.orderType == 'dineIn') ...[ + _buildRowItem(Icons.table_restaurant_outlined, + 'Meja ${order?.tableNumber}'), + const SizedBox(width: 16), + ], + _buildRowItem(Icons.restaurant_outlined, '${order?.orderType}'), + ], + ), + const SizedBox(height: 8), + Text( + 'Pelanggan: ${order?.metadata?['customer_name'] ?? ""}', + style: const TextStyle(color: Colors.white, fontSize: 14), + ), + Text( + 'Dibuat: ${order?.createdAt?.toFormattedDate3() ?? ""}', + style: const TextStyle(color: Colors.white70, fontSize: 12), + ), + ], + ), + ); + } + + Row _buildRowItem(IconData icon, String title) { + return Row( + children: [ + Icon( + icon, + color: Colors.white70, + size: 16, + ), + const SpaceWidth(4), + Text( + title, + style: const TextStyle(color: Colors.white70, fontSize: 14), + ), + ], + ); + } + + Container _buildStatus() { + switch (order?.status) { + case 'pending': + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: Colors.orange, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + (order?.status ?? "").toUpperCase(), + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ); + case 'completed': + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: Colors.greenAccent, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + (order?.status ?? "").toUpperCase(), + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ); + default: + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: Colors.grey, + borderRadius: BorderRadius.circular(12), + ), + child: Text( + (order?.status ?? "").toUpperCase(), + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ); + } + } +} diff --git a/lib/presentation/sales/widgets/sales_payment.dart b/lib/presentation/sales/widgets/sales_payment.dart new file mode 100644 index 0000000..6c83d51 --- /dev/null +++ b/lib/presentation/sales/widgets/sales_payment.dart @@ -0,0 +1,176 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/int_ext.dart'; +import 'package:enaklo_pos/core/extensions/string_ext.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:flutter/material.dart'; + +class SalesPayment extends StatelessWidget { + final Order? order; + const SalesPayment({super.key, this.order}); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Informasi Pembayaran', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + _buildPaymentStatus(), + ], + ), + const SpaceHeight(12), + ...List.generate( + order?.payments?.length ?? 0, + (index) => Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: _buildPaymentItem(order?.payments?[index] ?? Payment()), + ), + ), + const SpaceHeight(4), + const Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Jumlah yang Dibayar', + style: TextStyle(color: Colors.grey.shade700), + ), + Text( + (order?.totalPaid ?? 0).currencyFormatRpV2, + style: TextStyle(fontWeight: FontWeight.w500), + ), + ], + ), + if (((order?.totalAmount ?? 0) - (order?.totalPaid ?? 0)) != 0) ...[ + const SpaceHeight(4), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Sisa Tagihan', + style: TextStyle( + color: Colors.red.shade700, + fontWeight: FontWeight.w500, + ), + ), + Text( + ((order?.totalAmount ?? 0) - (order?.totalPaid ?? 0)) + .currencyFormatRpV2, + style: TextStyle( + color: Colors.red.shade700, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ], + ], + ), + ); + } + + Container _buildPaymentStatus() { + switch (order?.paymentStatus) { + case 'completed': + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: AppColors.green.withOpacity(0.2), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + (order?.paymentStatus ?? "").toTitleCase(), + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: AppColors.green, + ), + ), + ); + default: + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.amber.shade100, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + (order?.paymentStatus ?? "").toTitleCase(), + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: Colors.amber.shade800, + ), + ), + ); + } + } + + Row _buildPaymentItem(Payment payment) { + return Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: Colors.green.shade100, + borderRadius: BorderRadius.circular(4), + ), + child: Icon( + Icons.payments, + color: Colors.green.shade700, + size: 16, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + payment.paymentMethodName ?? "", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + if ((payment.splitTotal ?? 0) > 1) + Text( + 'Split ${payment.splitNumber ?? 0} of ${payment.splitTotal ?? 0}', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + ), + ), + ], + ), + ), + Text( + (payment.amount ?? 0).currencyFormatRpV2, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColors.green, + ), + ), + ], + ); + } +} diff --git a/lib/presentation/sales/widgets/sales_payment_summary.dart b/lib/presentation/sales/widgets/sales_payment_summary.dart new file mode 100644 index 0000000..698f51d --- /dev/null +++ b/lib/presentation/sales/widgets/sales_payment_summary.dart @@ -0,0 +1,94 @@ +import 'package:enaklo_pos/core/components/dashed_divider.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/int_ext.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:flutter/material.dart'; + +class SalesPaymentSummary extends StatelessWidget { + final Order? order; + const SalesPaymentSummary({super.key, this.order}); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Ringkasan Pembayaran', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + const SpaceHeight(12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Subtotal', + style: TextStyle(color: Colors.grey.shade700), + ), + Text((order?.subtotal ?? 0).currencyFormatRpV2), + ], + ), + const SpaceHeight(4), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Tax', + style: TextStyle(color: Colors.grey.shade700), + ), + Text((order?.taxAmount ?? 0).currencyFormatRpV2), + ], + ), + const SpaceHeight(4), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Discount', + style: TextStyle(color: Colors.grey.shade700), + ), + Text((order?.discountAmount ?? 0).currencyFormatRpV2), + ], + ), + const SpaceHeight(8), + const DashedDivider( + color: AppColors.grey, + ), + const SpaceHeight(8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Total', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + Text( + (order?.totalAmount ?? 0).currencyFormatRpV2, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/presentation/sales/widgets/sales_right_title.dart b/lib/presentation/sales/widgets/sales_right_title.dart new file mode 100644 index 0000000..dc9c943 --- /dev/null +++ b/lib/presentation/sales/widgets/sales_right_title.dart @@ -0,0 +1,43 @@ +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:flutter/material.dart'; + +class SalesRightTitle extends StatelessWidget { + final Order? order; + final List? actionWidget; + const SalesRightTitle({super.key, this.order, this.actionWidget}); + + @override + Widget build(BuildContext context) { + return Container( + height: context.deviceHeight * 0.1, + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.background, + width: 1.0, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + "Detail Pesanan", + style: TextStyle( + color: AppColors.black, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + ), + if (actionWidget != null) ...actionWidget!, + ], + ), + ); + } +} diff --git a/lib/presentation/sales/widgets/sales_title.dart b/lib/presentation/sales/widgets/sales_title.dart new file mode 100644 index 0000000..0700828 --- /dev/null +++ b/lib/presentation/sales/widgets/sales_title.dart @@ -0,0 +1,151 @@ +import 'package:enaklo_pos/core/components/components.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/date_time_ext.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_bloc/flutter_bloc.dart'; + +class SalesTitle extends StatelessWidget { + final String title; + final DateTime startDate; + final DateTime endDate; + final Function(String) onChanged; + final void Function(DateTime start, DateTime end) onDateRangeChanged; + + const SalesTitle( + {super.key, + required this.startDate, + required this.endDate, + required this.onChanged, + required this.onDateRangeChanged, + required this.title}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + height: context.deviceHeight * 0.1, + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.background, + width: 1.0, + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + onPressed: () => context.pop(), + icon: Icon(Icons.arrow_back, color: AppColors.black), + ), + SpaceWidth(16), + Expanded( + child: Text( + title, + style: TextStyle( + color: AppColors.black, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 12.0, + ), + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.background, + width: 1.0, + ), + ), + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + startDate.toFormattedDate2() == endDate.toFormattedDate2() + ? startDate.toFormattedDate2() + : '${startDate.toFormattedDate2()} - ${endDate.toFormattedDate2()}', + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.w600, + ), + ), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const SizedBox.shrink(), + loaded: (orders, totalOrder, _, __, ___) => Text( + '$totalOrder Pesanan', + style: TextStyle( + color: AppColors.black, + fontWeight: FontWeight.w600, + ), + ), + ); + }, + ), + ], + ), + SpaceHeight(16), + Row( + children: [ + Expanded( + child: TextFormField( + onChanged: onChanged, + decoration: InputDecoration( + prefixIcon: Icon( + Icons.search, + ), + hintText: 'Cari Pesanan', + ), + ), + ), + SpaceWidth(12), + GestureDetector( + onTap: () => showDialog( + context: context, + builder: (context) => SalesFilterDialog( + startDate: startDate, + endDate: endDate, + onDateRangeChanged: onDateRangeChanged, + ), + ), + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColors.primary, + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.filter_list_outlined, + color: AppColors.white, + size: 24, + ), + ), + ), + ], + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/presentation/setting/bloc/add_product/add_product_bloc.dart b/lib/presentation/setting/bloc/add_product/add_product_bloc.dart index 5efe3f9..e1885bb 100644 --- a/lib/presentation/setting/bloc/add_product/add_product_bloc.dart +++ b/lib/presentation/setting/bloc/add_product/add_product_bloc.dart @@ -1,11 +1,7 @@ -import 'dart:developer'; - import 'package:bloc/bloc.dart'; import 'package:enaklo_pos/data/datasources/product_remote_datasource.dart'; import 'package:enaklo_pos/data/models/request/product_request_model.dart'; -import 'package:enaklo_pos/data/models/response/product_response_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:image_picker/image_picker.dart'; part 'add_product_event.dart'; part 'add_product_state.dart'; @@ -18,21 +14,12 @@ class AddProductBloc extends Bloc { ) : super(const _Initial()) { on<_AddProduct>((event, emit) async { emit(const _Loading()); - final requestData = ProductRequestModel( - name: event.product.name!, - price: int.parse(event.product.price!), - stock: event.product.stock!, - categoryId: event.product.categoryId!, - isBestSeller: event.product.isFavorite!, - image: event.image, - ); - log("requestData: ${requestData.toString()}"); - final response = await datasource.addProduct(requestData); + final response = await datasource.addProduct(event.product); // products.add(newProduct); response.fold( (l) => emit(_Error(l)), (r) { - emit(_Success('Add Product Success')); + emit(_Success('Produk berhasil ditambahkan')); }, ); }); diff --git a/lib/presentation/setting/bloc/add_product/add_product_bloc.freezed.dart b/lib/presentation/setting/bloc/add_product/add_product_bloc.freezed.dart index a910a8e..9e6247b 100644 --- a/lib/presentation/setting/bloc/add_product/add_product_bloc.freezed.dart +++ b/lib/presentation/setting/bloc/add_product/add_product_bloc.freezed.dart @@ -19,19 +19,19 @@ mixin _$AddProductEvent { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product, XFile image) addProduct, + required TResult Function(ProductRequestModel product) addProduct, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product, XFile image)? addProduct, + TResult? Function(ProductRequestModel product)? addProduct, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product, XFile image)? addProduct, + TResult Function(ProductRequestModel product)? addProduct, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -119,7 +119,7 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product, XFile image) addProduct, + required TResult Function(ProductRequestModel product) addProduct, }) { return started(); } @@ -128,7 +128,7 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product, XFile image)? addProduct, + TResult? Function(ProductRequestModel product)? addProduct, }) { return started?.call(); } @@ -137,7 +137,7 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product, XFile image)? addProduct, + TResult Function(ProductRequestModel product)? addProduct, required TResult orElse(), }) { if (started != null) { @@ -188,7 +188,7 @@ abstract class _$$AddProductImplCopyWith<$Res> { _$AddProductImpl value, $Res Function(_$AddProductImpl) then) = __$$AddProductImplCopyWithImpl<$Res>; @useResult - $Res call({Product product, XFile image}); + $Res call({ProductRequestModel product}); } /// @nodoc @@ -205,17 +205,12 @@ class __$$AddProductImplCopyWithImpl<$Res> @override $Res call({ Object? product = null, - Object? image = null, }) { return _then(_$AddProductImpl( null == product ? _value.product : product // ignore: cast_nullable_to_non_nullable - as Product, - null == image - ? _value.image - : image // ignore: cast_nullable_to_non_nullable - as XFile, + as ProductRequestModel, )); } } @@ -223,16 +218,14 @@ class __$$AddProductImplCopyWithImpl<$Res> /// @nodoc class _$AddProductImpl implements _AddProduct { - const _$AddProductImpl(this.product, this.image); + const _$AddProductImpl(this.product); @override - final Product product; - @override - final XFile image; + final ProductRequestModel product; @override String toString() { - return 'AddProductEvent.addProduct(product: $product, image: $image)'; + return 'AddProductEvent.addProduct(product: $product)'; } @override @@ -240,12 +233,11 @@ class _$AddProductImpl implements _AddProduct { return identical(this, other) || (other.runtimeType == runtimeType && other is _$AddProductImpl && - (identical(other.product, product) || other.product == product) && - (identical(other.image, image) || other.image == image)); + (identical(other.product, product) || other.product == product)); } @override - int get hashCode => Object.hash(runtimeType, product, image); + int get hashCode => Object.hash(runtimeType, product); /// Create a copy of AddProductEvent /// with the given fields replaced by the non-null parameter values. @@ -259,29 +251,29 @@ class _$AddProductImpl implements _AddProduct { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(Product product, XFile image) addProduct, + required TResult Function(ProductRequestModel product) addProduct, }) { - return addProduct(product, image); + return addProduct(product); } @override @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(Product product, XFile image)? addProduct, + TResult? Function(ProductRequestModel product)? addProduct, }) { - return addProduct?.call(product, image); + return addProduct?.call(product); } @override @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(Product product, XFile image)? addProduct, + TResult Function(ProductRequestModel product)? addProduct, required TResult orElse(), }) { if (addProduct != null) { - return addProduct(product, image); + return addProduct(product); } return orElse(); } @@ -319,11 +311,10 @@ class _$AddProductImpl implements _AddProduct { } abstract class _AddProduct implements AddProductEvent { - const factory _AddProduct(final Product product, final XFile image) = + const factory _AddProduct(final ProductRequestModel product) = _$AddProductImpl; - Product get product; - XFile get image; + ProductRequestModel get product; /// Create a copy of AddProductEvent /// with the given fields replaced by the non-null parameter values. diff --git a/lib/presentation/setting/bloc/add_product/add_product_event.dart b/lib/presentation/setting/bloc/add_product/add_product_event.dart index 39e69b7..229716a 100644 --- a/lib/presentation/setting/bloc/add_product/add_product_event.dart +++ b/lib/presentation/setting/bloc/add_product/add_product_event.dart @@ -3,6 +3,6 @@ part of 'add_product_bloc.dart'; @freezed class AddProductEvent with _$AddProductEvent { const factory AddProductEvent.started() = _Started; - const factory AddProductEvent.addProduct(Product product, XFile image) = + const factory AddProductEvent.addProduct(ProductRequestModel product) = _AddProduct; } diff --git a/lib/presentation/setting/bloc/get_categories/get_categories_bloc.dart b/lib/presentation/setting/bloc/get_categories/get_categories_bloc.dart index 18fdc6b..e7ab7f6 100644 --- a/lib/presentation/setting/bloc/get_categories/get_categories_bloc.dart +++ b/lib/presentation/setting/bloc/get_categories/get_categories_bloc.dart @@ -1,4 +1,3 @@ - import 'package:bloc/bloc.dart'; import 'package:enaklo_pos/data/datasources/category_remote_datasource.dart'; import 'package:enaklo_pos/data/models/response/category_response_model.dart'; @@ -15,11 +14,11 @@ class GetCategoriesBloc extends Bloc { ) : super(const _Initial()) { on<_Fetch>((event, emit) async { emit(const _Loading()); - final result = await datasource.getCategories(); + final result = await datasource.getCategories(limit: 50); result.fold( (l) => emit(_Error(l)), (r) async { - emit(_Success(r.data)); + emit(_Success(r.data.categories)); }, ); }); diff --git a/lib/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_bloc.dart b/lib/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_bloc.dart new file mode 100644 index 0000000..c3101b0 --- /dev/null +++ b/lib/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_bloc.dart @@ -0,0 +1,20 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; +import 'package:enaklo_pos/data/models/response/print_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'get_printer_ticket_event.dart'; +part 'get_printer_ticket_state.dart'; +part 'get_printer_ticket_bloc.freezed.dart'; + +class GetPrinterTicketBloc + extends Bloc { + GetPrinterTicketBloc() : super(_Initial()) { + on<_Get>((event, emit) async { + emit(_Loading()); + final result = + await ProductLocalDatasource.instance.getPrinterByCode('ticket'); + emit(_Success(result)); + }); + } +} diff --git a/lib/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_bloc.freezed.dart b/lib/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_bloc.freezed.dart new file mode 100644 index 0000000..f091fec --- /dev/null +++ b/lib/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_bloc.freezed.dart @@ -0,0 +1,608 @@ +// 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 'get_printer_ticket_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$GetPrinterTicketEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() get, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? get, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? get, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Get value) get, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Get value)? get, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Get value)? get, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $GetPrinterTicketEventCopyWith<$Res> { + factory $GetPrinterTicketEventCopyWith(GetPrinterTicketEvent value, + $Res Function(GetPrinterTicketEvent) then) = + _$GetPrinterTicketEventCopyWithImpl<$Res, GetPrinterTicketEvent>; +} + +/// @nodoc +class _$GetPrinterTicketEventCopyWithImpl<$Res, + $Val extends GetPrinterTicketEvent> + implements $GetPrinterTicketEventCopyWith<$Res> { + _$GetPrinterTicketEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of GetPrinterTicketEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$GetImplCopyWith<$Res> { + factory _$$GetImplCopyWith(_$GetImpl value, $Res Function(_$GetImpl) then) = + __$$GetImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$GetImplCopyWithImpl<$Res> + extends _$GetPrinterTicketEventCopyWithImpl<$Res, _$GetImpl> + implements _$$GetImplCopyWith<$Res> { + __$$GetImplCopyWithImpl(_$GetImpl _value, $Res Function(_$GetImpl) _then) + : super(_value, _then); + + /// Create a copy of GetPrinterTicketEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$GetImpl implements _Get { + const _$GetImpl(); + + @override + String toString() { + return 'GetPrinterTicketEvent.get()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$GetImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() get, + }) { + return get(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? get, + }) { + return get?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? get, + required TResult orElse(), + }) { + if (get != null) { + return get(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Get value) get, + }) { + return get(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Get value)? get, + }) { + return get?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Get value)? get, + required TResult orElse(), + }) { + if (get != null) { + return get(this); + } + return orElse(); + } +} + +abstract class _Get implements GetPrinterTicketEvent { + const factory _Get() = _$GetImpl; +} + +/// @nodoc +mixin _$GetPrinterTicketState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(PrintModel? printer) success, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(PrintModel? printer)? success, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(PrintModel? printer)? success, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $GetPrinterTicketStateCopyWith<$Res> { + factory $GetPrinterTicketStateCopyWith(GetPrinterTicketState value, + $Res Function(GetPrinterTicketState) then) = + _$GetPrinterTicketStateCopyWithImpl<$Res, GetPrinterTicketState>; +} + +/// @nodoc +class _$GetPrinterTicketStateCopyWithImpl<$Res, + $Val extends GetPrinterTicketState> + implements $GetPrinterTicketStateCopyWith<$Res> { + _$GetPrinterTicketStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of GetPrinterTicketState + /// 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 _$GetPrinterTicketStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of GetPrinterTicketState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'GetPrinterTicketState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(PrintModel? printer) success, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(PrintModel? printer)? success, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(PrintModel? printer)? success, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements GetPrinterTicketState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$GetPrinterTicketStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of GetPrinterTicketState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'GetPrinterTicketState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(PrintModel? printer) success, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(PrintModel? printer)? success, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(PrintModel? printer)? success, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class _Loading implements GetPrinterTicketState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$SuccessImplCopyWith<$Res> { + factory _$$SuccessImplCopyWith( + _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = + __$$SuccessImplCopyWithImpl<$Res>; + @useResult + $Res call({PrintModel? printer}); +} + +/// @nodoc +class __$$SuccessImplCopyWithImpl<$Res> + extends _$GetPrinterTicketStateCopyWithImpl<$Res, _$SuccessImpl> + implements _$$SuccessImplCopyWith<$Res> { + __$$SuccessImplCopyWithImpl( + _$SuccessImpl _value, $Res Function(_$SuccessImpl) _then) + : super(_value, _then); + + /// Create a copy of GetPrinterTicketState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? printer = freezed, + }) { + return _then(_$SuccessImpl( + freezed == printer + ? _value.printer + : printer // ignore: cast_nullable_to_non_nullable + as PrintModel?, + )); + } +} + +/// @nodoc + +class _$SuccessImpl implements _Success { + const _$SuccessImpl(this.printer); + + @override + final PrintModel? printer; + + @override + String toString() { + return 'GetPrinterTicketState.success(printer: $printer)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SuccessImpl && + (identical(other.printer, printer) || other.printer == printer)); + } + + @override + int get hashCode => Object.hash(runtimeType, printer); + + /// Create a copy of GetPrinterTicketState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SuccessImplCopyWith<_$SuccessImpl> get copyWith => + __$$SuccessImplCopyWithImpl<_$SuccessImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(PrintModel? printer) success, + }) { + return success(printer); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(PrintModel? printer)? success, + }) { + return success?.call(printer); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(PrintModel? printer)? success, + required TResult orElse(), + }) { + if (success != null) { + return success(printer); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + }) { + return success(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + }) { + return success?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + required TResult orElse(), + }) { + if (success != null) { + return success(this); + } + return orElse(); + } +} + +abstract class _Success implements GetPrinterTicketState { + const factory _Success(final PrintModel? printer) = _$SuccessImpl; + + PrintModel? get printer; + + /// Create a copy of GetPrinterTicketState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SuccessImplCopyWith<_$SuccessImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_event.dart b/lib/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_event.dart new file mode 100644 index 0000000..4bbc8f9 --- /dev/null +++ b/lib/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_event.dart @@ -0,0 +1,6 @@ +part of 'get_printer_ticket_bloc.dart'; + +@freezed +class GetPrinterTicketEvent with _$GetPrinterTicketEvent { + const factory GetPrinterTicketEvent.get() = _Get; +} diff --git a/lib/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_state.dart b/lib/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_state.dart new file mode 100644 index 0000000..9df4697 --- /dev/null +++ b/lib/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_state.dart @@ -0,0 +1,8 @@ +part of 'get_printer_ticket_bloc.dart'; + +@freezed +class GetPrinterTicketState with _$GetPrinterTicketState { + const factory GetPrinterTicketState.initial() = _Initial; + const factory GetPrinterTicketState.loading() = _Loading; + const factory GetPrinterTicketState.success(PrintModel? printer) = _Success; +} diff --git a/lib/presentation/setting/bloc/get_products/get_products_bloc.dart b/lib/presentation/setting/bloc/get_products/get_products_bloc.dart index bf492ce..485c792 100644 --- a/lib/presentation/setting/bloc/get_products/get_products_bloc.dart +++ b/lib/presentation/setting/bloc/get_products/get_products_bloc.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:enaklo_pos/data/datasources/product_remote_datasource.dart'; @@ -9,19 +10,137 @@ part 'get_products_state.dart'; part 'get_products_bloc.freezed.dart'; class GetProductsBloc extends Bloc { - final ProductRemoteDatasource datasource; - GetProductsBloc( - this.datasource, - ) : super(const _Initial()) { - on<_Fetch>((event, emit) async { - emit(const _Loading()); - final response = await datasource.getProducts(); - response.fold( - (l) => emit(_Error(l)), - (r) { - emit(_Success(r.data!)); + final ProductRemoteDatasource _productRemoteDatasource; + + // Debouncing untuk mencegah multiple load more calls + Timer? _loadMoreDebounce; + bool _isLoadingMore = false; + + GetProductsBloc(this._productRemoteDatasource) + : super(GetProductsState.initial()) { + on<_Fetch>(_onGetProduct); + on<_LoadMore>(_onLoadMore); + on<_Refresh>(_onRefresh); + } + + @override + Future close() { + _loadMoreDebounce?.cancel(); + return super.close(); + } + + // Debounce transformer untuk load more + // EventTransformer _debounceTransformer() { + // return (events, mapper) { + // return events + // .debounceTime(const Duration(milliseconds: 300)) + // .asyncExpand(mapper); + // }; + // } + + // Initial load + Future _onGetProduct( + _Fetch event, + Emitter emit, + ) async { + emit(const _Loading()); + _isLoadingMore = false; // Reset loading state + + final result = await _productRemoteDatasource.getProducts( + page: 1, + limit: 10, + ); + + await result.fold( + (failure) async => emit(_Error(failure)), + (response) async { + final products = response.data?.products ?? []; + final hasReachedMax = products.length < 10; + + emit(_Success( + products: products, + hasReachedMax: hasReachedMax, + currentPage: 1, + isLoadingMore: false, + )); + }, + ); + } + + // Load more with enhanced debouncing + Future _onLoadMore( + _LoadMore event, + Emitter emit, + ) async { + final currentState = state; + + // Enhanced validation + if (currentState is! _Success || + currentState.hasReachedMax || + _isLoadingMore || + currentState.isLoadingMore) { + return; + } + + _isLoadingMore = true; + + // Emit loading more state + emit(currentState.copyWith(isLoadingMore: true)); + + final nextPage = currentState.currentPage + 1; + + try { + final result = await _productRemoteDatasource.getProducts( + page: nextPage, + limit: 10, + ); + + await result.fold( + (failure) async { + // On error, revert loading state but don't show error + // Just silently fail and allow retry + emit(currentState.copyWith(isLoadingMore: false)); + _isLoadingMore = false; + }, + (response) async { + final newProducts = response.data?.products ?? []; + + // Prevent duplicate products + final currentProductIds = + currentState.products.map((p) => p.id).toSet(); + final filteredNewProducts = newProducts + .where((product) => !currentProductIds.contains(product.id)) + .toList(); + + final allProducts = List.from(currentState.products) + ..addAll(filteredNewProducts); + + final hasReachedMax = newProducts.length < 10; + + emit(_Success( + products: allProducts, + hasReachedMax: hasReachedMax, + currentPage: nextPage, + isLoadingMore: false, + )); + + _isLoadingMore = false; }, ); - }); + } catch (e) { + // Handle unexpected errors + emit(currentState.copyWith(isLoadingMore: false)); + _isLoadingMore = false; + } + } + + // Refresh data + Future _onRefresh( + _Refresh event, + Emitter emit, + ) async { + _isLoadingMore = false; + _loadMoreDebounce?.cancel(); + add(const _Fetch()); } } diff --git a/lib/presentation/setting/bloc/get_products/get_products_bloc.freezed.dart b/lib/presentation/setting/bloc/get_products/get_products_bloc.freezed.dart index 8872dee..41ff493 100644 --- a/lib/presentation/setting/bloc/get_products/get_products_bloc.freezed.dart +++ b/lib/presentation/setting/bloc/get_products/get_products_bloc.freezed.dart @@ -18,39 +18,45 @@ final _privateConstructorUsedError = UnsupportedError( mixin _$GetProductsEvent { @optionalTypeArgs TResult when({ - required TResult Function() started, required TResult Function() fetch, + required TResult Function() loadMore, + required TResult Function() refresh, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function()? started, TResult? Function()? fetch, + TResult? Function()? loadMore, + TResult? Function()? refresh, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function()? started, TResult Function()? fetch, + TResult Function()? loadMore, + TResult Function()? refresh, required TResult orElse(), }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult map({ - required TResult Function(_Started value) started, required TResult Function(_Fetch value) fetch, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? mapOrNull({ - TResult? Function(_Started value)? started, TResult? Function(_Fetch value)? fetch, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeMap({ - TResult Function(_Started value)? started, TResult Function(_Fetch value)? fetch, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -77,111 +83,6 @@ class _$GetProductsEventCopyWithImpl<$Res, $Val extends GetProductsEvent> /// with the given fields replaced by the non-null parameter values. } -/// @nodoc -abstract class _$$StartedImplCopyWith<$Res> { - factory _$$StartedImplCopyWith( - _$StartedImpl value, $Res Function(_$StartedImpl) then) = - __$$StartedImplCopyWithImpl<$Res>; -} - -/// @nodoc -class __$$StartedImplCopyWithImpl<$Res> - extends _$GetProductsEventCopyWithImpl<$Res, _$StartedImpl> - implements _$$StartedImplCopyWith<$Res> { - __$$StartedImplCopyWithImpl( - _$StartedImpl _value, $Res Function(_$StartedImpl) _then) - : super(_value, _then); - - /// Create a copy of GetProductsEvent - /// with the given fields replaced by the non-null parameter values. -} - -/// @nodoc - -class _$StartedImpl implements _Started { - const _$StartedImpl(); - - @override - String toString() { - return 'GetProductsEvent.started()'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && other is _$StartedImpl); - } - - @override - int get hashCode => runtimeType.hashCode; - - @override - @optionalTypeArgs - TResult when({ - required TResult Function() started, - required TResult Function() fetch, - }) { - return started(); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function()? started, - TResult? Function()? fetch, - }) { - return started?.call(); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function()? started, - TResult Function()? fetch, - required TResult orElse(), - }) { - if (started != null) { - return started(); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(_Started value) started, - required TResult Function(_Fetch value) fetch, - }) { - return started(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(_Started value)? started, - TResult? Function(_Fetch value)? fetch, - }) { - return started?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(_Started value)? started, - TResult Function(_Fetch value)? fetch, - required TResult orElse(), - }) { - if (started != null) { - return started(this); - } - return orElse(); - } -} - -abstract class _Started implements GetProductsEvent { - const factory _Started() = _$StartedImpl; -} - /// @nodoc abstract class _$$FetchImplCopyWith<$Res> { factory _$$FetchImplCopyWith( @@ -223,8 +124,9 @@ class _$FetchImpl implements _Fetch { @override @optionalTypeArgs TResult when({ - required TResult Function() started, required TResult Function() fetch, + required TResult Function() loadMore, + required TResult Function() refresh, }) { return fetch(); } @@ -232,8 +134,9 @@ class _$FetchImpl implements _Fetch { @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function()? started, TResult? Function()? fetch, + TResult? Function()? loadMore, + TResult? Function()? refresh, }) { return fetch?.call(); } @@ -241,8 +144,9 @@ class _$FetchImpl implements _Fetch { @override @optionalTypeArgs TResult maybeWhen({ - TResult Function()? started, TResult Function()? fetch, + TResult Function()? loadMore, + TResult Function()? refresh, required TResult orElse(), }) { if (fetch != null) { @@ -254,8 +158,9 @@ class _$FetchImpl implements _Fetch { @override @optionalTypeArgs TResult map({ - required TResult Function(_Started value) started, required TResult Function(_Fetch value) fetch, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, }) { return fetch(this); } @@ -263,8 +168,9 @@ class _$FetchImpl implements _Fetch { @override @optionalTypeArgs TResult? mapOrNull({ - TResult? Function(_Started value)? started, TResult? Function(_Fetch value)? fetch, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, }) { return fetch?.call(this); } @@ -272,8 +178,9 @@ class _$FetchImpl implements _Fetch { @override @optionalTypeArgs TResult maybeMap({ - TResult Function(_Started value)? started, TResult Function(_Fetch value)? fetch, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, required TResult orElse(), }) { if (fetch != null) { @@ -287,13 +194,237 @@ abstract class _Fetch implements GetProductsEvent { const factory _Fetch() = _$FetchImpl; } +/// @nodoc +abstract class _$$LoadMoreImplCopyWith<$Res> { + factory _$$LoadMoreImplCopyWith( + _$LoadMoreImpl value, $Res Function(_$LoadMoreImpl) then) = + __$$LoadMoreImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadMoreImplCopyWithImpl<$Res> + extends _$GetProductsEventCopyWithImpl<$Res, _$LoadMoreImpl> + implements _$$LoadMoreImplCopyWith<$Res> { + __$$LoadMoreImplCopyWithImpl( + _$LoadMoreImpl _value, $Res Function(_$LoadMoreImpl) _then) + : super(_value, _then); + + /// Create a copy of GetProductsEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadMoreImpl implements _LoadMore { + const _$LoadMoreImpl(); + + @override + String toString() { + return 'GetProductsEvent.loadMore()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$LoadMoreImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() fetch, + required TResult Function() loadMore, + required TResult Function() refresh, + }) { + return loadMore(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? fetch, + TResult? Function()? loadMore, + TResult? Function()? refresh, + }) { + return loadMore?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? fetch, + TResult Function()? loadMore, + TResult Function()? refresh, + required TResult orElse(), + }) { + if (loadMore != null) { + return loadMore(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Fetch value) fetch, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + }) { + return loadMore(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Fetch value)? fetch, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + }) { + return loadMore?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Fetch value)? fetch, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + required TResult orElse(), + }) { + if (loadMore != null) { + return loadMore(this); + } + return orElse(); + } +} + +abstract class _LoadMore implements GetProductsEvent { + const factory _LoadMore() = _$LoadMoreImpl; +} + +/// @nodoc +abstract class _$$RefreshImplCopyWith<$Res> { + factory _$$RefreshImplCopyWith( + _$RefreshImpl value, $Res Function(_$RefreshImpl) then) = + __$$RefreshImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$RefreshImplCopyWithImpl<$Res> + extends _$GetProductsEventCopyWithImpl<$Res, _$RefreshImpl> + implements _$$RefreshImplCopyWith<$Res> { + __$$RefreshImplCopyWithImpl( + _$RefreshImpl _value, $Res Function(_$RefreshImpl) _then) + : super(_value, _then); + + /// Create a copy of GetProductsEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$RefreshImpl implements _Refresh { + const _$RefreshImpl(); + + @override + String toString() { + return 'GetProductsEvent.refresh()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$RefreshImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() fetch, + required TResult Function() loadMore, + required TResult Function() refresh, + }) { + return refresh(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? fetch, + TResult? Function()? loadMore, + TResult? Function()? refresh, + }) { + return refresh?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? fetch, + TResult Function()? loadMore, + TResult Function()? refresh, + required TResult orElse(), + }) { + if (refresh != null) { + return refresh(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Fetch value) fetch, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + }) { + return refresh(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Fetch value)? fetch, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + }) { + return refresh?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Fetch value)? fetch, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + required TResult orElse(), + }) { + if (refresh != null) { + return refresh(this); + } + return orElse(); + } +} + +abstract class _Refresh implements GetProductsEvent { + const factory _Refresh() = _$RefreshImpl; +} + /// @nodoc mixin _$GetProductsState { @optionalTypeArgs TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List products) success, + required TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore) + success, required TResult Function(String message) error, }) => throw _privateConstructorUsedError; @@ -301,7 +432,9 @@ mixin _$GetProductsState { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List products)? success, + TResult? Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + success, TResult? Function(String message)? error, }) => throw _privateConstructorUsedError; @@ -309,7 +442,9 @@ mixin _$GetProductsState { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List products)? success, + TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + success, TResult Function(String message)? error, required TResult orElse(), }) => @@ -405,7 +540,9 @@ class _$InitialImpl implements _Initial { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List products) success, + required TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore) + success, required TResult Function(String message) error, }) { return initial(); @@ -416,7 +553,9 @@ class _$InitialImpl implements _Initial { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List products)? success, + TResult? Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + success, TResult? Function(String message)? error, }) { return initial?.call(); @@ -427,7 +566,9 @@ class _$InitialImpl implements _Initial { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List products)? success, + TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + success, TResult Function(String message)? error, required TResult orElse(), }) { @@ -522,7 +663,9 @@ class _$LoadingImpl implements _Loading { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List products) success, + required TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore) + success, required TResult Function(String message) error, }) { return loading(); @@ -533,7 +676,9 @@ class _$LoadingImpl implements _Loading { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List products)? success, + TResult? Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + success, TResult? Function(String message)? error, }) { return loading?.call(); @@ -544,7 +689,9 @@ class _$LoadingImpl implements _Loading { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List products)? success, + TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + success, TResult Function(String message)? error, required TResult orElse(), }) { @@ -602,7 +749,11 @@ abstract class _$$SuccessImplCopyWith<$Res> { _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = __$$SuccessImplCopyWithImpl<$Res>; @useResult - $Res call({List products}); + $Res call( + {List products, + bool hasReachedMax, + int currentPage, + bool isLoadingMore}); } /// @nodoc @@ -619,12 +770,27 @@ class __$$SuccessImplCopyWithImpl<$Res> @override $Res call({ Object? products = null, + Object? hasReachedMax = null, + Object? currentPage = null, + Object? isLoadingMore = null, }) { return _then(_$SuccessImpl( - null == products + products: null == products ? _value._products : products // ignore: cast_nullable_to_non_nullable as List, + hasReachedMax: null == hasReachedMax + ? _value.hasReachedMax + : hasReachedMax // ignore: cast_nullable_to_non_nullable + as bool, + currentPage: null == currentPage + ? _value.currentPage + : currentPage // ignore: cast_nullable_to_non_nullable + as int, + isLoadingMore: null == isLoadingMore + ? _value.isLoadingMore + : isLoadingMore // ignore: cast_nullable_to_non_nullable + as bool, )); } } @@ -632,7 +798,12 @@ class __$$SuccessImplCopyWithImpl<$Res> /// @nodoc class _$SuccessImpl implements _Success { - const _$SuccessImpl(final List products) : _products = products; + const _$SuccessImpl( + {required final List products, + required this.hasReachedMax, + required this.currentPage, + required this.isLoadingMore}) + : _products = products; final List _products; @override @@ -642,9 +813,16 @@ class _$SuccessImpl implements _Success { return EqualUnmodifiableListView(_products); } + @override + final bool hasReachedMax; + @override + final int currentPage; + @override + final bool isLoadingMore; + @override String toString() { - return 'GetProductsState.success(products: $products)'; + return 'GetProductsState.success(products: $products, hasReachedMax: $hasReachedMax, currentPage: $currentPage, isLoadingMore: $isLoadingMore)'; } @override @@ -652,12 +830,22 @@ class _$SuccessImpl implements _Success { return identical(this, other) || (other.runtimeType == runtimeType && other is _$SuccessImpl && - const DeepCollectionEquality().equals(other._products, _products)); + const DeepCollectionEquality().equals(other._products, _products) && + (identical(other.hasReachedMax, hasReachedMax) || + other.hasReachedMax == hasReachedMax) && + (identical(other.currentPage, currentPage) || + other.currentPage == currentPage) && + (identical(other.isLoadingMore, isLoadingMore) || + other.isLoadingMore == isLoadingMore)); } @override - int get hashCode => - Object.hash(runtimeType, const DeepCollectionEquality().hash(_products)); + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_products), + hasReachedMax, + currentPage, + isLoadingMore); /// Create a copy of GetProductsState /// with the given fields replaced by the non-null parameter values. @@ -672,10 +860,12 @@ class _$SuccessImpl implements _Success { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List products) success, + required TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore) + success, required TResult Function(String message) error, }) { - return success(products); + return success(products, hasReachedMax, currentPage, isLoadingMore); } @override @@ -683,10 +873,12 @@ class _$SuccessImpl implements _Success { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List products)? success, + TResult? Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + success, TResult? Function(String message)? error, }) { - return success?.call(products); + return success?.call(products, hasReachedMax, currentPage, isLoadingMore); } @override @@ -694,12 +886,14 @@ class _$SuccessImpl implements _Success { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List products)? success, + TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + success, TResult Function(String message)? error, required TResult orElse(), }) { if (success != null) { - return success(products); + return success(products, hasReachedMax, currentPage, isLoadingMore); } return orElse(); } @@ -743,9 +937,16 @@ class _$SuccessImpl implements _Success { } abstract class _Success implements GetProductsState { - const factory _Success(final List products) = _$SuccessImpl; + const factory _Success( + {required final List products, + required final bool hasReachedMax, + required final int currentPage, + required final bool isLoadingMore}) = _$SuccessImpl; List get products; + bool get hasReachedMax; + int get currentPage; + bool get isLoadingMore; /// Create a copy of GetProductsState /// with the given fields replaced by the non-null parameter values. @@ -824,7 +1025,9 @@ class _$ErrorImpl implements _Error { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List products) success, + required TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore) + success, required TResult Function(String message) error, }) { return error(message); @@ -835,7 +1038,9 @@ class _$ErrorImpl implements _Error { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List products)? success, + TResult? Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + success, TResult? Function(String message)? error, }) { return error?.call(message); @@ -846,7 +1051,9 @@ class _$ErrorImpl implements _Error { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List products)? success, + TResult Function(List products, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + success, TResult Function(String message)? error, required TResult orElse(), }) { diff --git a/lib/presentation/setting/bloc/get_products/get_products_event.dart b/lib/presentation/setting/bloc/get_products/get_products_event.dart index 18fe6d2..0f28bf8 100644 --- a/lib/presentation/setting/bloc/get_products/get_products_event.dart +++ b/lib/presentation/setting/bloc/get_products/get_products_event.dart @@ -2,6 +2,7 @@ part of 'get_products_bloc.dart'; @freezed class GetProductsEvent with _$GetProductsEvent { - const factory GetProductsEvent.started() = _Started; const factory GetProductsEvent.fetch() = _Fetch; + const factory GetProductsEvent.loadMore() = _LoadMore; + const factory GetProductsEvent.refresh() = _Refresh; } diff --git a/lib/presentation/setting/bloc/get_products/get_products_state.dart b/lib/presentation/setting/bloc/get_products/get_products_state.dart index a0b9636..4c786ae 100644 --- a/lib/presentation/setting/bloc/get_products/get_products_state.dart +++ b/lib/presentation/setting/bloc/get_products/get_products_state.dart @@ -4,6 +4,11 @@ part of 'get_products_bloc.dart'; class GetProductsState with _$GetProductsState { const factory GetProductsState.initial() = _Initial; const factory GetProductsState.loading() = _Loading; - const factory GetProductsState.success(List products) = _Success; + const factory GetProductsState.success({ + required List products, + required bool hasReachedMax, + required int currentPage, + required bool isLoadingMore, + }) = _Success; const factory GetProductsState.error(String message) = _Error; } diff --git a/lib/presentation/setting/bloc/update_product/update_product_bloc.dart b/lib/presentation/setting/bloc/update_product/update_product_bloc.dart index ace0614..6d3b625 100644 --- a/lib/presentation/setting/bloc/update_product/update_product_bloc.dart +++ b/lib/presentation/setting/bloc/update_product/update_product_bloc.dart @@ -1,12 +1,9 @@ import 'dart:developer'; import 'package:bloc/bloc.dart'; -import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; import 'package:enaklo_pos/data/datasources/product_remote_datasource.dart'; import 'package:enaklo_pos/data/models/request/product_request_model.dart'; -import 'package:enaklo_pos/data/models/response/product_response_model.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:image_picker/image_picker.dart'; part 'update_product_event.dart'; part 'update_product_state.dart'; @@ -19,62 +16,13 @@ class UpdateProductBloc extends Bloc { ) : super(const _Initial()) { on<_UpdateProduct>((event, emit) async { emit(const _Loading()); - + try { - // Validate required fields - if (event.product.name == null || event.product.name!.isEmpty) { - emit(_Error('Product name is required')); - return; - } - - if (event.product.price == null || event.product.price!.isEmpty) { - emit(_Error('Product price is required')); - return; - } - - if (event.product.stock == null) { - emit(_Error('Product stock is required')); - return; - } - - if (event.product.categoryId == null) { - emit(_Error('Product category is required')); - return; - } - - // Parse price safely - final price = int.tryParse(event.product.price!); - if (price == null) { - emit(_Error('Invalid price format')); - return; - } - - final requestData = ProductRequestModel( - id: event.product.id, - name: event.product.name!, - price: price, - stock: event.product.stock!, - categoryId: event.product.categoryId!, - isBestSeller: event.product.isFavorite ?? 0, // Default to 0 if null - image: event.image, - printerType: event.product.printerType ?? 'kitchen', // Default to kitchen if null - ); - - log("Update requestData: ${requestData.toString()}"); - log("Request map: ${requestData.toMap()}"); - - final response = await datasource.updateProduct(requestData); + final response = await datasource.updateProduct(event.product); response.fold( (l) => emit(_Error(l)), (r) async { - // Update local database after successful API update - try { - await ProductLocalDatasource.instance.updateProduct(event.product); - log("Local product updated successfully"); - } catch (e) { - log("Error updating local product: $e"); - } - emit(_Success('Update Product Success')); + emit(_Success('Product berhasil diupdate')); }, ); } catch (e) { @@ -83,4 +31,4 @@ class UpdateProductBloc extends Bloc { } }); } -} \ No newline at end of file +} diff --git a/lib/presentation/setting/bloc/update_product/update_product_bloc.freezed.dart b/lib/presentation/setting/bloc/update_product/update_product_bloc.freezed.dart index ad1c03a..5909ed1 100644 --- a/lib/presentation/setting/bloc/update_product/update_product_bloc.freezed.dart +++ b/lib/presentation/setting/bloc/update_product/update_product_bloc.freezed.dart @@ -16,21 +16,20 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$UpdateProductEvent { - Product get product => throw _privateConstructorUsedError; - XFile? get image => throw _privateConstructorUsedError; + ProductRequestModel get product => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ - required TResult Function(Product product, XFile? image) updateProduct, + required TResult Function(ProductRequestModel product) updateProduct, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(Product product, XFile? image)? updateProduct, + TResult? Function(ProductRequestModel product)? updateProduct, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function(Product product, XFile? image)? updateProduct, + TResult Function(ProductRequestModel product)? updateProduct, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -64,7 +63,7 @@ abstract class $UpdateProductEventCopyWith<$Res> { UpdateProductEvent value, $Res Function(UpdateProductEvent) then) = _$UpdateProductEventCopyWithImpl<$Res, UpdateProductEvent>; @useResult - $Res call({Product product, XFile? image}); + $Res call({ProductRequestModel product}); } /// @nodoc @@ -83,17 +82,12 @@ class _$UpdateProductEventCopyWithImpl<$Res, $Val extends UpdateProductEvent> @override $Res call({ Object? product = null, - Object? image = freezed, }) { return _then(_value.copyWith( product: null == product ? _value.product : product // ignore: cast_nullable_to_non_nullable - as Product, - image: freezed == image - ? _value.image - : image // ignore: cast_nullable_to_non_nullable - as XFile?, + as ProductRequestModel, ) as $Val); } } @@ -106,7 +100,7 @@ abstract class _$$UpdateProductImplCopyWith<$Res> __$$UpdateProductImplCopyWithImpl<$Res>; @override @useResult - $Res call({Product product, XFile? image}); + $Res call({ProductRequestModel product}); } /// @nodoc @@ -123,17 +117,12 @@ class __$$UpdateProductImplCopyWithImpl<$Res> @override $Res call({ Object? product = null, - Object? image = freezed, }) { return _then(_$UpdateProductImpl( null == product ? _value.product : product // ignore: cast_nullable_to_non_nullable - as Product, - freezed == image - ? _value.image - : image // ignore: cast_nullable_to_non_nullable - as XFile?, + as ProductRequestModel, )); } } @@ -141,16 +130,14 @@ class __$$UpdateProductImplCopyWithImpl<$Res> /// @nodoc class _$UpdateProductImpl implements _UpdateProduct { - const _$UpdateProductImpl(this.product, this.image); + const _$UpdateProductImpl(this.product); @override - final Product product; - @override - final XFile? image; + final ProductRequestModel product; @override String toString() { - return 'UpdateProductEvent.updateProduct(product: $product, image: $image)'; + return 'UpdateProductEvent.updateProduct(product: $product)'; } @override @@ -158,12 +145,11 @@ class _$UpdateProductImpl implements _UpdateProduct { return identical(this, other) || (other.runtimeType == runtimeType && other is _$UpdateProductImpl && - (identical(other.product, product) || other.product == product) && - (identical(other.image, image) || other.image == image)); + (identical(other.product, product) || other.product == product)); } @override - int get hashCode => Object.hash(runtimeType, product, image); + int get hashCode => Object.hash(runtimeType, product); /// Create a copy of UpdateProductEvent /// with the given fields replaced by the non-null parameter values. @@ -176,27 +162,27 @@ class _$UpdateProductImpl implements _UpdateProduct { @override @optionalTypeArgs TResult when({ - required TResult Function(Product product, XFile? image) updateProduct, + required TResult Function(ProductRequestModel product) updateProduct, }) { - return updateProduct(product, image); + return updateProduct(product); } @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(Product product, XFile? image)? updateProduct, + TResult? Function(ProductRequestModel product)? updateProduct, }) { - return updateProduct?.call(product, image); + return updateProduct?.call(product); } @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(Product product, XFile? image)? updateProduct, + TResult Function(ProductRequestModel product)? updateProduct, required TResult orElse(), }) { if (updateProduct != null) { - return updateProduct(product, image); + return updateProduct(product); } return orElse(); } @@ -231,13 +217,11 @@ class _$UpdateProductImpl implements _UpdateProduct { } abstract class _UpdateProduct implements UpdateProductEvent { - const factory _UpdateProduct(final Product product, final XFile? image) = + const factory _UpdateProduct(final ProductRequestModel product) = _$UpdateProductImpl; @override - Product get product; - @override - XFile? get image; + ProductRequestModel get product; /// Create a copy of UpdateProductEvent /// with the given fields replaced by the non-null parameter values. diff --git a/lib/presentation/setting/bloc/update_product/update_product_event.dart b/lib/presentation/setting/bloc/update_product/update_product_event.dart index 5fa93b6..a3356f1 100644 --- a/lib/presentation/setting/bloc/update_product/update_product_event.dart +++ b/lib/presentation/setting/bloc/update_product/update_product_event.dart @@ -2,5 +2,6 @@ part of 'update_product_bloc.dart'; @freezed class UpdateProductEvent with _$UpdateProductEvent { - const factory UpdateProductEvent.updateProduct(Product product, XFile? image) = _UpdateProduct; -} \ No newline at end of file + const factory UpdateProductEvent.updateProduct(ProductRequestModel product) = + _UpdateProduct; +} diff --git a/lib/presentation/setting/bloc/upload_file/upload_file_bloc.dart b/lib/presentation/setting/bloc/upload_file/upload_file_bloc.dart new file mode 100644 index 0000000..1652848 --- /dev/null +++ b/lib/presentation/setting/bloc/upload_file/upload_file_bloc.dart @@ -0,0 +1,29 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/file_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/response/file_response_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'upload_file_event.dart'; +part 'upload_file_state.dart'; +part 'upload_file_bloc.freezed.dart'; + +class UploadFileBloc extends Bloc { + final FileRemoteDataSource _fileRemoteDataSource; + UploadFileBloc(this._fileRemoteDataSource) + : super(UploadFileState.initial()) { + on<_Upload>((event, emit) async { + emit(_Loading()); + final result = await _fileRemoteDataSource.uploadFile( + filePath: event.filePath, + fileType: 'image', + description: 'Product Image', + ); + + result.fold((l) { + emit(_Error(l)); + }, (r) { + emit(_Success(r.data)); + }); + }); + } +} diff --git a/lib/presentation/setting/bloc/upload_file/upload_file_bloc.freezed.dart b/lib/presentation/setting/bloc/upload_file/upload_file_bloc.freezed.dart new file mode 100644 index 0000000..feec33b --- /dev/null +++ b/lib/presentation/setting/bloc/upload_file/upload_file_bloc.freezed.dart @@ -0,0 +1,845 @@ +// 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 'upload_file_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$UploadFileEvent { + String get filePath => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(String filePath) upload, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String filePath)? upload, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String filePath)? upload, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Upload value) upload, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Upload value)? upload, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Upload value)? upload, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Create a copy of UploadFileEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $UploadFileEventCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UploadFileEventCopyWith<$Res> { + factory $UploadFileEventCopyWith( + UploadFileEvent value, $Res Function(UploadFileEvent) then) = + _$UploadFileEventCopyWithImpl<$Res, UploadFileEvent>; + @useResult + $Res call({String filePath}); +} + +/// @nodoc +class _$UploadFileEventCopyWithImpl<$Res, $Val extends UploadFileEvent> + implements $UploadFileEventCopyWith<$Res> { + _$UploadFileEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of UploadFileEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? filePath = null, + }) { + return _then(_value.copyWith( + filePath: null == filePath + ? _value.filePath + : filePath // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$UploadImplCopyWith<$Res> + implements $UploadFileEventCopyWith<$Res> { + factory _$$UploadImplCopyWith( + _$UploadImpl value, $Res Function(_$UploadImpl) then) = + __$$UploadImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String filePath}); +} + +/// @nodoc +class __$$UploadImplCopyWithImpl<$Res> + extends _$UploadFileEventCopyWithImpl<$Res, _$UploadImpl> + implements _$$UploadImplCopyWith<$Res> { + __$$UploadImplCopyWithImpl( + _$UploadImpl _value, $Res Function(_$UploadImpl) _then) + : super(_value, _then); + + /// Create a copy of UploadFileEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? filePath = null, + }) { + return _then(_$UploadImpl( + null == filePath + ? _value.filePath + : filePath // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$UploadImpl implements _Upload { + const _$UploadImpl(this.filePath); + + @override + final String filePath; + + @override + String toString() { + return 'UploadFileEvent.upload(filePath: $filePath)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UploadImpl && + (identical(other.filePath, filePath) || + other.filePath == filePath)); + } + + @override + int get hashCode => Object.hash(runtimeType, filePath); + + /// Create a copy of UploadFileEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$UploadImplCopyWith<_$UploadImpl> get copyWith => + __$$UploadImplCopyWithImpl<_$UploadImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String filePath) upload, + }) { + return upload(filePath); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String filePath)? upload, + }) { + return upload?.call(filePath); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String filePath)? upload, + required TResult orElse(), + }) { + if (upload != null) { + return upload(filePath); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Upload value) upload, + }) { + return upload(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Upload value)? upload, + }) { + return upload?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Upload value)? upload, + required TResult orElse(), + }) { + if (upload != null) { + return upload(this); + } + return orElse(); + } +} + +abstract class _Upload implements UploadFileEvent { + const factory _Upload(final String filePath) = _$UploadImpl; + + @override + String get filePath; + + /// Create a copy of UploadFileEvent + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$UploadImplCopyWith<_$UploadImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$UploadFileState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(FileModel file) success, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(FileModel file)? success, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(FileModel file)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UploadFileStateCopyWith<$Res> { + factory $UploadFileStateCopyWith( + UploadFileState value, $Res Function(UploadFileState) then) = + _$UploadFileStateCopyWithImpl<$Res, UploadFileState>; +} + +/// @nodoc +class _$UploadFileStateCopyWithImpl<$Res, $Val extends UploadFileState> + implements $UploadFileStateCopyWith<$Res> { + _$UploadFileStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of UploadFileState + /// 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 _$UploadFileStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of UploadFileState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'UploadFileState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(FileModel file) success, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(FileModel file)? success, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(FileModel file)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements UploadFileState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$UploadFileStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of UploadFileState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'UploadFileState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(FileModel file) success, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(FileModel file)? success, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(FileModel file)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class _Loading implements UploadFileState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$SuccessImplCopyWith<$Res> { + factory _$$SuccessImplCopyWith( + _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = + __$$SuccessImplCopyWithImpl<$Res>; + @useResult + $Res call({FileModel file}); +} + +/// @nodoc +class __$$SuccessImplCopyWithImpl<$Res> + extends _$UploadFileStateCopyWithImpl<$Res, _$SuccessImpl> + implements _$$SuccessImplCopyWith<$Res> { + __$$SuccessImplCopyWithImpl( + _$SuccessImpl _value, $Res Function(_$SuccessImpl) _then) + : super(_value, _then); + + /// Create a copy of UploadFileState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? file = null, + }) { + return _then(_$SuccessImpl( + null == file + ? _value.file + : file // ignore: cast_nullable_to_non_nullable + as FileModel, + )); + } +} + +/// @nodoc + +class _$SuccessImpl implements _Success { + const _$SuccessImpl(this.file); + + @override + final FileModel file; + + @override + String toString() { + return 'UploadFileState.success(file: $file)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SuccessImpl && + (identical(other.file, file) || other.file == file)); + } + + @override + int get hashCode => Object.hash(runtimeType, file); + + /// Create a copy of UploadFileState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SuccessImplCopyWith<_$SuccessImpl> get copyWith => + __$$SuccessImplCopyWithImpl<_$SuccessImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(FileModel file) success, + required TResult Function(String message) error, + }) { + return success(file); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(FileModel file)? success, + TResult? Function(String message)? error, + }) { + return success?.call(file); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(FileModel file)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(file); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return success(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return success?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(this); + } + return orElse(); + } +} + +abstract class _Success implements UploadFileState { + const factory _Success(final FileModel file) = _$SuccessImpl; + + FileModel get file; + + /// Create a copy of UploadFileState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SuccessImplCopyWith<_$SuccessImpl> 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 _$UploadFileStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of UploadFileState + /// 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 'UploadFileState.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 UploadFileState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(FileModel file) success, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(FileModel file)? success, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(FileModel file)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements UploadFileState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of UploadFileState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/setting/bloc/upload_file/upload_file_event.dart b/lib/presentation/setting/bloc/upload_file/upload_file_event.dart new file mode 100644 index 0000000..c22bd69 --- /dev/null +++ b/lib/presentation/setting/bloc/upload_file/upload_file_event.dart @@ -0,0 +1,6 @@ +part of 'upload_file_bloc.dart'; + +@freezed +class UploadFileEvent with _$UploadFileEvent { + const factory UploadFileEvent.upload(String filePath) = _Upload; +} diff --git a/lib/presentation/setting/bloc/upload_file/upload_file_state.dart b/lib/presentation/setting/bloc/upload_file/upload_file_state.dart new file mode 100644 index 0000000..0e8de3d --- /dev/null +++ b/lib/presentation/setting/bloc/upload_file/upload_file_state.dart @@ -0,0 +1,9 @@ +part of 'upload_file_bloc.dart'; + +@freezed +class UploadFileState with _$UploadFileState { + const factory UploadFileState.initial() = _Initial; + const factory UploadFileState.loading() = _Loading; + const factory UploadFileState.success(FileModel file) = _Success; + const factory UploadFileState.error(String message) = _Error; +} diff --git a/lib/presentation/setting/dialogs/detail_product_dialog.dart b/lib/presentation/setting/dialogs/detail_product_dialog.dart new file mode 100644 index 0000000..3a9ced6 --- /dev/null +++ b/lib/presentation/setting/dialogs/detail_product_dialog.dart @@ -0,0 +1,155 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/constants/variables.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/core/extensions/string_ext.dart'; +import 'package:enaklo_pos/data/models/response/product_response_model.dart'; +import 'package:flutter/material.dart'; + +class DetailProductDialog extends StatelessWidget { + final Product product; + const DetailProductDialog({super.key, required this.product}); + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: "Detail Produk", + maxWidth: context.deviceWidth * 0.5, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: CachedNetworkImage( + imageUrl: (product.imageUrl ?? "").contains('http') + ? product.imageUrl! + : '${Variables.baseUrl}/${product.imageUrl}', + fit: BoxFit.cover, + width: 120, + height: 120, + errorWidget: (context, url, error) => Container( + width: 120, + height: 120, + decoration: BoxDecoration( + color: AppColors.grey.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.image_outlined, + color: AppColors.grey, + size: 40, + ), + ), + ), + ), + const SpaceWidth(16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Align( + alignment: Alignment.centerRight, + child: _buildStatus(), + ), + const SpaceHeight(8), + Text( + product.name ?? "-", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + if (product.description != null && + product.description!.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Text( + product.description!, + style: const TextStyle( + fontSize: 14, + color: AppColors.grey, + ), + ), + ), + ], + ), + ), + ], + ), + const SpaceHeight(16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildItem( + "-", + "Kategori", + ), + // _buildItem( + // "${product.stock}", + // "Stok", + // valueColor: product.stock! < 50 + // ? AppColors.red + // : product.stock! < 100 + // ? Colors.yellow + // : AppColors.green, + // ), + _buildItem( + (product.price ?? 0).toString().currencyFormatRpV2, + "Harga", + valueColor: AppColors.primary, + ), + ], + ), + ], + ), + ), + ); + } + + Column _buildItem(String value, String label, + {Color valueColor = AppColors.black}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: const TextStyle( + fontSize: 12, + color: Colors.grey, + ), + ), + const SpaceHeight(4), + Text( + value, + style: TextStyle( + fontSize: 14, + color: valueColor, + fontWeight: FontWeight.w600, + ), + ), + ], + ); + } + + Container _buildStatus() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: product.isActive == true ? AppColors.green : AppColors.red, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + product.isActive == true ? 'Aktif' : 'Tidak Aktif', + style: const TextStyle( + color: Colors.white, fontSize: 12, fontWeight: FontWeight.w700), + ), + ); + } +} diff --git a/lib/presentation/setting/dialogs/form_discount_dialog.dart b/lib/presentation/setting/dialogs/form_discount_dialog.dart index 78fa5d8..53336cd 100644 --- a/lib/presentation/setting/dialogs/form_discount_dialog.dart +++ b/lib/presentation/setting/dialogs/form_discount_dialog.dart @@ -1,3 +1,4 @@ +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/core/components/custom_text_field.dart'; @@ -23,19 +24,10 @@ class _FormDiscountDialogState extends State { final discountController = TextEditingController(); @override Widget build(BuildContext context) { - return AlertDialog( - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - onPressed: () => context.pop(), - icon: const Icon(Icons.close), - ), - const Text('Tambah Diskon'), - const Spacer(), - ], - ), - content: SingleChildScrollView( + return CustomModalDialog( + title: widget.data == null ? 'Tambah Diskon' : 'Edit Diskon', + contentPadding: const EdgeInsets.all(16.0), + child: SingleChildScrollView( child: SizedBox( width: context.deviceWidth / 3, child: Column( diff --git a/lib/presentation/setting/dialogs/form_product_dialog.dart b/lib/presentation/setting/dialogs/form_product_dialog.dart index 2c4a3f3..797c39c 100644 --- a/lib/presentation/setting/dialogs/form_product_dialog.dart +++ b/lib/presentation/setting/dialogs/form_product_dialog.dart @@ -1,5 +1,8 @@ import 'dart:developer'; +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/data/models/request/product_request_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/core/components/custom_text_field.dart'; @@ -21,10 +24,7 @@ import '../../../core/components/spaces.dart'; class FormProductDialog extends StatefulWidget { final Product? product; - const FormProductDialog({ - super.key, - this.product, - }); + const FormProductDialog({super.key, this.product}); @override State createState() => _FormProductDialogState(); @@ -32,13 +32,17 @@ class FormProductDialog extends StatefulWidget { class _FormProductDialogState extends State { TextEditingController? nameController; + TextEditingController? descriptionController; + TextEditingController? skuController; + TextEditingController? barcodeController; TextEditingController? priceController; - TextEditingController? stockController; + TextEditingController? costController; XFile? imageFile; bool isBestSeller = false; int priceValue = 0; + int costValue = 0; CategoryModel? selectCategory; String? imageUrl; @@ -50,21 +54,27 @@ class _FormProductDialogState extends State { context.read().add(const GetCategoriesEvent.fetch()); nameController = TextEditingController(); priceController = TextEditingController(); - stockController = TextEditingController(); + descriptionController = TextEditingController(); + skuController = TextEditingController(); + barcodeController = TextEditingController(); + costController = TextEditingController(); // Check if we're in edit mode isEditMode = widget.product != null; - + if (isEditMode) { // Pre-fill the form with existing product data final product = widget.product!; nameController!.text = product.name ?? ''; - priceValue = int.tryParse(product.price ?? '0') ?? 0; + priceValue = product.price ?? 0; + costValue = product.cost ?? 0; priceController!.text = priceValue.currencyFormatRp; - stockController!.text = (product.stock ?? 0).toString(); - isBestSeller = product.isFavorite == 1; - printType = product.printerType ?? 'kitchen'; - imageUrl = product.image; + costController!.text = costValue.currencyFormatRp; + descriptionController!.text = product.description ?? ''; + isBestSeller = false; + printType = 'kitchen'; + imageUrl = product.imageUrl; + selectCategory?.id = product.categoryId.toString(); } super.initState(); @@ -75,335 +85,983 @@ class _FormProductDialogState extends State { super.dispose(); nameController!.dispose(); priceController!.dispose(); - stockController!.dispose(); + descriptionController!.dispose(); + skuController!.dispose(); + barcodeController!.dispose(); + costController!.dispose(); } @override Widget build(BuildContext context) { - return AlertDialog( - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - onPressed: () => context.pop(), - icon: const Icon(Icons.close), - ), - Text( - isEditMode ? 'Edit Product' : 'Add Product', - style: TextStyle(fontWeight: FontWeight.bold), - ), - const Spacer(), - ], - ), - content: SingleChildScrollView( - child: SizedBox( - width: context.deviceWidth / 3, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CustomTextField( - controller: nameController!, - label: 'Product Name', - keyboardType: TextInputType.text, - textInputAction: TextInputAction.next, - textCapitalization: TextCapitalization.words, - ), - const SpaceHeight(20.0), - CustomTextField( - controller: priceController!, - label: 'Price', - keyboardType: TextInputType.number, - textInputAction: TextInputAction.next, - onChanged: (value) { - priceValue = value.toIntegerFromText; - final int newValue = value.toIntegerFromText; - priceController!.text = newValue.currencyFormatRp; - priceController!.selection = TextSelection.fromPosition( - TextPosition(offset: priceController!.text.length)); - }, - ), - const SpaceHeight(20.0), - ImagePickerWidget( - label: 'Photo Product', - onChanged: (file) { - if (file == null) { - return; - } - imageFile = file; - }, - initialImageUrl: imageUrl, - ), - const SpaceHeight(20.0), - CustomTextField( - controller: stockController!, - label: 'Stock', - keyboardType: TextInputType.number, - ), - const SpaceHeight(20.0), - const Text( - "Category", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w700, + return CustomModalDialog( + title: widget.product == null ? "Tambah Produk" : "Ubah Produk", + subtitle: widget.product == null + ? "Silakan isi formulir untuk menambahkan produk baru" + : "Silakan edit formulir untuk memperbarui produk", + child: Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomTextField( + controller: nameController!, + label: 'Nama Produk', + keyboardType: TextInputType.text, + textInputAction: TextInputAction.next, + textCapitalization: TextCapitalization.words, ), - ), - const SpaceHeight(12.0), - BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, - success: (categories) { - // Set the selected category if in edit mode and not already set - if (isEditMode && selectCategory == null && widget.product?.category != null) { - try { - selectCategory = categories.firstWhere( - (cat) => cat.id == widget.product!.category!.id, - ); - } catch (e) { - // If no exact match found, leave selectCategory as null - // This will show the hint text instead - log("No matching category found for product category ID: ${widget.product!.category!.id}"); - } - } - - return DropdownButtonHideUnderline( - child: Container( + const SpaceHeight(20.0), + CustomTextField( + controller: descriptionController!, + label: 'Deskripsi', + keyboardType: TextInputType.text, + textInputAction: TextInputAction.next, + textCapitalization: TextCapitalization.words, + ), + if (widget.product == null) ...[ + const SpaceHeight(20.0), + CustomTextField( + controller: skuController!, + label: 'SKU', + keyboardType: TextInputType.text, + textInputAction: TextInputAction.next, + textCapitalization: TextCapitalization.words, + ), + const SpaceHeight(20.0), + CustomTextField( + controller: barcodeController!, + label: 'Barcode', + keyboardType: TextInputType.text, + textInputAction: TextInputAction.next, + textCapitalization: TextCapitalization.words, + ), + ], + + const SpaceHeight(20.0), + CustomTextField( + controller: priceController!, + label: 'Harga', + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + onChanged: (value) { + priceValue = value.toIntegerFromText; + final int newValue = value.toIntegerFromText; + priceController!.text = newValue.currencyFormatRp; + priceController!.selection = TextSelection.fromPosition( + TextPosition(offset: priceController!.text.length)); + }, + ), + const SpaceHeight(20.0), + CustomTextField( + controller: costController!, + label: 'Cost', + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + onChanged: (value) { + costValue = value.toIntegerFromText; + final int newValue = value.toIntegerFromText; + costController!.text = newValue.currencyFormatRp; + costController!.selection = TextSelection.fromPosition( + TextPosition(offset: costController!.text.length)); + }, + ), + const SpaceHeight(20.0), + ImagePickerWidget( + label: 'Foto Produk', + onChanged: (file) { + if (file == null) { + return; + } + imageFile = file; + }, + initialImageUrl: imageUrl, + onUploaded: (uploadedUrl) => + setState(() => imageUrl = uploadedUrl), + autoUpload: true, + ), + const SpaceHeight(20.0), + const SpaceHeight(20.0), + const Text( + "Kategori", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w700, + color: Colors.black87, + ), + ), + const SpaceHeight(12.0), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () { + return Container( + height: 56, decoration: BoxDecoration( - border: Border.all(color: Colors.grey), + border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(12), ), - padding: const EdgeInsets.symmetric( - horizontal: 10, vertical: 5), - child: DropdownButton( - value: selectCategory, - hint: const Text("Select Category"), - isExpanded: true, // Untuk mengisi lebar container - onChanged: (newValue) { - if (newValue != null) { - selectCategory = newValue; - setState(() {}); - log("selectCategory: ${selectCategory!.name}"); - } - }, - items: categories - .map>( - (CategoryModel category) { - return DropdownMenuItem( - value: category, - child: Text(category.name!), - ); - }).toList(), + child: const Center( + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.blue, + ), + ), ), - ), - ); - }, - ); - }, - ), - const SpaceHeight(12.0), - const Text( - "Print Type", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w700, - ), - ), - //radio printer type - const SpaceHeight(12.0), - Row( - children: [ - Radio( - value: 'kitchen', - groupValue: printType, - onChanged: (value) { - setState(() { - printType = value; - }); - }, - ), - const Text('Kitchen'), - ], - ), - Row( - children: [ - Radio( - value: 'bar', - groupValue: printType, - onChanged: (value) { - setState(() { - printType = value; - }); - }, - ), - const Text('Bar'), - ], - ), - const SpaceHeight(20.0), - Row( - children: [ - Checkbox( - value: isBestSeller, - onChanged: (value) { - setState(() { - isBestSeller = value!; - }); - }, - ), - const Text('Favorite Product'), - ], - ), - const SpaceHeight(20.0), - const SpaceHeight(24.0), - if (isEditMode) - BlocConsumer( - listener: (context, state) { - state.maybeMap( - orElse: () {}, - success: (_) { - context - .read() - .add(const SyncProductEvent.syncProduct()); - context - .read() - .add(const GetProductsEvent.fetch()); - context.pop(true); - - const snackBar = SnackBar( - content: Text('Success Update Product'), - backgroundColor: AppColors.primary, ); - ScaffoldMessenger.of(context).showSnackBar( - snackBar, + }, + success: (categories) { + if (isEditMode == true) { + selectCategory = categories.firstWhere( + (item) => item.id == widget.product!.categoryId, + orElse: () => CategoryModel( + id: '', + organizationId: '', + name: '', + businessType: '', + metadata: {}, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ), + ); + } + // Null safety check untuk categories + if (categories.isEmpty) { + return Container( + height: 56, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(12), + ), + child: const Center( + child: Text( + "Tidak ada kategori tersedia", + style: TextStyle( + color: Colors.grey, + fontSize: 14, + ), + ), + ), + ); + } + + return DropdownSearch( + items: categories, + selectedItem: selectCategory, + + // Dropdown properties + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + hintText: "Pilih Kategori", + hintStyle: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + ), + prefixIcon: Icon( + Icons.category_outlined, + color: Colors.grey.shade500, + size: 20, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, + width: 1.5, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, + width: 1.5, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.blue.shade400, + width: 2, + ), + ), + filled: true, + fillColor: Colors.white, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + ), + ), + + // Popup properties + popupProps: PopupProps.menu( + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Cari kategori...", + prefixIcon: const Icon(Icons.search), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + ), + menuProps: MenuProps( + backgroundColor: Colors.white, + elevation: 8, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + itemBuilder: (context, category, isSelected) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + decoration: BoxDecoration( + color: isSelected + ? Colors.blue.shade50 + : Colors.transparent, + border: Border( + bottom: BorderSide( + color: Colors.grey.shade100, + width: 0.5, + ), + ), + ), + child: Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: isSelected + ? Colors.blue.shade600 + : Colors.grey.shade400, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + category.name, + style: TextStyle( + fontSize: 14, + fontWeight: isSelected + ? FontWeight.w600 + : FontWeight.w500, + color: isSelected + ? Colors.blue.shade700 + : Colors.black87, + ), + ), + ), + if (isSelected) + Icon( + Icons.check, + color: Colors.blue.shade600, + size: 18, + ), + ], + ), + ); + }, + emptyBuilder: (context, searchEntry) { + return Container( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.search_off, + color: Colors.grey.shade400, + size: 48, + ), + const SizedBox(height: 12), + Text( + searchEntry.isEmpty + ? "Tidak ada kategori tersedia" + : "Tidak ditemukan kategori dengan '${searchEntry}'", + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + ], + ), + ); + }, + ), + + // Item as string (for search functionality) + itemAsString: (CategoryModel category) => + category.name, + + // Comparison function + compareFn: + (CategoryModel? item1, CategoryModel? item2) { + return item1?.id == item2?.id; + }, + + // On changed callback + onChanged: (CategoryModel? selectedCategory) { + if (selectedCategory != null) { + setState(() { + selectCategory = selectedCategory; + }); + log("selectCategory: ${selectCategory!.name}"); + } + }, + + // Validator (optional) + validator: (CategoryModel? value) { + if (value == null) { + return "Kategori harus dipilih"; + } + return null; + }, ); }, error: (message) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Error: $message'), - backgroundColor: Colors.red, + return Container( + height: 56, + decoration: BoxDecoration( + border: Border.all(color: Colors.red.shade300), + borderRadius: BorderRadius.circular(12), + ), + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.error_outline, + color: Colors.red.shade400, + size: 20, + ), + const SizedBox(width: 8), + Text( + "Gagal memuat kategori", + style: TextStyle( + color: Colors.red.shade600, + fontSize: 14, + ), + ), + ], + ), ), ); }, ); }, - builder: (context, state) { - return state.maybeWhen( - orElse: () { - return Button.filled( - onPressed: () { - if (selectCategory == null) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Please select a category'), - backgroundColor: Colors.red, - ), - ); - return; - } - - log("isBestSeller: $isBestSeller"); - final String name = nameController!.text; - final int stock = stockController!.text.toIntegerFromText; - - final Product product = widget.product!.copyWith( - name: name, - price: priceValue.toString(), - stock: stock, - categoryId: selectCategory!.id!, - isFavorite: isBestSeller ? 1 : 0, - printerType: printType, - ); - - context.read().add( - UpdateProductEvent.updateProduct(product, imageFile)); - }, - label: 'Update Product', - ); - }, - loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, - ); - }, - ) - else - BlocConsumer( - listener: (context, state) { - state.maybeMap( - orElse: () {}, - success: (_) { - context - .read() - .add(const SyncProductEvent.syncProduct()); - context - .read() - .add(const GetProductsEvent.fetch()); - context.pop(true); - - const snackBar = SnackBar( - content: Text('Success Add Product'), - backgroundColor: AppColors.primary, - ); - ScaffoldMessenger.of(context).showSnackBar( - snackBar, - ); - }, - ); - }, - builder: (context, state) { - return state.maybeWhen( - orElse: () { - return Button.filled( - onPressed: () { - if (selectCategory == null) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Please select a category'), - backgroundColor: Colors.red, - ), - ); - return; - } - - log("isBestSeller: $isBestSeller"); - final String name = nameController!.text; - - final int stock = - stockController!.text.toIntegerFromText; - final Product product = Product( - name: name, - price: priceValue.toString(), - stock: stock, - categoryId: selectCategory!.id!, - isFavorite: isBestSeller ? 1 : 0, - image: imageFile!.path, - printerType: printType, - ); - context.read().add( - AddProductEvent.addProduct(product, imageFile!)); - }, - label: 'Save Product', - ); - }, - loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, - ); - }, ), - const SpaceHeight(16.0), - ], + const SpaceHeight(12.0), + const Text( + "Tipe Print", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w700, + ), + ), + //radio printer type + const SpaceHeight(12.0), + Row( + children: [ + Radio( + value: 'kitchen', + groupValue: printType, + onChanged: (value) { + setState(() { + printType = value; + }); + }, + ), + const Text('Kitchen'), + ], + ), + Row( + children: [ + Radio( + value: 'bar', + groupValue: printType, + onChanged: (value) { + setState(() { + printType = value; + }); + }, + ), + const Text('Bar'), + ], + ), + const SpaceHeight(20.0), + Row( + children: [ + Checkbox( + value: isBestSeller, + onChanged: (value) { + setState(() { + isBestSeller = value!; + }); + }, + ), + const Text('Produk Favorit'), + ], + ), + const SpaceHeight(20.0), + const SpaceHeight(24.0), + if (isEditMode) + BlocConsumer( + listener: (context, state) { + state.maybeMap( + orElse: () {}, + success: (_) { + context + .read() + .add(const SyncProductEvent.syncProduct()); + context + .read() + .add(const GetProductsEvent.fetch()); + context.pop(true); + + const snackBar = SnackBar( + content: Text('Success Update Product'), + backgroundColor: AppColors.primary, + ); + ScaffoldMessenger.of(context).showSnackBar( + snackBar, + ); + }, + error: (message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error: $message'), + backgroundColor: Colors.red, + ), + ); + }, + ); + }, + builder: (context, state) { + return state.maybeWhen( + orElse: () { + return Button.filled( + onPressed: () { + if (selectCategory == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Please select a category'), + backgroundColor: Colors.red, + ), + ); + return; + } + + log("isBestSeller: $isBestSeller"); + final String name = nameController!.text; + + final ProductRequestModel product = + ProductRequestModel( + id: widget.product!.id, + name: name, + description: descriptionController!.text, + price: priceValue, + cost: costValue, + categoryId: selectCategory!.id, + printerType: printType, + imageUrl: imageUrl.toString(), + ); + context.read().add( + UpdateProductEvent.updateProduct(product)); + }, + label: 'Ubah Produk', + ); + }, + loading: () { + return const Center( + child: CircularProgressIndicator(), + ); + }, + ); + }, + ) + else + BlocConsumer( + listener: (context, state) { + state.maybeMap( + orElse: () {}, + success: (_) { + context + .read() + .add(const SyncProductEvent.syncProduct()); + context + .read() + .add(const GetProductsEvent.fetch()); + context.pop(true); + + const snackBar = SnackBar( + content: Text('Success Add Product'), + backgroundColor: AppColors.primary, + ); + ScaffoldMessenger.of(context).showSnackBar( + snackBar, + ); + }, + ); + }, + builder: (context, state) { + return state.maybeWhen( + orElse: () { + return Button.filled( + onPressed: () { + if (selectCategory == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Please select a category'), + backgroundColor: Colors.red, + ), + ); + return; + } + + log("isBestSeller: $isBestSeller"); + final String name = nameController!.text; + + final ProductRequestModel product = + ProductRequestModel( + name: name, + price: priceValue, + printerType: printType, + categoryId: selectCategory!.id, + cost: costValue, + imageUrl: imageUrl.toString(), + barcode: barcodeController!.text, + sku: skuController!.text, + description: descriptionController!.text); + context + .read() + .add(AddProductEvent.addProduct( + product, + )); + }, + label: 'Simpan Produk', + ); + }, + loading: () { + return const Center( + child: CircularProgressIndicator(), + ); + }, + ); + }, + ), + const SpaceHeight(16.0), + ], + ), ), ), ), ); } } + +// class FormProductDialogOld extends StatefulWidget { +// final Product? product; +// const FormProductDialogOld({ +// super.key, +// this.product, +// }); + +// @override +// State createState() => _FormProductDialogOldState(); +// } + +// class _FormProductDialogOldState extends State { +// TextEditingController? nameController; +// TextEditingController? priceController; +// TextEditingController? stockController; + +// XFile? imageFile; + +// bool isBestSeller = false; +// int priceValue = 0; +// int costValue = 0; + +// CategoryModel? selectCategory; +// String? imageUrl; +// String? printType = 'kitchen'; +// bool isEditMode = false; + +// @override +// void initState() { +// context.read().add(const GetCategoriesEvent.fetch()); +// nameController = TextEditingController(); +// priceController = TextEditingController(); +// stockController = TextEditingController(); + +// // Check if we're in edit mode +// isEditMode = widget.product != null; + +// if (isEditMode) { +// // Pre-fill the form with existing product data +// // final product = widget.product!; +// // nameController!.text = product.name ?? ''; +// // priceValue = int.tryParse(product.price ?? '0') ?? 0; +// // priceController!.text = priceValue.currencyFormatRp; +// // stockController!.text = (product.stock ?? 0).toString(); +// // isBestSeller = product.isFavorite == 1; +// // printType = product.printerType ?? 'kitchen'; +// // imageUrl = product.image; +// } + +// super.initState(); +// } + +// @override +// void dispose() { +// super.dispose(); +// nameController!.dispose(); +// priceController!.dispose(); +// stockController!.dispose(); +// } + +// @override +// Widget build(BuildContext context) { +// return AlertDialog( +// title: Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// IconButton( +// onPressed: () => context.pop(), +// icon: const Icon(Icons.close), +// ), +// Text( +// isEditMode ? 'Edit Product' : 'Add Product', +// style: TextStyle(fontWeight: FontWeight.bold), +// ), +// const Spacer(), +// ], +// ), +// content: SingleChildScrollView( +// child: SizedBox( +// width: context.deviceWidth / 3, +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// CustomTextField( +// controller: nameController!, +// label: 'Product Name', +// keyboardType: TextInputType.text, +// textInputAction: TextInputAction.next, +// textCapitalization: TextCapitalization.words, +// ), +// const SpaceHeight(20.0), +// CustomTextField( +// controller: priceController!, +// label: 'Price', +// keyboardType: TextInputType.number, +// textInputAction: TextInputAction.next, +// onChanged: (value) { +// priceValue = value.toIntegerFromText; +// final int newValue = value.toIntegerFromText; +// priceController!.text = newValue.currencyFormatRp; +// priceController!.selection = TextSelection.fromPosition( +// TextPosition(offset: priceController!.text.length)); +// }, +// ), +// const SpaceHeight(20.0), +// ImagePickerWidget( +// label: 'Photo Product', +// onChanged: (file) { +// if (file == null) { +// return; +// } +// imageFile = file; +// }, +// initialImageUrl: imageUrl, +// ), +// const SpaceHeight(20.0), +// CustomTextField( +// controller: stockController!, +// label: 'Stock', +// keyboardType: TextInputType.number, +// ), +// const SpaceHeight(20.0), +// const Text( +// "Category", +// style: TextStyle( +// fontSize: 14, +// fontWeight: FontWeight.w700, +// ), +// ), +// const SpaceHeight(12.0), +// BlocBuilder( +// builder: (context, state) { +// return state.maybeWhen( +// orElse: () { +// return const Center( +// child: CircularProgressIndicator(), +// ); +// }, +// success: (categories) { +// // Set the selected category if in edit mode and not already set +// // if (isEditMode && +// // selectCategory == null && +// // widget.product?.category != null) { +// // try { +// // selectCategory = categories.firstWhere( +// // (cat) => cat.id == widget.product!.category!.id, +// // ); +// // } catch (e) { +// // // If no exact match found, leave selectCategory as null +// // // This will show the hint text instead +// // log("No matching category found for product category ID: ${widget.product!.category!.id}"); +// // } +// // } + +// return DropdownButtonHideUnderline( +// child: Container( +// decoration: BoxDecoration( +// border: Border.all(color: Colors.grey), +// borderRadius: BorderRadius.circular(12), +// ), +// padding: const EdgeInsets.symmetric( +// horizontal: 10, vertical: 5), +// child: DropdownButton( +// value: selectCategory, +// hint: const Text("Select Category"), +// isExpanded: true, // Untuk mengisi lebar container +// onChanged: (newValue) { +// if (newValue != null) { +// selectCategory = newValue; +// setState(() {}); +// log("selectCategory: ${selectCategory!.name}"); +// } +// }, +// items: categories +// .map>( +// (CategoryModel category) { +// return DropdownMenuItem( +// value: category, +// child: Text(category.name!), +// ); +// }).toList(), +// ), +// ), +// ); +// }, +// ); +// }, +// ), +// const SpaceHeight(12.0), +// const Text( +// "Print Type", +// style: TextStyle( +// fontSize: 14, +// fontWeight: FontWeight.w700, +// ), +// ), +// //radio printer type +// const SpaceHeight(12.0), +// Row( +// children: [ +// Radio( +// value: 'kitchen', +// groupValue: printType, +// onChanged: (value) { +// setState(() { +// printType = value; +// }); +// }, +// ), +// const Text('Kitchen'), +// ], +// ), +// Row( +// children: [ +// Radio( +// value: 'bar', +// groupValue: printType, +// onChanged: (value) { +// setState(() { +// printType = value; +// }); +// }, +// ), +// const Text('Bar'), +// ], +// ), +// const SpaceHeight(20.0), +// Row( +// children: [ +// Checkbox( +// value: isBestSeller, +// onChanged: (value) { +// setState(() { +// isBestSeller = value!; +// }); +// }, +// ), +// const Text('Favorite Product'), +// ], +// ), +// const SpaceHeight(20.0), +// const SpaceHeight(24.0), +// if (isEditMode) +// BlocConsumer( +// listener: (context, state) { +// state.maybeMap( +// orElse: () {}, +// success: (_) { +// context +// .read() +// .add(const SyncProductEvent.syncProduct()); +// context +// .read() +// .add(const GetProductsEvent.fetch()); +// context.pop(true); + +// const snackBar = SnackBar( +// content: Text('Success Update Product'), +// backgroundColor: AppColors.primary, +// ); +// ScaffoldMessenger.of(context).showSnackBar( +// snackBar, +// ); +// }, +// error: (message) { +// ScaffoldMessenger.of(context).showSnackBar( +// SnackBar( +// content: Text('Error: $message'), +// backgroundColor: Colors.red, +// ), +// ); +// }, +// ); +// }, +// builder: (context, state) { +// return state.maybeWhen( +// orElse: () { +// return Button.filled( +// onPressed: () { +// if (selectCategory == null) { +// ScaffoldMessenger.of(context).showSnackBar( +// const SnackBar( +// content: Text('Please select a category'), +// backgroundColor: Colors.red, +// ), +// ); +// return; +// } + +// log("isBestSeller: $isBestSeller"); +// final String name = nameController!.text; +// final int stock = +// stockController!.text.toIntegerFromText; + +// // final Product product = widget.product!.copyWith( +// // name: name, +// // price: priceValue.toString(), +// // stock: stock, +// // categoryId: selectCategory!.id!, +// // isFavorite: isBestSeller ? 1 : 0, +// // printerType: printType, +// // ); + +// // context.read().add( +// // UpdateProductEvent.updateProduct( +// // product, imageFile)); +// }, +// label: 'Update Product', +// ); +// }, +// loading: () { +// return const Center( +// child: CircularProgressIndicator(), +// ); +// }, +// ); +// }, +// ) +// else +// BlocConsumer( +// listener: (context, state) { +// state.maybeMap( +// orElse: () {}, +// success: (_) { +// context +// .read() +// .add(const SyncProductEvent.syncProduct()); +// context +// .read() +// .add(const GetProductsEvent.fetch()); +// context.pop(true); + +// const snackBar = SnackBar( +// content: Text('Success Add Product'), +// backgroundColor: AppColors.primary, +// ); +// ScaffoldMessenger.of(context).showSnackBar( +// snackBar, +// ); +// }, +// ); +// }, +// builder: (context, state) { +// return state.maybeWhen( +// orElse: () { +// return Button.filled( +// onPressed: () { +// if (selectCategory == null) { +// ScaffoldMessenger.of(context).showSnackBar( +// const SnackBar( +// content: Text('Please select a category'), +// backgroundColor: Colors.red, +// ), +// ); +// return; +// } + +// log("isBestSeller: $isBestSeller"); +// final String name = nameController!.text; + +// final int stock = +// stockController!.text.toIntegerFromText; +// // final Product product = Product( +// // name: name, +// // price: priceValue.toString(), +// // stock: stock, +// // categoryId: selectCategory!.id!, +// // isFavorite: isBestSeller ? 1 : 0, +// // image: imageFile!.path, +// // printerType: printType, +// // ); +// // context.read().add( +// // AddProductEvent.addProduct( +// // product, imageFile!)); +// }, +// label: 'Save Product', +// ); +// }, +// loading: () { +// return const Center( +// child: CircularProgressIndicator(), +// ); +// }, +// ); +// }, +// ), +// const SpaceHeight(16.0), +// ], +// ), +// ), +// ), +// ); +// } +// } diff --git a/lib/presentation/setting/dialogs/form_tax_dialog.dart b/lib/presentation/setting/dialogs/form_tax_dialog.dart index 4297648..2a93684 100644 --- a/lib/presentation/setting/dialogs/form_tax_dialog.dart +++ b/lib/presentation/setting/dialogs/form_tax_dialog.dart @@ -1,3 +1,4 @@ +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; import 'package:flutter/material.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; @@ -28,7 +29,8 @@ class _FormTaxDialogState extends State { @override void initState() { super.initState(); - serviceFeeController = TextEditingController(text: widget.serviceChargeValue.toString()); + serviceFeeController = + TextEditingController(text: widget.serviceChargeValue.toString()); taxFeeController = TextEditingController(text: widget.taxValue.toString()); } @@ -41,19 +43,10 @@ class _FormTaxDialogState extends State { @override Widget build(BuildContext context) { - return AlertDialog( - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - onPressed: () => context.pop(), - icon: const Icon(Icons.close), - ), - const Text('Edit Perhitungan Biaya'), - const Spacer(), - ], - ), - content: SingleChildScrollView( + return CustomModalDialog( + title: 'Edit Perhitungan Biaya', + contentPadding: const EdgeInsets.all(16.0), + child: SingleChildScrollView( child: SizedBox( width: context.deviceWidth / 3, child: Column( @@ -78,12 +71,13 @@ class _FormTaxDialogState extends State { Button.filled( onPressed: () { final taxValue = int.tryParse(taxFeeController.text) ?? 0; - final serviceChargeValue = int.tryParse(serviceFeeController.text) ?? 0; - + final serviceChargeValue = + int.tryParse(serviceFeeController.text) ?? 0; + if (widget.onSave != null) { widget.onSave!(taxValue, serviceChargeValue); } - + context.pop(); }, label: 'Simpan', diff --git a/lib/presentation/setting/pages/discount_page.dart b/lib/presentation/setting/pages/discount_page.dart index 09086a5..c16ceb6 100644 --- a/lib/presentation/setting/pages/discount_page.dart +++ b/lib/presentation/setting/pages/discount_page.dart @@ -1,3 +1,5 @@ +import 'package:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/presentation/setting/bloc/discount/discount_bloc.dart'; @@ -5,7 +7,6 @@ import 'package:enaklo_pos/presentation/setting/bloc/discount/discount_bloc.dart import '../../home/widgets/custom_tab_bar.dart'; import '../dialogs/form_discount_dialog.dart'; import '../models/discount_model.dart'; -import '../widgets/add_data.dart'; import '../widgets/manage_discount_card.dart'; import '../widgets/settings_title.dart'; @@ -49,83 +50,94 @@ class _DiscountPageState extends State { @override Widget build(BuildContext context) { - return SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const SettingsTitle('Kelola Diskon'), - const SizedBox(height: 24), - CustomTabBar( - tabTitles: const ['Semua'], - initialTabIndex: 0, - tabViews: [ - // SEMUA TAB - SizedBox( - child: BlocBuilder( - builder: (context, state) { - return state.maybeWhen(orElse: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loaded: (discounts) { - return GridView.builder( - shrinkWrap: true, - itemCount: discounts.length + 1, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - childAspectRatio: 0.85, - crossAxisCount: 3, - crossAxisSpacing: 30.0, - mainAxisSpacing: 30.0, - ), - itemBuilder: (context, index) { - if (index == 0) { - return AddData( - title: 'Tambah Diskon Baru', - onPressed: onAddDataTap, + return Column( + children: [ + SettingsTitle( + 'Kelola Diskon', + subtitle: 'Kelola diskon untuk produk Anda', + actionWidget: [ + Button.outlined( + onPressed: onAddDataTap, + label: "Tambah Diskon", + icon: Icon(Icons.add, color: AppColors.primary), + ) + ], + ), + Expanded( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + CustomTabBar( + tabTitles: const ['Semua'], + initialTabIndex: 0, + tabViews: [ + // SEMUA TAB + SizedBox( + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen(orElse: () { + return const Center( + child: CircularProgressIndicator(), ); - } - final item = discounts[index - 1]; - return ManageDiscountCard( - data: item, - onEditTap: (){}, - ); + }, loaded: (discounts) { + return GridView.builder( + shrinkWrap: true, + itemCount: discounts.length, + physics: const NeverScrollableScrollPhysics(), + padding: + const EdgeInsets.symmetric(horizontal: 16.0), + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + childAspectRatio: 0.85, + crossAxisCount: 3, + crossAxisSpacing: 30.0, + mainAxisSpacing: 30.0, + ), + itemBuilder: (context, index) { + final item = discounts[index]; + return ManageDiscountCard( + data: item, + onEditTap: () {}, + ); + }, + ); + }); + // return GridView.builder( + // shrinkWrap: true, + // itemCount: discounts.length + 1, + // physics: const NeverScrollableScrollPhysics(), + // gridDelegate: + // const SliverGridDelegateWithFixedCrossAxisCount( + // childAspectRatio: 0.85, + // crossAxisCount: 3, + // crossAxisSpacing: 30.0, + // mainAxisSpacing: 30.0, + // ), + // itemBuilder: (context, index) { + // if (index == 0) { + // return AddData( + // title: 'Tambah Diskon Baru', + // onPressed: onAddDataTap, + // ); + // } + // final item = discounts[index - 1]; + // return ManageDiscountCard( + // data: item, + // onEditTap: () => onEditTap(item), + // ); + // }, + // ); }, - ); - }); - // return GridView.builder( - // shrinkWrap: true, - // itemCount: discounts.length + 1, - // physics: const NeverScrollableScrollPhysics(), - // gridDelegate: - // const SliverGridDelegateWithFixedCrossAxisCount( - // childAspectRatio: 0.85, - // crossAxisCount: 3, - // crossAxisSpacing: 30.0, - // mainAxisSpacing: 30.0, - // ), - // itemBuilder: (context, index) { - // if (index == 0) { - // return AddData( - // title: 'Tambah Diskon Baru', - // onPressed: onAddDataTap, - // ); - // } - // final item = discounts[index - 1]; - // return ManageDiscountCard( - // data: item, - // onEditTap: () => onEditTap(item), - // ); - // }, - // ); - }, + ), + ), + ], ), - ), - ], + ], + ), ), - ], - ), + ), + ], ); } } diff --git a/lib/presentation/setting/pages/manage_printer_page.dart b/lib/presentation/setting/pages/manage_printer_page.dart index 3172b29..a8f936a 100644 --- a/lib/presentation/setting/pages/manage_printer_page.dart +++ b/lib/presentation/setting/pages/manage_printer_page.dart @@ -171,8 +171,7 @@ class _ManagePrinterPageState extends State { //bytes += generator.setGlobalFont(PosFontType.fontA); bytes += generator.reset(); - bytes += - generator.text('Enaklo POS', styles: const PosStyles(bold: true)); + bytes += generator.text('Apskel POS', styles: const PosStyles(bold: true)); bytes += generator.text('Reverse text', styles: const PosStyles(reverse: true)); bytes += generator.text('Underlined text', diff --git a/lib/presentation/setting/pages/printer_page.dart b/lib/presentation/setting/pages/printer_page.dart new file mode 100644 index 0000000..9dd7272 --- /dev/null +++ b/lib/presentation/setting/pages/printer_page.dart @@ -0,0 +1,42 @@ +import 'package:enaklo_pos/presentation/home/widgets/custom_tab_bar.dart'; +import 'package:enaklo_pos/presentation/setting/widgets/bar_printer_page.dart'; +import 'package:enaklo_pos/presentation/setting/widgets/checker_printer_page.dart'; +import 'package:enaklo_pos/presentation/setting/widgets/kitchen_printer_page.dart'; +import 'package:enaklo_pos/presentation/setting/widgets/receipt_printer_page.dart'; +import 'package:enaklo_pos/presentation/setting/widgets/settings_title.dart'; +import 'package:enaklo_pos/presentation/setting/widgets/ticket_printer_page.dart'; +import 'package:flutter/material.dart'; + +class SettingPrinterPage extends StatelessWidget { + const SettingPrinterPage({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + SettingsTitle( + 'Printer', + subtitle: 'Kelola printer untuk cetak struk', + ), + Expanded( + child: CustomTabBarV2( + tabTitles: [ + 'Receipt Printer', + 'Checker Printer', + 'Kitchen Printer', + 'Bar Printer', + 'Tiket Printer', + ], + tabViews: [ + ReceiptPrinterPage(), + CheckerPrinterPage(), + KitchenPrinterPage(), + BarPrinterPage(), + TicketPrinterPage() + ], + ), + ), + ], + ); + } +} diff --git a/lib/presentation/setting/pages/product_page.dart b/lib/presentation/setting/pages/product_page.dart index 50bcc50..14d66d0 100644 --- a/lib/presentation/setting/pages/product_page.dart +++ b/lib/presentation/setting/pages/product_page.dart @@ -1,3 +1,5 @@ +import 'package:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/data/datasources/product_remote_datasource.dart'; @@ -6,7 +8,6 @@ import 'package:enaklo_pos/presentation/setting/bloc/update_product/update_produ import 'package:enaklo_pos/presentation/setting/bloc/sync_product/sync_product_bloc.dart'; import 'package:enaklo_pos/presentation/setting/bloc/add_product/add_product_bloc.dart'; import 'package:enaklo_pos/presentation/setting/dialogs/form_product_dialog.dart'; -import 'package:enaklo_pos/presentation/setting/widgets/add_data.dart'; import 'package:enaklo_pos/presentation/setting/widgets/menu_product_item.dart'; import 'package:enaklo_pos/presentation/setting/widgets/settings_title.dart'; @@ -18,6 +19,7 @@ class ProductPage extends StatefulWidget { } class _ProductPageState extends State { + ScrollController scrollController = ScrollController(); @override void initState() { context.read().add(const GetProductsEvent.fetch()); @@ -27,81 +29,104 @@ class _ProductPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: ListView( - padding: const EdgeInsets.all(24.0), + backgroundColor: AppColors.background, + body: Column( children: [ - const SettingsTitle('Manage Products'), - const SizedBox(height: 24), - BlocBuilder( - builder: (context, state) { - return state.maybeWhen(orElse: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, success: (products) { - return GridView.builder( - padding: EdgeInsets.zero, - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - childAspectRatio: 1, - crossAxisCount: 3, - mainAxisSpacing: 12, - crossAxisSpacing: 12, - ), - itemCount: products.length + 1, - shrinkWrap: true, - physics: const ScrollPhysics(), - itemBuilder: (BuildContext context, int index) { - if (index == 0) { - return AddData( - title: 'Add New Product', - onPressed: () { - showDialog( - context: context, - builder: (context) => MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => AddProductBloc(ProductRemoteDatasource()), - ), - BlocProvider.value( - value: context.read(), - ), - BlocProvider.value( - value: context.read(), - ), - ], - child: const FormProductDialog(), - ), - ); - }, - ); - } - final item = products[index - 1]; - return MenuProductItem( - data: item, - onTapEdit: () { - showDialog( - context: context, - builder: (context) => MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => UpdateProductBloc(ProductRemoteDatasource()), + SettingsTitle( + 'Kelola Produk', + subtitle: 'Kelola produk anda', + actionWidget: [ + Button.outlined( + onPressed: () { + showDialog( + context: context, + builder: (context) => MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => + AddProductBloc(ProductRemoteDatasource()), + ), + BlocProvider.value( + value: context.read(), + ), + BlocProvider.value( + value: context.read(), + ), + ], + child: const FormProductDialog(), + ), + ); + }, + label: "Tambah Produk", + icon: Icon(Icons.add, color: AppColors.primary), + ) + ], + ), + Expanded( + child: BlocBuilder( + builder: (context, state) { + return NotificationListener( + onNotification: (notification) { + return state.maybeWhen( + orElse: () => false, + success: (products, hasReachedMax, currentPage, _) { + if (notification is ScrollEndNotification && + scrollController.position.extentAfter == 0) { + context + .read() + .add(const GetProductsEvent.loadMore()); + return true; + } + + return true; + }); + }, + child: state.maybeWhen(orElse: () { + return const Center( + child: CircularProgressIndicator(), + ); + }, success: (products, hasReachedMax, currentPage, _) { + return GridView.builder( + padding: EdgeInsets.all(16), + controller: scrollController, + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 200, + mainAxisSpacing: 30, + crossAxisSpacing: 30, + childAspectRatio: 0.85, + ), + itemCount: products.length, + itemBuilder: (BuildContext context, int index) { + final item = products[index]; + return MenuProductItem( + data: item, + onTapEdit: () { + showDialog( + context: context, + builder: (context) => MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => UpdateProductBloc( + ProductRemoteDatasource()), + ), + BlocProvider.value( + value: context.read(), + ), + BlocProvider.value( + value: context.read(), + ), + ], + child: FormProductDialog(product: item), ), - BlocProvider.value( - value: context.read(), - ), - BlocProvider.value( - value: context.read(), - ), - ], - child: FormProductDialog(product: item), - ), + ); + }, ); }, ); - }, + }), ); - }); - }, + }, + ), ), ], ), diff --git a/lib/presentation/setting/pages/server_key_page.dart b/lib/presentation/setting/pages/server_key_page.dart index 4a41ab7..f5fdb3e 100644 --- a/lib/presentation/setting/pages/server_key_page.dart +++ b/lib/presentation/setting/pages/server_key_page.dart @@ -1,3 +1,6 @@ +import 'package:enaklo_pos/core/components/components.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/presentation/setting/widgets/settings_title.dart'; import 'package:flutter/material.dart'; import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; @@ -36,37 +39,64 @@ class _ServerKeyPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Save Server Key'), - centerTitle: true, - ), - //textfield untuk input server key + backgroundColor: AppColors.background, body: Column( children: [ + SettingsTitle('Server Key'), Padding( - padding: const EdgeInsets.all(20.0), - child: TextField( - controller: serverKeyController, - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Server Key', - ), - ), - ), - //button untuk save server key - ElevatedButton( - onPressed: () { - AuthLocalDataSource() - .saveMidtransServerKey(serverKeyController!.text); - - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Server Key saved'), - backgroundColor: AppColors.primary, + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(20.0), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(8.0), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Server Key', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: AppColors.black, + ), + ), + SpaceHeight(4), + TextField( + controller: serverKeyController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'Server Key', + ), + ), + ], + ), ), - ); - }, - child: const Text('Save'), + const SpaceHeight(20), + Align( + alignment: Alignment.centerRight, + child: Button.filled( + width: context.deviceWidth * 0.2, + height: 44, + onPressed: () { + AuthLocalDataSource() + .saveMidtransServerKey(serverKeyController!.text); + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Server Key saved'), + backgroundColor: AppColors.primary, + ), + ); + }, + label: 'Simpan', + ), + ), + ], + ), ), ], ), diff --git a/lib/presentation/setting/pages/setting_page.dart b/lib/presentation/setting/pages/setting_page.dart new file mode 100644 index 0000000..d835371 --- /dev/null +++ b/lib/presentation/setting/pages/setting_page.dart @@ -0,0 +1,112 @@ +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/presentation/setting/pages/printer_page.dart'; +import 'package:enaklo_pos/presentation/setting/pages/setting_tile.dart'; +import 'package:flutter/material.dart'; + +class SettingPage extends StatefulWidget { + const SettingPage({super.key}); + + @override + State createState() => _SettingPageState(); +} + +class _SettingPageState extends State { + int currentIndex = 0; + String? role; + + void indexValue(int index) { + currentIndex = index; + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.background, + body: Row( + children: [ + Expanded( + flex: 2, + child: Align( + alignment: Alignment.topCenter, + child: Material( + color: AppColors.white, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 12.0, + ), + width: double.infinity, + height: context.deviceHeight * 0.1, + decoration: const BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.background, + width: 1.0, + ), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Pengaturan', + style: TextStyle( + color: AppColors.black, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + Text( + 'Kelola pengaturan aplikasi', + style: TextStyle( + color: AppColors.grey, + fontSize: 14, + ), + ), + ], + ), + ), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + SettingTile( + index: 0, + currentIndex: currentIndex, + title: 'Printer', + subtitle: 'Kelola printer', + icon: Icons.print_outlined, + onTap: () => indexValue(0), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + Expanded( + flex: 4, + child: Align( + alignment: AlignmentDirectional.topStart, + child: IndexedStack( + index: currentIndex, + children: [ + SettingPrinterPage(), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/setting/pages/setting_tile.dart b/lib/presentation/setting/pages/setting_tile.dart new file mode 100644 index 0000000..217c09f --- /dev/null +++ b/lib/presentation/setting/pages/setting_tile.dart @@ -0,0 +1,72 @@ +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:flutter/material.dart'; + +class SettingTile extends StatelessWidget { + final int index; + final int currentIndex; + final String title; + final String subtitle; + final IconData icon; + final Function() onTap; + + const SettingTile({ + super.key, + required this.title, + required this.subtitle, + required this.icon, + required this.onTap, + required this.index, + required this.currentIndex, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(12.0), + decoration: BoxDecoration( + border: Border( + right: BorderSide( + color: currentIndex == index + ? AppColors.primary + : Colors.transparent, + width: 4.0, + ), + ), + ), + child: Row( + children: [ + Icon( + icon, + size: 24.0, + color: currentIndex == index ? AppColors.primary : AppColors.grey, + ), + const SpaceWidth(12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4.0), + Text( + subtitle, + style: + const TextStyle(fontSize: 14.0, color: AppColors.grey), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/setting/pages/settings_page.dart b/lib/presentation/setting/pages/settings_page.dart index 4f7958b..6c5833b 100644 --- a/lib/presentation/setting/pages/settings_page.dart +++ b/lib/presentation/setting/pages/settings_page.dart @@ -1,15 +1,14 @@ +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/presentation/setting/pages/setting_tile.dart'; import 'package:flutter/material.dart'; import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; import 'package:enaklo_pos/presentation/sales/pages/sales_page.dart'; import 'package:enaklo_pos/presentation/setting/pages/discount_page.dart'; -import 'package:enaklo_pos/presentation/setting/pages/manage_printer_page.dart'; import 'package:enaklo_pos/presentation/setting/pages/product_page.dart'; import 'package:enaklo_pos/presentation/setting/pages/server_key_page.dart'; import 'package:enaklo_pos/presentation/setting/pages/sync_data_page.dart'; import 'package:enaklo_pos/presentation/setting/pages/tax_page.dart'; -import '../../../core/assets/assets.gen.dart'; -import '../../../core/components/spaces.dart'; import '../../../core/constants/colors.dart'; class SettingsPage extends StatefulWidget { @@ -45,6 +44,7 @@ class _SettingsPageState extends State { @override Widget build(BuildContext context) { return Scaffold( + backgroundColor: AppColors.background, body: Row( children: [ // LEFT CONTENT @@ -52,89 +52,109 @@ class _SettingsPageState extends State { flex: 2, child: Align( alignment: Alignment.topCenter, - child: ListView( - padding: const EdgeInsets.all(16.0), - children: [ - const Text( - 'Settings', - style: TextStyle( - color: AppColors.primary, - fontSize: 28, - fontWeight: FontWeight.w600, - ), - ), - const SpaceHeight(16.0), - role != null && role! != 'admin' - ? const SizedBox() - : ListTile( - contentPadding: const EdgeInsets.all(12.0), - leading: Assets.icons.kelolaProduk.svg(), - title: const Text('Manage Products'), - subtitle: const Text('Manage products in your store'), - textColor: AppColors.primary, - tileColor: currentIndex == 0 - ? AppColors.blueLight - : Colors.transparent, - onTap: () => indexValue(0), + child: Material( + color: AppColors.white, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 12.0, + ), + width: double.infinity, + height: context.deviceHeight * 0.1, + decoration: const BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.background, + width: 1.0, + ), ), - ListTile( - contentPadding: const EdgeInsets.all(12.0), - leading: Assets.icons.kelolaDiskon.svg(), - title: const Text('Kelola Diskon'), - subtitle: const Text('Kelola Diskon Pelanggan'), - textColor: AppColors.primary, - tileColor: currentIndex == 1 - ? AppColors.blueLight - : Colors.transparent, - onTap: () => indexValue(1), - ), - ListTile( - contentPadding: const EdgeInsets.all(12.0), - leading: Assets.icons.dashboard.svg(), - title: const Text('History Transaksi'), - subtitle: const Text('Lihat history transaksi'), - textColor: AppColors.primary, - tileColor: currentIndex == 2 - ? AppColors.blueLight - : Colors.transparent, - onTap: () => indexValue(2), - ), - ListTile( - contentPadding: const EdgeInsets.all(12.0), - leading: Assets.icons.kelolaPajak.svg(), - title: const Text('Perhitungan Biaya'), - subtitle: const Text('Kelola biaya diluar biaya modal'), - textColor: AppColors.primary, - tileColor: currentIndex == 3 - ? AppColors.blueLight - : Colors.transparent, - onTap: () => indexValue(3), - ), - ListTile( - contentPadding: const EdgeInsets.all(12.0), - leading: Assets.icons.kelolaPajak.svg(), - title: const Text('Sync Data'), - subtitle: - const Text('Sinkronisasi data dari dan ke server'), - textColor: AppColors.primary, - tileColor: currentIndex == 4 - ? AppColors.blueLight - : Colors.transparent, - onTap: () => indexValue(4), - ), - ListTile( - contentPadding: const EdgeInsets.all(12.0), - leading: Image.asset(Assets.images.manageQr.path, - fit: BoxFit.contain), - title: const Text('QR Key Setting'), - subtitle: const Text('QR Key Configuration'), - textColor: AppColors.primary, - tileColor: currentIndex == 6 - ? AppColors.blueLight - : Colors.transparent, - onTap: () => indexValue(6), - ), - ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Pengaturan', + style: TextStyle( + color: AppColors.black, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + Text( + 'Kelola pengaturan aplikasi', + style: TextStyle( + color: AppColors.grey, + fontSize: 14, + ), + ), + ], + ), + ), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + role != null && role! != 'admin' + ? const SizedBox() + : SettingTile( + index: 0, + currentIndex: currentIndex, + title: 'Kelola Produk', + subtitle: 'Kelola produk anda', + icon: Icons.inventory_outlined, + onTap: () => indexValue(0), + ), + SettingTile( + index: 1, + currentIndex: currentIndex, + title: 'Kelola Diskon', + subtitle: 'Kelola diskon pelanggan', + icon: Icons.discount_outlined, + onTap: () => indexValue(1), + ), + SettingTile( + index: 2, + currentIndex: currentIndex, + title: 'Riwayat Transaksi', + subtitle: 'Lihat riwayat transaksi', + icon: Icons.receipt_long_outlined, + onTap: () => + context.push(SalesPage(status: 'completed')), + ), + SettingTile( + index: 3, + currentIndex: currentIndex, + title: 'Perhitungan Biaya', + subtitle: 'Kelola biaya diluar biaya modal', + icon: Icons.attach_money_outlined, + onTap: () => indexValue(3), + ), + SettingTile( + index: 4, + currentIndex: currentIndex, + title: 'Sinkronisasi Data', + subtitle: 'Sinkronisasi data dari dan ke server', + icon: Icons.sync_outlined, + onTap: () => indexValue(4), + ), + SettingTile( + index: 6, + currentIndex: currentIndex, + title: 'Qr Key Setting', + subtitle: 'Kelola QR Key', + icon: Icons.qr_code_2_outlined, + onTap: () => indexValue(6), + ), + ], + ), + ), + ), + ], + ), ), ), ), @@ -144,26 +164,23 @@ class _SettingsPageState extends State { flex: 4, child: Align( alignment: AlignmentDirectional.topStart, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: IndexedStack( - index: currentIndex, - children: [ - role != null && role! != 'admin' - ? SizedBox() - : ProductPage(), - DiscountPage(), - SalesPage(), - TaxPage(), - SyncDataPage(), - ProductPage(), - ServerKeyPage() - // Text('tax'), - // ManageDiscount(), - // ManagePrinterPage(), - // ManageTax(), - ], - ), + child: IndexedStack( + index: currentIndex, + children: [ + role != null && role! != 'admin' ? SizedBox() : ProductPage(), + DiscountPage(), + SalesPage( + status: 'completed', + ), + TaxPage(), + SyncDataPage(), + ProductPage(), + ServerKeyPage() + // Text('tax'), + // ManageDiscount(), + // ManagePrinterPage(), + // ManageTax(), + ], ), ), ), diff --git a/lib/presentation/setting/pages/sync_data_page.dart b/lib/presentation/setting/pages/sync_data_page.dart index c1acd55..e5b690c 100644 --- a/lib/presentation/setting/pages/sync_data_page.dart +++ b/lib/presentation/setting/pages/sync_data_page.dart @@ -1,4 +1,7 @@ import 'dart:developer'; +import 'package:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/presentation/setting/widgets/settings_title.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; @@ -16,101 +19,154 @@ class _SyncDataPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Sync Data'), - ), + backgroundColor: AppColors.background, body: Column( children: [ - BlocConsumer( - listener: (context, state) { - state.maybeWhen( - orElse: () {}, - error: (message) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(message), - backgroundColor: Colors.red, - ), - ); - }, - loaded: (productResponseModel) async { - await ProductLocalDatasource.instance.deleteAllProducts(); - await ProductLocalDatasource.instance.insertProducts( - productResponseModel.data!, - ); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Sync Product Success2'), - backgroundColor: Colors.green, - ), - ); - }, - ); - }, - builder: (context, state) { - return state.maybeWhen( - orElse: () { - return ElevatedButton( - onPressed: () { - context - .read() - .add(const SyncProductEvent.syncProduct()); - }, - child: const Text('Sync Product')); - }, - loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, - ); - }, + SettingsTitle('Sync Data'), + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(16.0), + margin: const EdgeInsets.only(bottom: 16.0), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(8.0), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Sinkronasikan Produk', + style: TextStyle( + color: AppColors.black, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + BlocConsumer( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + error: (message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: Colors.red, + ), + ); + }, + loaded: (productResponseModel) async { + // await ProductLocalDatasource.instance + // .deleteAllProducts(); + // await ProductLocalDatasource.instance + // .insertProducts( + // productResponseModel.data!.products!, + // ); + // ScaffoldMessenger.of(context).showSnackBar( + // const SnackBar( + // content: Text('Sync Product Success2'), + // backgroundColor: Colors.green, + // ), + // ); + }, + ); + }, + builder: (context, state) { + return state.maybeWhen( + orElse: () { + return Button.filled( + width: 100, + height: 40, + onPressed: () { + context.read().add( + const SyncProductEvent.syncProduct()); + }, + label: 'Sinkronasikan', + ); + }, + loading: () { + return const Center( + child: CircularProgressIndicator(), + ); + }, + ); + }, + ), + ], + ), + ), + Container( + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(8.0), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Sinkronasikan Pesanan', + style: TextStyle( + color: AppColors.black, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + BlocConsumer( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + error: (message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: Colors.red, + ), + ); + }, + loaded: () { + // ScaffoldMessenger.of(context).showSnackBar( + // const SnackBar( + // content: Text('Sync Order Success'), + // backgroundColor: Colors.green, + // ), + // ); + }, + ); + }, + builder: (context, state) { + return state.maybeWhen( + orElse: () { + return Button.filled( + width: 100, + height: 40, + onPressed: () { + log("🔘 Sync Order button pressed"); + log("🔘 SyncOrderBloc instance: ${context.read()}"); + context + .read() + .add(const SyncOrderEvent.syncOrder()); + log("🔘 SyncOrderEvent.syncOrder dispatched"); + }, + label: 'Sinkronasikan', + ); + }, + loading: () { + return const Center( + child: CircularProgressIndicator(), + ); + }, + ); + }, + ) + ], + ), + ), + ], + ), ), - BlocConsumer( - listener: (context, state) { - state.maybeWhen( - orElse: () {}, - error: (message) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(message), - backgroundColor: Colors.red, - ), - ); - }, - loaded: () { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Sync Order Success'), - backgroundColor: Colors.green, - ), - ); - }, - ); - }, - builder: (context, state) { - return state.maybeWhen( - orElse: () { - return ElevatedButton( - onPressed: () { - log("🔘 Sync Order button pressed"); - log("🔘 SyncOrderBloc instance: ${context.read()}"); - context - .read() - .add(const SyncOrderEvent.syncOrder()); - log("🔘 SyncOrderEvent.syncOrder dispatched"); - }, - child: const Text('Sync Order'), - ); - }, - loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, - ); - }, - ) ], ), ); diff --git a/lib/presentation/setting/pages/tax_page.dart b/lib/presentation/setting/pages/tax_page.dart index 304977b..0c227f6 100644 --- a/lib/presentation/setting/pages/tax_page.dart +++ b/lib/presentation/setting/pages/tax_page.dart @@ -31,11 +31,11 @@ class _TaxPageState extends State { serviceChargeValue: serviceChargeValue, onSave: (newTaxValue, newServiceChargeValue) { context.read().add( - TaxSettingsEvent.updateSettings( - taxValue: newTaxValue, - serviceChargeValue: newServiceChargeValue, - ), - ); + TaxSettingsEvent.updateSettings( + taxValue: newTaxValue, + serviceChargeValue: newServiceChargeValue, + ), + ); }, ), ); @@ -49,11 +49,11 @@ class _TaxPageState extends State { serviceChargeValue: serviceChargeValue, onSave: (newTaxValue, newServiceChargeValue) { context.read().add( - TaxSettingsEvent.updateSettings( - taxValue: newTaxValue, - serviceChargeValue: newServiceChargeValue, - ), - ); + TaxSettingsEvent.updateSettings( + taxValue: newTaxValue, + serviceChargeValue: newServiceChargeValue, + ), + ); }, ), ); @@ -61,91 +61,110 @@ class _TaxPageState extends State { @override Widget build(BuildContext context) { - return SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const SettingsTitle('Perhitungan Biaya'), - const SizedBox(height: 24), - BlocBuilder( - builder: (context, state) { - return state.when( - initial: () => const Center(child: CircularProgressIndicator()), - loading: () => const Center(child: CircularProgressIndicator()), - error: (message) => Center(child: Text('Error: $message')), - loaded: (taxModel, serviceChargeValue) { - final items = [ - TaxModel(name: 'Biaya Layanan', type: TaxType.layanan, value: serviceChargeValue), - taxModel, - ]; + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const SettingsTitle( + 'Perhitungan Biaya', + subtitle: 'Biaya Layanan dan Pajak', + ), + Expanded( + child: SingleChildScrollView( + child: BlocBuilder( + builder: (context, state) { + return state.when( + initial: () => + const Center(child: CircularProgressIndicator()), + loading: () => + const Center(child: CircularProgressIndicator()), + error: (message) => Center(child: Text('Error: $message')), + loaded: (taxModel, serviceChargeValue) { + final items = [ + TaxModel( + name: 'Biaya Layanan', + type: TaxType.layanan, + value: serviceChargeValue), + taxModel, + ]; - return CustomTabBar( - tabTitles: const ['Layanan', 'Pajak'], - initialTabIndex: 0, - tabViews: [ - // LAYANAN TAB - SizedBox( - child: GridView.builder( - shrinkWrap: true, - itemCount: 2, // Add button + 1 service charge item - physics: const NeverScrollableScrollPhysics(), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - childAspectRatio: 0.85, - crossAxisCount: 3, - crossAxisSpacing: 30.0, - mainAxisSpacing: 30.0, - ), - itemBuilder: (context, index) { - if (index == 0) { - return AddData( - title: 'Edit Perhitungan', - onPressed: () => onAddDataTap(serviceChargeValue, taxModel.value), + return CustomTabBar( + tabTitles: const ['Layanan', 'Pajak'], + initialTabIndex: 0, + tabViews: [ + // LAYANAN TAB + SizedBox( + child: GridView.builder( + shrinkWrap: true, + itemCount: 2, // Add button + 1 service charge item + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.symmetric(horizontal: 16), + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + childAspectRatio: 0.85, + crossAxisCount: 3, + crossAxisSpacing: 30.0, + mainAxisSpacing: 30.0, + ), + itemBuilder: (context, index) { + if (index == 0) { + return AddData( + title: 'Edit Perhitungan', + onPressed: () => onAddDataTap( + serviceChargeValue, taxModel.value), + ); + } + final item = items.firstWhere( + (element) => element.type.isLayanan); + return ManageTaxCard( + data: item, + onEditTap: () => onEditTap( + item, serviceChargeValue, taxModel.value), ); - } - final item = items.firstWhere((element) => element.type.isLayanan); - return ManageTaxCard( - data: item, - onEditTap: () => onEditTap(item, serviceChargeValue, taxModel.value), - ); - }, + }, + ), ), - ), - // PAJAK TAB - SizedBox( - child: GridView.builder( - shrinkWrap: true, - itemCount: 2, // Add button + 1 tax item - physics: const NeverScrollableScrollPhysics(), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - childAspectRatio: 0.85, - crossAxisCount: 3, - crossAxisSpacing: 30.0, - mainAxisSpacing: 30.0, - ), - itemBuilder: (context, index) { - if (index == 0) { - return AddData( - title: 'Edit Perhitungan', - onPressed: () => onAddDataTap(serviceChargeValue, taxModel.value), + // PAJAK TAB + SizedBox( + child: GridView.builder( + shrinkWrap: true, + itemCount: 2, // Add button + 1 tax item + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.symmetric(horizontal: 16), + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + childAspectRatio: 0.85, + crossAxisCount: 3, + crossAxisSpacing: 30.0, + mainAxisSpacing: 30.0, + ), + itemBuilder: (context, index) { + if (index == 0) { + return AddData( + title: 'Edit Perhitungan', + onPressed: () => onAddDataTap( + serviceChargeValue, taxModel.value), + ); + } + final item = items.firstWhere( + (element) => element.type.isPajak); + return ManageTaxCard( + data: item, + onEditTap: () => onEditTap( + item, serviceChargeValue, taxModel.value), ); - } - final item = items.firstWhere((element) => element.type.isPajak); - return ManageTaxCard( - data: item, - onEditTap: () => onEditTap(item, serviceChargeValue, taxModel.value), - ); - }, + }, + ), ), - ), - ], - ); - }, - ); - }, + ], + ); + }, + ); + }, + ), ), - ], - ), + ), + ], ); } } diff --git a/lib/presentation/setting/widgets/add_data.dart b/lib/presentation/setting/widgets/add_data.dart index 384aab1..832309e 100644 --- a/lib/presentation/setting/widgets/add_data.dart +++ b/lib/presentation/setting/widgets/add_data.dart @@ -3,8 +3,6 @@ import 'package:flutter/material.dart'; import '../../../core/components/spaces.dart'; import '../../../core/constants/colors.dart'; - - class AddData extends StatelessWidget { final String title; final VoidCallback onPressed; @@ -21,18 +19,25 @@ class AddData extends StatelessWidget { onTap: onPressed, child: Container( padding: const EdgeInsets.all(16.0), - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - side: const BorderSide(width: 1, color: AppColors.card), - borderRadius: BorderRadius.circular(19), - ), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(8.0), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon( - Icons.add, - color: AppColors.primary, + Container( + width: 56.0, + height: 56.0, + padding: const EdgeInsets.all(12.0), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.add, + color: AppColors.primary, + ), ), const SpaceHeight(8.0), Text( diff --git a/lib/presentation/setting/widgets/manage_discount_card.dart b/lib/presentation/setting/widgets/manage_discount_card.dart index 8fa1c80..71be390 100644 --- a/lib/presentation/setting/widgets/manage_discount_card.dart +++ b/lib/presentation/setting/widgets/manage_discount_card.dart @@ -18,11 +18,9 @@ class ManageDiscountCard extends StatelessWidget { Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16.0), - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - side: const BorderSide(width: 1, color: AppColors.card), - borderRadius: BorderRadius.circular(19), - ), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(8.0), ), child: Stack( children: [ @@ -36,7 +34,7 @@ class ManageDiscountCard extends StatelessWidget { margin: const EdgeInsets.only(top: 30.0), decoration: BoxDecoration( shape: BoxShape.circle, - color: AppColors.disabled.withOpacity(0.4), + color: AppColors.primary.withOpacity(0.1), ), child: Text( '${data.value!.replaceAll('.00', '')}%', @@ -48,23 +46,12 @@ class ManageDiscountCard extends StatelessWidget { ), const Spacer(), Center( - child: RichText( - text: TextSpan( - text: 'Nama Promo : ', - children: [ - TextSpan( - text: data.name, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), - ], - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: AppColors.black, - ), + child: Text( + data.name ?? "-", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.black, ), ), ), diff --git a/lib/presentation/setting/widgets/manage_tax_card.dart b/lib/presentation/setting/widgets/manage_tax_card.dart index 0577f68..38a9f0e 100644 --- a/lib/presentation/setting/widgets/manage_tax_card.dart +++ b/lib/presentation/setting/widgets/manage_tax_card.dart @@ -18,11 +18,9 @@ class ManageTaxCard extends StatelessWidget { Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16.0), - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - side: const BorderSide(width: 1, color: AppColors.card), - borderRadius: BorderRadius.circular(19), - ), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(8.0), ), child: Stack( children: [ @@ -36,7 +34,7 @@ class ManageTaxCard extends StatelessWidget { margin: const EdgeInsets.only(top: 30.0), decoration: BoxDecoration( shape: BoxShape.circle, - color: AppColors.disabled.withOpacity(0.4), + color: AppColors.primary.withOpacity(0.1), ), child: Text( '${data.value}%', @@ -48,23 +46,12 @@ class ManageTaxCard extends StatelessWidget { ), const Spacer(), Center( - child: RichText( - text: TextSpan( - text: 'Nama Promo : ', - children: [ - TextSpan( - text: data.type.name, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), - ], - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: AppColors.black, - ), + child: Text( + data.type.name, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColors.black, ), ), ), diff --git a/lib/presentation/setting/widgets/menu_product_item.dart b/lib/presentation/setting/widgets/menu_product_item.dart index 083393d..3543c09 100644 --- a/lib/presentation/setting/widgets/menu_product_item.dart +++ b/lib/presentation/setting/widgets/menu_product_item.dart @@ -1,5 +1,7 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'package:cached_network_image/cached_network_image.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/presentation/setting/dialogs/detail_product_dialog.dart'; import 'package:flutter/material.dart'; import 'package:enaklo_pos/data/models/response/product_response_model.dart'; @@ -11,7 +13,149 @@ import '../../../core/constants/variables.dart'; class MenuProductItem extends StatelessWidget { final Product data; final Function() onTapEdit; - const MenuProductItem({ + const MenuProductItem( + {super.key, required this.data, required this.onTapEdit}); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.zero, + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.circular(12.0), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(8.0).copyWith(bottom: 0), + child: Stack( + children: [ + AspectRatio( + aspectRatio: 1.2, + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: CachedNetworkImage( + imageUrl: (data.imageUrl ?? '').contains('http') + ? data.imageUrl! + : '${Variables.baseUrl}/${data.imageUrl}', + fit: BoxFit.cover, + errorWidget: (context, url, error) => Container( + width: double.infinity, + height: context.deviceHeight * 0.18, + decoration: BoxDecoration( + color: AppColors.grey.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.image_outlined, + color: AppColors.grey, + size: 40, + ), + ), + ), + ), + ), + Positioned( + top: 8, + right: 8, + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: AppColors.primary, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + "", + style: const TextStyle( + color: AppColors.white, + fontSize: 10, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + child: Row( + children: [ + Expanded( + child: Text( + "${data.name}", + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w700, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () => showDialog( + context: context, + builder: (context) => DetailProductDialog(product: data), + ), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 4), + decoration: BoxDecoration( + color: AppColors.primary, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(12), + ), + ), + child: Icon( + Icons.visibility_outlined, + color: AppColors.white, + size: 18, + ), + ), + ), + ), + Container( + width: 1, + color: AppColors.grey.withOpacity(0.2), + ), + Expanded( + child: GestureDetector( + onTap: onTapEdit, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 4), + decoration: BoxDecoration( + color: AppColors.primary, + borderRadius: const BorderRadius.only( + bottomRight: Radius.circular(12), + ), + ), + child: Icon( + Icons.edit_outlined, + color: AppColors.white, + size: 18, + ), + ), + ), + ), + ], + ), + ], + ), + ); + } +} + +class MenuProductItemOld extends StatelessWidget { + final Product data; + final Function() onTapEdit; + const MenuProductItemOld({ super.key, required this.data, required this.onTapEdit, @@ -41,7 +185,7 @@ class MenuProductItem extends StatelessWidget { child: ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(10.0)), child: CachedNetworkImage( - imageUrl: '${Variables.baseUrl}/${data.image}', + imageUrl: '${Variables.baseUrl}/${data.name}', placeholder: (context, url) => const Center(child: CircularProgressIndicator()), errorWidget: (context, url, error) => const Icon( @@ -70,7 +214,7 @@ class MenuProductItem extends StatelessWidget { overflow: TextOverflow.ellipsis, ), Text( - data.category?.name ?? '-', + '-', style: const TextStyle( fontSize: 12, color: Colors.grey, @@ -116,7 +260,7 @@ class MenuProductItem extends StatelessWidget { Radius.circular(10.0)), child: CachedNetworkImage( imageUrl: - '${Variables.baseUrl}${data.image}', + '${Variables.baseUrl}${data.name}', placeholder: (context, url) => const Center( child: @@ -131,7 +275,7 @@ class MenuProductItem extends StatelessWidget { ), const SpaceHeight(10.0), Text( - data.category?.name ?? '-', + '-', style: const TextStyle( fontSize: 12, color: Colors.grey, @@ -147,7 +291,7 @@ class MenuProductItem extends StatelessWidget { ), const SpaceHeight(10.0), Text( - data.stock.toString(), + "data.stock.toString()", style: const TextStyle( fontSize: 12, color: Colors.grey, diff --git a/lib/presentation/setting/widgets/receipt_printer_page.dart b/lib/presentation/setting/widgets/receipt_printer_page.dart index 8561e6e..0e1d016 100644 --- a/lib/presentation/setting/widgets/receipt_printer_page.dart +++ b/lib/presentation/setting/widgets/receipt_printer_page.dart @@ -161,62 +161,71 @@ class _ReceiptPrinterPageState extends State { SpaceHeight(16), // button test print Button.outlined( - onPressed: () async { - if (addressController!.text.isNotEmpty && printNameController!.text.isNotEmpty) { - try { - // Get actual tax and service charge settings - final settingsLocalDatasource = SettingsLocalDatasource(); - final taxModel = await settingsLocalDatasource.getTax(); - final serviceChargeValue = await settingsLocalDatasource.getServiceCharge(); - - // Create a test print model - final testPrinter = PrintModel( - code: 'receipt', - name: printNameController!.text, - address: addressController!.text, - paper: paper, - type: selectedPrinter, - ); - - // Generate test print data - final testPrintData = await PrintDataoutputs.instance.printOrderV3( - [], // Empty product list for test - 0, - 0, - 'Test', - 0, - 0, - 0, - 0, - 0, - 1, - 'Test Cashier', - 'Test Customer', - int.parse(paper), - taxPercentage: taxModel.value, - serviceChargePercentage: serviceChargeValue, - ); - - // Print test - await PrinterService().printWithPrinter( - testPrinter, - testPrintData, - context, - ); - } catch (e) { - log("Error test printing: $e"); + onPressed: () async { + if (addressController!.text.isNotEmpty && + printNameController!.text.isNotEmpty) { + try { + // Get actual tax and service charge settings + final settingsLocalDatasource = + SettingsLocalDatasource(); + final taxModel = + await settingsLocalDatasource.getTax(); + final serviceChargeValue = + await settingsLocalDatasource + .getServiceCharge(); + + // Create a test print model + final testPrinter = PrintModel( + code: 'receipt', + name: printNameController!.text, + address: addressController!.text, + paper: paper, + type: selectedPrinter, + ); + + // Generate test print data + final testPrintData = await PrintDataoutputs + .instance + .printOrderV3( + [], // Empty product list for test + 0, + 0, + 'Test', + 0, + 0, + 0, + 0, + 0, + 1, + 'Test Cashier', + 'Test Customer', + int.parse(paper), + taxPercentage: taxModel.value, + serviceChargePercentage: serviceChargeValue, + ); + + // Print test + await PrinterService().printWithPrinter( + testPrinter, + testPrintData, + context, + ); + } catch (e) { + log("Error test printing: $e"); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error test printing: $e')), + ); + } + } else { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Error test printing: $e')), + SnackBar( + content: Text( + 'Please fill in printer details first')), ); } - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Please fill in printer details first')), - ); - } - }, - label: 'Test Print' - ), + }, + label: 'Test Print'), SpaceHeight(8), // button save data == null diff --git a/lib/presentation/setting/widgets/settings_title.dart b/lib/presentation/setting/widgets/settings_title.dart index 00e30b1..bcb8222 100644 --- a/lib/presentation/setting/widgets/settings_title.dart +++ b/lib/presentation/setting/widgets/settings_title.dart @@ -1,45 +1,76 @@ +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:flutter/material.dart'; import '../../../core/components/search_input.dart'; import '../../../core/constants/colors.dart'; - - class SettingsTitle extends StatelessWidget { final String title; + final String? subtitle; final TextEditingController? controller; final Function(String value)? onChanged; + final List? actionWidget; const SettingsTitle( this.title, { super.key, this.controller, this.onChanged, + this.actionWidget, + this.subtitle, }); @override Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - title, - style: const TextStyle( - color: AppColors.primary, - fontSize: 20, - fontWeight: FontWeight.w600, + return Container( + height: context.deviceHeight * 0.1, + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + decoration: BoxDecoration( + color: AppColors.white, + border: Border( + bottom: BorderSide( + color: AppColors.background, + width: 1.0, ), ), - if (controller != null) - SizedBox( - width: 300.0, - child: SearchInput( - controller: controller!, - onChanged: onChanged, - hintText: 'Search for food, coffe, etc..', - ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + title, + style: TextStyle( + color: AppColors.black, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + if (subtitle != null) + Text( + subtitle ?? '', + style: TextStyle( + color: AppColors.grey, + fontSize: 14, + ), + ), + ], ), - ], + if (controller != null) + SizedBox( + width: 300.0, + child: SearchInput( + controller: controller!, + onChanged: onChanged, + hintText: 'Search for food, coffe, etc..', + ), + ), + if (actionWidget != null) ...actionWidget!, + ], + ), ); } } diff --git a/lib/presentation/setting/widgets/ticket_printer_page.dart b/lib/presentation/setting/widgets/ticket_printer_page.dart new file mode 100644 index 0000000..bf80b29 --- /dev/null +++ b/lib/presentation/setting/widgets/ticket_printer_page.dart @@ -0,0 +1,310 @@ +import 'dart:developer'; + +import 'package:enaklo_pos/core/function/app_function.dart'; +import 'package:enaklo_pos/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:enaklo_pos/core/components/components.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/core/utils/printer_service.dart'; +import 'package:enaklo_pos/data/dataoutputs/print_dataoutputs.dart'; +import 'package:enaklo_pos/data/models/response/print_model.dart'; +import 'package:enaklo_pos/presentation/setting/bloc/create_printer/create_printer_bloc.dart'; +import 'package:enaklo_pos/presentation/setting/bloc/update_printer/update_printer_bloc.dart'; +import 'package:enaklo_pos/presentation/setting/widgets/dialog_search_printer.dart'; + +class TicketPrinterPage extends StatefulWidget { + const TicketPrinterPage({super.key}); + + @override + State createState() => _TicketPrinterPageState(); +} + +class _TicketPrinterPageState extends State { + String selectedPrinter = 'Bluetooth'; + TextEditingController? addressController; + TextEditingController? printNameController; + String paper = '58'; + bool isInitialized = false; + @override + void initState() { + super.initState(); + addressController = TextEditingController(); + printNameController = TextEditingController(); + context.read().add(GetPrinterTicketEvent.get()); + } + + @override + void dispose() { + addressController!.dispose(); + printNameController!.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () { + return SizedBox.shrink(); + }, + loading: () { + return Center(child: CircularProgressIndicator()); + }, + success: (data) { + if (data != null && !isInitialized) { + addressController!.text = data.address; + printNameController!.text = data.name; + selectedPrinter = data.type; + paper = data.paper; + isInitialized = true; + } + return Container( + // width: 300, + width: context.deviceWidth, + padding: const EdgeInsets.all(16.0), + color: Colors.white, + child: ListView( + children: [ + const Text( + 'Tiket Printer', + style: TextStyle( + color: Colors.black, + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + SpaceHeight(16), + + DropdownButtonFormField( + decoration: InputDecoration( + labelText: 'Printer Type', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.0), + ), + ), + value: selectedPrinter, // nilai default + items: const [ + DropdownMenuItem( + value: 'Bluetooth', + child: Text('Bluetooth'), + ), + DropdownMenuItem( + value: 'Network', + child: Text('Network'), + ), + ], + onChanged: (value) { + selectedPrinter = value ?? 'Bluetooth'; + setState(() {}); + }, + ), + SpaceHeight(8), + //button search + selectedPrinter == 'Bluetooth' + ? Button.outlined( + onPressed: () { + showDialog( + context: context, + builder: (context) => DialogSearchPrinter( + onSelected: (value) { + addressController!.text = value; + setState(() {}); + }, + ), + ); + }, + label: 'Search') + : CustomTextField( + controller: addressController!, + label: 'Address', + showLabel: false, + ), + SpaceHeight(16), + // Textfield for name + CustomTextField( + controller: printNameController!, + label: 'Print Name', + showLabel: false, + ), + SpaceHeight(16), + // Textfield for width paper + DropdownButtonFormField( + decoration: InputDecoration( + labelText: 'Width Paper', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.0), + ), + ), + value: paper, // nilai default + items: const [ + DropdownMenuItem( + value: '58', + child: Text('58 mm'), + ), + DropdownMenuItem( + value: '80', + child: Text('80 mm'), + ), + ], + onChanged: (value) { + paper = value ?? '58'; + log('Paper : $paper'); + }, + ), + + SpaceHeight(16), + // button test print + Button.outlined( + onPressed: () async { + if (addressController!.text.isNotEmpty && + printNameController!.text.isNotEmpty) { + try { + // Create a test print model + final testPrinter = PrintModel( + code: 'ticket', + name: printNameController!.text, + address: addressController!.text, + paper: paper, + type: selectedPrinter, + ); + final barcode = + await generateBarcodeAsUint8List('123'); + // Generate test print data + final testPrintData = + await PrintDataoutputs.instance.printTicket( + 10000, + barcode, + int.parse(paper), + ); + + // Print test + await PrinterService().printWithPrinter( + testPrinter, + testPrintData, + context, + ); + } catch (e) { + log("Error test printing: $e"); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error test printing: $e')), + ); + } + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Please fill in printer details first')), + ); + } + }, + label: 'Test Print'), + SpaceHeight(8), + // button save + data == null + ? BlocConsumer( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: (message) { + final snackBar = SnackBar( + content: Text(message), + backgroundColor: AppColors.primary, + ); + ScaffoldMessenger.of(context) + .showSnackBar(snackBar); + context + .read() + .add(GetPrinterTicketEvent.get()); + }, + ); + }, + builder: (context, state) { + return state.maybeWhen( + orElse: () { + return Button.filled( + onPressed: () { + final printData = PrintModel( + code: 'ticket', + name: printNameController!.text, + address: addressController!.text, + paper: paper, + type: selectedPrinter, + ); + + context.read().add( + CreatePrinterEvent.createPrinter( + printData), + ); + }, + label: 'Save', + ); + }, + loading: () { + return CircularProgressIndicator(); + }, + ); + }, + ) + : BlocConsumer( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: (message) { + final snackBar = SnackBar( + content: Text(message), + backgroundColor: AppColors.primary, + ); + ScaffoldMessenger.of(context) + .showSnackBar(snackBar); + context + .read() + .add(GetPrinterTicketEvent.get()); + }, + ); + }, + builder: (context, state) { + return state.maybeWhen( + orElse: () { + return Button.filled( + onPressed: () { + final printData = PrintModel( + id: data.id, + code: 'ticket', + name: printNameController!.text, + address: addressController!.text, + paper: paper, + type: selectedPrinter, + ); + log("Print Data : ${printData.toMap()}"); + + context.read().add( + UpdatePrinterEvent.updatePrinter( + printData, + ), + ); + }, + label: 'Save', + ); + }, + loading: () { + return CircularProgressIndicator(); + }, + ); + }, + ), + SpaceHeight(16), + ], + ), + ); + }, + ); + }, + )); + } +} diff --git a/lib/presentation/split_bill/pages/split_bill_page.dart b/lib/presentation/split_bill/pages/split_bill_page.dart new file mode 100644 index 0000000..3d5b6c5 --- /dev/null +++ b/lib/presentation/split_bill/pages/split_bill_page.dart @@ -0,0 +1,959 @@ +import 'dart:developer'; + +import 'package:enaklo_pos/core/components/flushbar.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/data/models/response/customer_response_model.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:enaklo_pos/presentation/home/widgets/customer_auto_complete_field.dart'; +import 'package:enaklo_pos/presentation/payment/pages/payment_page.dart'; +import 'package:flutter/material.dart'; + +class AppColorSplitBill { + static const Color primary = Color(0xff36175e); + static const Color background = Color(0xfff8f9fa); + static const Color cardBackground = Colors.white; + static const Color textPrimary = Color(0xff2d3436); + static const Color textSecondary = Color(0xff636e72); + static const Color success = Color(0xff00b894); + static const Color border = Color(0xffddd); +} + +class SplitBillPage extends StatefulWidget { + final Order order; + + const SplitBillPage({super.key, required this.order}); + + @override + State createState() => _SplitBillPageState(); +} + +class _SplitBillPageState extends State { + final customerController = TextEditingController(); + Customer? selectedCustomer; + + int selectedSplitType = 0; // 0 = Per Product, 1 = Per Amount + + // Per Product Split Data + Map selectedProducts = {}; // {itemId: quantity} + + // Per Amount Split Data + TextEditingController amountController = TextEditingController(); + int splitAmount = 0; + int remainingAmount = 0; + + List getOrderItemPending() => + widget.order.orderItems + ?.where((item) => item.status == "pending") + .toList() ?? + []; + + init() { + setState(() { + remainingAmount = + (widget.order.totalAmount ?? 0) - (widget.order.totalPaid ?? 0); + selectedSplitType = widget.order.splitType == 'AMOUNT' ? 1 : 0; + }); + } + + @override + void initState() { + init(); + super.initState(); + } + + @override + void dispose() { + amountController.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColorSplitBill.background, + body: Row( + children: [ + // Left Panel - Bill Details + Expanded( + flex: 2, + child: Container( + color: AppColorSplitBill.cardBackground, + padding: EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Ringkasan Pesanan', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColorSplitBill.textPrimary, + ), + ), + SizedBox(height: 8), + Text( + 'Order #${widget.order.orderNumber}', + style: TextStyle( + fontSize: 16, + color: AppColorSplitBill.textSecondary, + ), + ), + SizedBox(height: 24), + Expanded( + child: ListView.builder( + itemCount: getOrderItemPending().length, + itemBuilder: (context, index) { + return _buildOrderItem(getOrderItemPending()[index]); + }, + ), + ), + Container( + width: double.infinity, + height: 1, + color: AppColorSplitBill.border, + margin: EdgeInsets.symmetric(vertical: 16), + ), + if (widget.order.subtotal != widget.order.totalAmount) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Subtotal', + style: TextStyle( + fontSize: 16, + color: AppColorSplitBill.textSecondary, + ), + ), + Text( + 'Rp ${_formatCurrency(widget.order.subtotal ?? 0)}', + style: TextStyle( + fontSize: 16, + color: AppColorSplitBill.textSecondary, + ), + ), + ], + ), + if ((widget.order.taxAmount ?? 0) > 0) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Pajak', + style: TextStyle( + fontSize: 16, + color: AppColorSplitBill.textSecondary, + ), + ), + Text( + 'Rp ${_formatCurrency(widget.order.taxAmount ?? 0)}', + style: TextStyle( + fontSize: 16, + color: AppColorSplitBill.textSecondary, + ), + ), + ], + ), + if ((widget.order.discountAmount ?? 0) > 0) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Diskon', + style: TextStyle( + fontSize: 16, + color: AppColorSplitBill.success, + ), + ), + Text( + '- Rp ${_formatCurrency(widget.order.discountAmount ?? 0)}', + style: TextStyle( + fontSize: 16, + color: AppColorSplitBill.success, + ), + ), + ], + ), + SizedBox(height: 8), + ], + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total terbayar', + style: TextStyle( + fontSize: 14, + color: AppColorSplitBill.textPrimary, + fontWeight: FontWeight.w600, + ), + ), + Text( + 'Rp ${_formatCurrency(widget.order.totalPaid ?? 0)}', + style: TextStyle( + fontSize: 14, + color: AppColorSplitBill.textPrimary, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + SizedBox(height: 2), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Sisa Tagihan', + style: TextStyle( + fontSize: 14, + color: Colors.red, + fontWeight: FontWeight.w600, + ), + ), + Text( + 'Rp ${_formatCurrency(widget.order.remainingAmount ?? 0)}', + style: TextStyle( + fontSize: 14, + color: Colors.red, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + SizedBox(height: 2), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total Pembayaran', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: AppColorSplitBill.textPrimary, + ), + ), + Text( + 'Rp ${_formatCurrency(widget.order.totalAmount ?? 0)}', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: AppColorSplitBill.primary, + ), + ), + ], + ), + ], + ), + ), + ), + + // Right Panel - Split Options + Expanded( + flex: 3, + child: Container( + padding: EdgeInsets.all(24), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Bagi Bill', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColorSplitBill.textPrimary, + ), + ), + SizedBox(height: 24), + + _buildCustomer(context), + + SizedBox(height: 24), + + // Split Type Selection + _buildRowButtonSplit(), + + SizedBox(height: 32), + + // Split Content + Container( + child: selectedSplitType == 0 + ? _buildPerProductSplit() + : _buildPerAmountSplit(), + ), + + SizedBox(height: 24), + + // Action Buttons + Row( + children: [ + Expanded( + child: _buildActionButton( + 'Batal', + AppColorSplitBill.textSecondary, + Colors.transparent, + () => Navigator.pop(context), + ), + ), + SizedBox(width: 16), + Expanded( + child: _buildActionButton( + 'Konfirmasi Split', + Colors.white, + AppColorSplitBill.primary, + () { + _confirmSplit(); + }, + ), + ), + ], + ), + ], + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildRowButtonSplit() { + switch (widget.order.splitType) { + case 'AMOUNT': + return _buildSplitTypeButton('Per Jumlah', 1); + case 'ITEM': + return _buildSplitTypeButton('Per Produk', 0); + default: + return Row( + children: [ + _buildSplitTypeButton('Per Produk', 0), + SizedBox(width: 16), + _buildSplitTypeButton('Per Jumlah', 1), + ], + ); + } + } + + Widget _buildCustomer(BuildContext context) { + return Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColorSplitBill.cardBackground, + border: Border.all(color: AppColorSplitBill.border), + borderRadius: BorderRadius.circular(8), + ), + child: CustomerAutocomplete( + controller: customerController, + selectedCustomer: selectedCustomer, + onSelected: (customer) { + setState(() { + selectedCustomer = customer; + }); + }, + ), + ); + } + + Widget _buildOrderItem(OrderItem item) { + return Container( + padding: EdgeInsets.symmetric(vertical: 12), + child: Column( + children: [ + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.productName ?? '', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColorSplitBill.textPrimary, + ), + ), + if (item.productVariantName != null && + item.productVariantName!.isNotEmpty) + Text( + item.productVariantName!, + style: TextStyle( + fontSize: 14, + color: AppColorSplitBill.textSecondary, + fontStyle: FontStyle.italic, + ), + ), + Text( + 'Qty: ${item.quantity} x Rp ${_formatCurrency(item.unitPrice ?? 0)}', + style: TextStyle( + fontSize: 14, + color: AppColorSplitBill.textSecondary, + ), + ), + ], + ), + ), + Text( + 'Rp ${_formatCurrency(item.totalPrice ?? 0)}', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColorSplitBill.textPrimary, + ), + ), + ], + ), + if ((item.paidQuantity ?? 0) > 1) ...[ + SpaceHeight(6), + Align( + alignment: Alignment.centerRight, + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: AppColorSplitBill.primary.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: AppColorSplitBill.primary), + ), + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: '${item.paidQuantity} ', + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: AppColorSplitBill.primary, + ), + ), + TextSpan( + text: 'dari ', + style: const TextStyle( + fontSize: 12, + color: AppColorSplitBill.primary, + ), + ), + TextSpan( + text: '${item.quantity} ', + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: AppColorSplitBill.primary, + ), + ), + TextSpan( + text: 'kuantiti telah dibayar.', + style: const TextStyle( + fontSize: 12, + color: AppColorSplitBill.primary, + ), + ), + ], + ), + ), + ), + ) + ], + ], + ), + ); + } + + Widget _buildSplitTypeButton(String title, int type) { + bool isSelected = selectedSplitType == type; + return GestureDetector( + onTap: () { + setState(() { + selectedSplitType = type; + }); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), + decoration: BoxDecoration( + color: isSelected ? AppColorSplitBill.primary : Colors.transparent, + border: Border.all( + color: isSelected + ? AppColorSplitBill.primary + : AppColorSplitBill.border, + width: 2, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + title, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: isSelected ? Colors.white : AppColorSplitBill.textPrimary, + ), + ), + ), + ); + } + + Widget _buildPerProductSplit() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Pilih Produk untuk Split', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColorSplitBill.textPrimary, + ), + ), + SizedBox(height: 16), + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColorSplitBill.cardBackground, + border: Border.all(color: AppColorSplitBill.border), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Split Bill', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColorSplitBill.primary, + ), + ), + SizedBox(height: 16), + ListView.builder( + itemCount: getOrderItemPending().length, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return _buildProductSplitItem(getOrderItemPending()[index]); + }, + ), + Container( + width: double.infinity, + height: 1, + color: AppColorSplitBill.border, + margin: EdgeInsets.symmetric(vertical: 12), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total Split:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColorSplitBill.textPrimary, + ), + ), + Text( + 'Rp ${_formatCurrency(_calculateSplitTotal())}', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColorSplitBill.primary, + ), + ), + ], + ), + ], + ), + ), + ], + ); + } + + Widget _buildProductSplitItem(OrderItem item) { + int selectedQty = selectedProducts[item.id] ?? 0; + int maxQty = (item.quantity ?? 0) - (item.paidQuantity ?? 0); + + return Container( + margin: EdgeInsets.only(bottom: 12), + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: selectedQty > 0 + ? AppColorSplitBill.primary.withOpacity(0.1) + : Colors.transparent, + border: Border.all( + color: selectedQty > 0 + ? AppColorSplitBill.primary + : AppColorSplitBill.border, + ), + borderRadius: BorderRadius.circular(6), + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.productName ?? '', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColorSplitBill.textPrimary, + ), + ), + if (item.productVariantName != null && + item.productVariantName!.isNotEmpty) + Text( + item.productVariantName!, + style: TextStyle( + fontSize: 12, + color: AppColorSplitBill.textSecondary, + fontStyle: FontStyle.italic, + ), + ), + Text( + 'Rp ${_formatCurrency(item.unitPrice ?? 0)} per item', + style: TextStyle( + fontSize: 12, + color: AppColorSplitBill.textSecondary, + ), + ), + ], + ), + ), + Row( + children: [ + GestureDetector( + onTap: () { + if (selectedQty > 0) { + setState(() { + selectedProducts[item.id!] = selectedQty - 1; + if (selectedProducts[item.id!] == 0) { + selectedProducts.remove(item.id); + } + }); + } + }, + child: Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: selectedQty > 0 + ? AppColorSplitBill.primary + : AppColorSplitBill.border, + borderRadius: BorderRadius.circular(4), + ), + child: Icon( + Icons.remove, + size: 16, + color: selectedQty > 0 + ? Colors.white + : AppColorSplitBill.textSecondary, + ), + ), + ), + Container( + width: 40, + height: 32, + margin: EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + border: Border.all(color: AppColorSplitBill.border), + borderRadius: BorderRadius.circular(4), + ), + child: Center( + child: Text( + '$selectedQty', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: AppColorSplitBill.textPrimary, + ), + ), + ), + ), + GestureDetector( + onTap: () { + if (selectedQty < maxQty) { + setState(() { + selectedProducts[item.id!] = selectedQty + 1; + }); + } + }, + child: Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: selectedQty < maxQty + ? AppColorSplitBill.primary + : AppColorSplitBill.border, + borderRadius: BorderRadius.circular(4), + ), + child: Icon( + Icons.add, + size: 16, + color: selectedQty < maxQty + ? Colors.white + : AppColorSplitBill.textSecondary, + ), + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildPerAmountSplit() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Masukkan Jumlah yang Akan Dibayar', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColorSplitBill.textPrimary, + ), + ), + SizedBox(height: 16), + Column( + children: [ + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColorSplitBill.cardBackground, + border: Border.all(color: AppColorSplitBill.border), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Split Bill', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColorSplitBill.primary, + ), + ), + SizedBox(height: 16), + Text( + 'Jumlah yang akan dibayar:', + style: TextStyle( + fontSize: 14, + color: AppColorSplitBill.textSecondary, + ), + ), + SizedBox(height: 8), + Container( + decoration: BoxDecoration( + border: Border.all(color: AppColorSplitBill.border), + borderRadius: BorderRadius.circular(8), + ), + child: TextField( + controller: amountController, + keyboardType: TextInputType.number, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: AppColorSplitBill.textPrimary, + ), + onChanged: (value) { + setState(() { + splitAmount = int.tryParse(value) ?? 0; + }); + }, + decoration: InputDecoration( + hintText: '0', + prefixText: 'Rp ', + prefixStyle: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: AppColorSplitBill.textPrimary, + ), + border: InputBorder.none, + contentPadding: EdgeInsets.all(16), + ), + ), + ), + SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total yang dibayar:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColorSplitBill.textPrimary, + ), + ), + Text( + 'Rp ${_formatCurrency(splitAmount)}', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: AppColorSplitBill.primary, + ), + ), + ], + ), + SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Sisa bill:', + style: TextStyle( + fontSize: 14, + color: AppColorSplitBill.textSecondary, + ), + ), + Text( + 'Rp ${_formatCurrency((remainingAmount) - splitAmount)}', + style: TextStyle( + fontSize: 14, + color: AppColorSplitBill.textSecondary, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ], + ); + } + + Widget _buildActionButton( + String text, Color textColor, Color bgColor, VoidCallback onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + height: 50, + decoration: BoxDecoration( + color: bgColor, + border: bgColor == Colors.transparent + ? Border.all(color: AppColorSplitBill.border) + : null, + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Text( + text, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: textColor, + ), + ), + ), + ), + ); + } + + int _calculateSplitTotal() { + int total = 0; + selectedProducts.forEach((itemId, quantity) { + OrderItem? item = getOrderItemPending() + .firstWhere((item) => item.id == itemId, orElse: () => OrderItem()); + if (item.unitPrice != null) { + total += (item.unitPrice! * quantity); + } + }); + return total; + } + + String _formatCurrency(int amount) { + return amount.toString().replaceAllMapped( + RegExp(r'(\d)(?=(\d{3})+(?!\d))'), + (Match m) => '${m[1]}.', + ); + } + + void _confirmSplit() { + if (selectedSplitType == 0) { + // Per Product Split + int splitTotal = _calculateSplitTotal(); + if (splitTotal > 0) { + // Create a new Order object with only the selected products for split + List splitItems = []; + selectedProducts.forEach((itemId, quantity) { + OrderItem? originalItem = getOrderItemPending().firstWhere( + (item) => item.id == itemId, + orElse: () => OrderItem()); + // ignore: unnecessary_null_comparison + if (originalItem != null && originalItem.id != null) { + // Create a copy of the item with the selected quantity + OrderItem splitItem = OrderItem( + id: originalItem.id, + productName: originalItem.productName, + productVariantName: originalItem.productVariantName, + quantity: quantity, + unitPrice: originalItem.unitPrice, + totalPrice: (originalItem.unitPrice ?? 0) * quantity, + status: originalItem.status, + ); + splitItems.add(splitItem); + } + }); + + // Create split order object + Order splitOrder = Order( + id: widget.order.id, + orderNumber: widget.order.orderNumber, + orderItems: splitItems, + subtotal: splitTotal, + totalAmount: splitTotal, + taxAmount: 0, // You might want to calculate proportional tax + discountAmount: + 0, // You might want to calculate proportional discount + ); + + log("Split Order: ${splitItems.length}"); + + // Navigate to PaymentPage with the split order + context.push(PaymentPage( + order: splitOrder, + isSplit: true, + splitType: 'ITEM', + customerId: selectedCustomer?.id, + )); + } else { + AppFlushbar.showError(context, "Pilih minimal satu produk untuk split"); + } + } else { + // Per Amount Split + int totalAmount = + (widget.order.totalAmount ?? 0) - (widget.order.totalPaid ?? 0); + + if (splitAmount > 0 && splitAmount <= totalAmount) { + // Create split order object with the specified amount + Order splitOrder = Order( + id: widget.order.id, + orderNumber: widget.order.orderNumber, + orderItems: getOrderItemPending(), // Keep all items for reference + subtotal: splitAmount, + totalAmount: splitAmount, + taxAmount: 0, // You might want to calculate proportional values + discountAmount: 0, + ); + + // Navigate to PaymentPage with the split order + context.push( + PaymentPage( + order: splitOrder, + isSplit: true, + splitType: 'AMOUNT', + customerId: selectedCustomer?.id, + ), + ); + } else if (splitAmount > totalAmount) { + AppFlushbar.showError( + context, "Jumlah split tidak boleh melebihi total bill"); + } else { + AppFlushbar.showError(context, "Pilih minimal satu produk untuk split"); + } + } + } +} diff --git a/lib/presentation/success/pages/success_order_page.dart b/lib/presentation/success/pages/success_order_page.dart new file mode 100644 index 0000000..b13d54d --- /dev/null +++ b/lib/presentation/success/pages/success_order_page.dart @@ -0,0 +1,1218 @@ +import 'package:enaklo_pos/core/constants/colors.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/string_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/presentation/home/models/product_quantity.dart'; +import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart'; +import 'package:flutter/material.dart'; + +class SuccessOrderPage extends StatefulWidget { + final List productQuantity; + final Order order; + final String paymentMethod; + final int nominalBayar; + final int taxPercentage; + + const SuccessOrderPage({ + super.key, + required this.order, + required this.productQuantity, + required this.paymentMethod, + required this.nominalBayar, + required this.taxPercentage, + }); + + @override + State createState() => _SuccessOrderPageState(); +} + +class _SuccessOrderPageState extends State + with TickerProviderStateMixin { + late AnimationController _mainController; + late AnimationController _successController; + late AnimationController _cardController; + late AnimationController _floatingController; + + late Animation _fadeInAnimation; + late Animation _scaleAnimation; + late Animation _successIconAnimation; + late Animation _slideUpAnimation; + late Animation _floatingAnimation; + late Animation _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( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _mainController, + curve: const Interval(0.0, 0.6, curve: Curves.easeOut), + )); + + _scaleAnimation = Tween( + begin: 0.8, + end: 1.0, + ).animate(CurvedAnimation( + parent: _mainController, + curve: const Interval(0.2, 0.8, curve: Curves.elasticOut), + )); + + _successIconAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _successController, + curve: Curves.elasticOut, + )); + + _slideUpAnimation = Tween( + begin: const Offset(0.0, 0.3), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: _cardController, + curve: Curves.easeOutCubic, + )); + + _floatingAnimation = Tween( + begin: -5.0, + end: 5.0, + ).animate(CurvedAnimation( + parent: _floatingController, + curve: Curves.easeInOut, + )); + + _shimmerAnimation = Tween( + 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 + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.background, + body: SafeArea( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + AppColors.primary.withOpacity(0.05), + AppColors.background, + AppColors.background, + ], + ), + ), + child: FadeTransition( + opacity: _fadeInAnimation, + child: ScaleTransition( + scale: _scaleAnimation, + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Row( + children: [ + // Left Panel - Success Message & Order Info + Expanded( + flex: 35, + child: _buildLeftPanel(), + ), + + const SizedBox(width: 16), + + // Right Panel - Order Details + Expanded( + flex: 65, + child: _buildRightPanel(), + ), + ], + ), + ), + ), + ), + ), + ), + ); + } + + Widget _buildLeftPanel() { + return SlideTransition( + position: _slideUpAnimation, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.1), + blurRadius: 30, + offset: const Offset(0, 10), + ), + ], + ), + child: Column( + children: [ + // Success Header with Glassmorphism Effect + Container( + width: double.infinity, + padding: const EdgeInsets.all(32.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.05), + ], + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(24), + ), + ), + child: Column( + children: [ + // Animated Success Icon with Floating Effect + 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), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 20, + offset: const Offset(0, 10), + ), + ], + ), + child: const Icon( + Icons.check_rounded, + size: 48, + color: Colors.white, + ), + ), + ), + ); + }, + ), + + const SizedBox(height: 24), + + // Success Title with Shimmer Effect + 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!', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + ), + + const SizedBox(height: 12), + + FadeTransition( + opacity: _fadeInAnimation, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + ), + child: const Text( + 'Pesanan telah diterima dan sedang diproses', + style: TextStyle( + fontSize: 14, + color: AppColors.primary, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + ), + + // Order Information Section + Expanded( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionTitle('Informasi Pesanan'), + const SizedBox(height: 24), + + // Customer Card with Gradient Background + _buildInfoCard( + icon: Icons.person_outline_rounded, + title: 'Nama Pelanggan', + value: widget.order.metadata?['customer_name'] ?? "-", + gradient: [ + Colors.blue.withOpacity(0.1), + Colors.purple.withOpacity(0.1), + ], + ), + + const SizedBox(height: 16), + + // Order Details Grid + Expanded( + child: Column( + children: [ + _buildInfoRow( + icon: Icons.receipt_long_outlined, + label: 'No. Pesanan', + value: widget.order.orderNumber ?? "-", + delay: 0.3, + ), + const SizedBox(height: 12), + _buildInfoRow( + icon: Icons.table_restaurant_outlined, + label: 'No. Meja', + value: widget.order.tableNumber ?? "-", + delay: 0.4, + ), + const SizedBox(height: 12), + _buildInfoRow( + icon: Icons.access_time_rounded, + label: 'Waktu', + value: (widget.order.createdAt ?? DateTime.now()) + .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, + ), + ], + ), + ), + ], + ), + ), + ), + + // Total and Action Buttons + _buildBottomSection(), + ], + ), + ), + ); + } + + Widget _buildRightPanel() { + return SlideTransition( + position: _slideUpAnimation, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 30, + offset: const Offset(0, 10), + ), + ], + ), + child: Column( + children: [ + // Header with Modern Design + Container( + width: double.infinity, + padding: const EdgeInsets.all(24.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.background, + Colors.grey.shade50, + ], + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(24), + ), + ), + child: Row( + children: [ + ScaleTransition( + scale: _successIconAnimation, + child: Container( + padding: const EdgeInsets.all(12.0), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary.withOpacity(0.2), + AppColors.primary.withOpacity(0.1), + ], + ), + borderRadius: BorderRadius.circular(16.0), + ), + child: Icon( + Icons.receipt_long_rounded, + color: AppColors.primary, + size: 28, + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FadeTransition( + opacity: _fadeInAnimation, + child: const Text( + 'Detail Pesanan', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 4), + FadeTransition( + opacity: _fadeInAnimation, + child: Text( + 'Ringkasan item yang dipesan', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + ), + ], + ), + ), + ScaleTransition( + scale: _scaleAnimation, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 10), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Text( + '${widget.productQuantity.length} Items', + style: const TextStyle( + fontSize: 13, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ), + + // Enhanced Product List + Expanded( + child: ListView.separated( + padding: const EdgeInsets.all(24.0), + itemCount: widget.productQuantity.length, + separatorBuilder: (context, index) => + const SizedBox(height: 12), + itemBuilder: (context, index) { + return _buildProductCard(index); + }, + ), + ), + + // Enhanced Summary Footer + _buildSummaryFooter(), + ], + ), + ), + ); + } + + Widget _buildProductCard(int index) { + final item = widget.productQuantity[index]; + final totalPrice = (item.product.price ?? 0) * item.quantity + + (item.variant?.priceModifier ?? 0); + + return TweenAnimationBuilder( + tween: Tween(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), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Colors.grey.shade50, + Colors.white, + ], + ), + borderRadius: BorderRadius.circular(16.0), + border: Border.all( + color: Colors.grey.withOpacity(0.1), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + // Enhanced Product Image + Hero( + tag: 'product_${index}', + child: Container( + width: 70, + height: 70, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary.withOpacity(0.2), + AppColors.primary.withOpacity(0.1), + ], + ), + borderRadius: BorderRadius.circular(16.0), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.2), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Icon( + Icons.restaurant_rounded, + color: AppColors.primary, + size: 28, + ), + ), + ), + + const SizedBox(width: 16), + + // Product Details + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.product.name ?? "-", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 6), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + ((item.product.price ?? 0) + + (item.variant?.priceModifier ?? 0)) + .toString() + .currencyFormatRpV2, + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade700, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + + const SizedBox(width: 16), + + // Quantity and Total + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 6, + offset: const Offset(0, 2), + ), + ], + ), + child: Text( + '${item.quantity}x', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + const SizedBox(height: 8), + Text( + totalPrice.toString().currencyFormatRpV2, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + ], + ), + ), + ), + ); + }, + ); + } + + Widget _buildSectionTitle(String title) { + return FadeTransition( + opacity: _fadeInAnimation, + child: Text( + title, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ); + } + + Widget _buildInfoCard({ + required IconData icon, + required String title, + required String value, + required List gradient, + }) { + return ScaleTransition( + scale: _scaleAnimation, + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(20.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: gradient, + ), + borderRadius: BorderRadius.circular(16.0), + border: Border.all( + color: Colors.white.withOpacity(0.3), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + icon, + size: 20, + color: AppColors.primary, + ), + const SizedBox(width: 8), + Text( + title, + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + value, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + ), + ); + } + + Widget _buildInfoRow({ + required IconData icon, + required String label, + required String value, + required double delay, + Color? valueColor, + bool showBadge = false, + }) { + return TweenAnimationBuilder( + tween: Tween(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: [ + Icon( + icon, + size: 18, + color: Colors.grey.shade600, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + label, + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + ), + if (showBadge && valueColor != null) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: valueColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.check_circle, + size: 14, + color: valueColor, + ), + const SizedBox(width: 4), + Text( + value, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: valueColor, + ), + ), + ], + ), + ) + else + Text( + value, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: valueColor ?? Colors.black87, + ), + ), + ], + ), + ), + ); + }, + ); + } + + Widget _buildBottomSection() { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(24.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.grey.shade50, + Colors.white, + ], + ), + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(24), + ), + ), + child: Column( + children: [ + // Total Amount with Enhanced Styling + ScaleTransition( + scale: _scaleAnimation, + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.05), + ], + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColors.primary.withOpacity(0.2), + width: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Total Pembayaran', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Text( + (widget.order.totalAmount ?? 0) + .toString() + .currencyFormatRpV2, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + ), + ), + + const SizedBox(height: 24), + + // Action Buttons with Modern Design + Row( + children: [ + Expanded( + child: TweenAnimationBuilder( + tween: Tween(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( + height: 50, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColors.primary.withOpacity(0.3), + width: 2, + ), + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(14), + child: InkWell( + borderRadius: BorderRadius.circular(14), + onTap: () { + context.push(DashboardPage()); + }, + child: const Center( + child: Text( + 'Kembali ke Beranda', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ), + ), + ), + ), + ); + }, + ), + ), + const SizedBox(width: 16), + Expanded( + child: TweenAnimationBuilder( + tween: Tween(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( + height: 50, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(16), + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: () async { + onPrintRecipt( + context, + order: widget.order, + paymentMethod: widget.paymentMethod, + nominalBayar: widget.paymentMethod == "Cash" + ? widget.nominalBayar + : widget.order.totalAmount ?? 0, + kembalian: widget.nominalBayar - + (widget.order.totalAmount ?? 0), + ); + onPrint( + context, + productQuantity: widget.productQuantity, + order: widget.order, + ); + }, + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.print_rounded, + color: Colors.white, + size: 20, + ), + SizedBox(width: 8), + Text( + 'Cetak Struk', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ], + ), + ), + ), + ), + ); + }, + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildSummaryFooter() { + return SlideTransition( + position: _slideUpAnimation, + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(24.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.grey.shade50, + Colors.white, + ], + ), + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(24), + ), + ), + child: Column( + children: [ + // Decorative Divider + Container( + height: 1, + margin: const EdgeInsets.only(bottom: 20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.transparent, + AppColors.primary.withOpacity(0.3), + Colors.transparent, + ], + ), + ), + ), + + // Subtotal Row + FadeTransition( + opacity: _fadeInAnimation, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Icon( + Icons.shopping_cart_outlined, + size: 16, + color: Colors.grey.shade600, + ), + const SizedBox(width: 8), + Text( + 'Subtotal (${widget.productQuantity.length} items)', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + Text( + (widget.order.totalAmount ?? 0) + .toString() + .currencyFormatRpV2, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + + const SizedBox(height: 16), + + // Total Payment Row with Enhanced Styling + ScaleTransition( + scale: _scaleAnimation, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.05), + ], + ), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: AppColors.primary.withOpacity(0.2), + width: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.payments_rounded, + size: 16, + color: AppColors.primary, + ), + ), + const SizedBox(width: 12), + const Text( + 'Total Pembayaran', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Text( + (widget.order.totalAmount ?? 0) + .toString() + .currencyFormatRpV2, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/success/pages/success_payment_page.dart b/lib/presentation/success/pages/success_payment_page.dart new file mode 100644 index 0000000..5b1d6aa --- /dev/null +++ b/lib/presentation/success/pages/success_payment_page.dart @@ -0,0 +1,983 @@ +import 'package:enaklo_pos/core/constants/colors.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/int_ext.dart'; +import 'package:enaklo_pos/core/extensions/string_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/payment_response_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/sales/blocs/order_loader/order_loader_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SuccessPaymentPage extends StatefulWidget { + final List productQuantity; + final PaymentData payment; + final String paymentMethod; + final int nominalBayar; + const SuccessPaymentPage({ + super.key, + required this.payment, + required this.productQuantity, + required this.paymentMethod, + required this.nominalBayar, + }); + + @override + State createState() => _SuccessPaymentPageState(); +} + +class _SuccessPaymentPageState extends State { + @override + void initState() { + super.initState(); + context + .read() + .add(OrderLoaderEvent.getById(widget.payment.orderId ?? "")); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.background, + body: SafeArea( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + AppColors.primary.withOpacity(0.05), + AppColors.background, + AppColors.background, + ], + ), + ), + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => SizedBox.shrink(), + loading: () => Center( + child: CircularProgressIndicator(), + ), + loadedDetail: (order) { + return Padding( + padding: const EdgeInsets.all(24.0), + child: Row( + children: [ + // Left Panel - Success Message & Order Info + Expanded( + flex: 35, + child: _buildLeftPanel(order), + ), + + const SizedBox(width: 16), + + // Right Panel - Order Details + Expanded( + flex: 65, + child: _buildRightPanel(order), + ), + ], + ), + ); + }, + ); + }, + ), + ), + ), + ); + } + + Widget _buildLeftPanel(Order order) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.1), + blurRadius: 30, + offset: const Offset(0, 10), + ), + ], + ), + child: Column( + children: [ + // Success Header + Container( + width: double.infinity, + padding: const EdgeInsets.all(32.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.05), + ], + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(24), + ), + ), + child: Column( + children: [ + // Success Icon + Container( + padding: const EdgeInsets.all(20.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 20, + offset: const Offset(0, 10), + ), + ], + ), + child: const Icon( + Icons.check_rounded, + size: 48, + color: Colors.white, + ), + ), + + const SizedBox(height: 24), + + // Success Title + const Text( + 'Pesanan Berhasil!', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + + const SizedBox(height: 12), + + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + ), + child: const Text( + 'Pesanan telah diterima dan sedang diproses', + style: TextStyle( + fontSize: 14, + color: AppColors.primary, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + + // Order Information Section + Expanded( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionTitle('Informasi Pesanan'), + const SizedBox(height: 24), + + // Customer Card + _buildInfoCard( + icon: Icons.person_outline_rounded, + title: 'Nama Pelanggan', + value: order.metadata?['customer_name'] ?? "-", + gradient: [ + Colors.blue.withOpacity(0.1), + Colors.purple.withOpacity(0.1), + ], + ), + + const SizedBox(height: 16), + + // Order Details + Expanded( + child: Column( + children: [ + _buildInfoRow( + icon: Icons.receipt_long_outlined, + label: 'No. Pesanan', + value: order.orderNumber ?? "-", + ), + const SizedBox(height: 12), + _buildInfoRow( + icon: Icons.table_restaurant_outlined, + label: 'No. Meja', + value: order.tableNumber ?? "-", + ), + const SizedBox(height: 12), + _buildInfoRow( + icon: Icons.access_time_rounded, + label: 'Waktu', + value: (order.createdAt ?? DateTime.now()) + .toFormattedDate3(), + ), + const SizedBox(height: 12), + _buildInfoRow( + icon: Icons.check_circle_outline, + label: 'Status Pembayaran', + value: 'Lunas', + valueColor: Colors.green, + showBadge: true, + ), + ], + ), + ), + ], + ), + ), + ), + + // Total and Action Buttons + _buildBottomSection(order), + ], + ), + ); + } + + Widget _buildRightPanel(Order order) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 30, + offset: const Offset(0, 10), + ), + ], + ), + child: Column( + children: [ + // Header + Container( + width: double.infinity, + padding: const EdgeInsets.all(24.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.background, + Colors.grey.shade50, + ], + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(24), + ), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12.0), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary.withOpacity(0.2), + AppColors.primary.withOpacity(0.1), + ], + ), + borderRadius: BorderRadius.circular(16.0), + ), + child: Icon( + Icons.receipt_long_rounded, + color: AppColors.primary, + size: 28, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Detail Pesanan', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + 'Ringkasan item yang dipesan', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + ], + ), + ), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Text( + '${widget.productQuantity.length} Items', + style: const TextStyle( + fontSize: 13, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + + // Product List + Expanded( + child: ListView.separated( + padding: const EdgeInsets.all(24.0), + itemCount: widget.productQuantity.length, + separatorBuilder: (context, index) => const SizedBox(height: 12), + itemBuilder: (context, index) { + return _buildProductCard(index); + }, + ), + ), + + // Summary Footer + _buildSummaryFooter(order), + ], + ), + ); + } + + Widget _buildProductCard(int index) { + final item = widget.productQuantity[index]; + final totalPrice = (item.product.price ?? 0) * item.quantity; + + return Container( + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Colors.grey.shade50, + Colors.white, + ], + ), + borderRadius: BorderRadius.circular(16.0), + border: Border.all( + color: Colors.grey.withOpacity(0.1), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + // Product Image + Container( + width: 70, + height: 70, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary.withOpacity(0.2), + AppColors.primary.withOpacity(0.1), + ], + ), + borderRadius: BorderRadius.circular(16.0), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.2), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Icon( + Icons.restaurant_rounded, + color: AppColors.primary, + size: 28, + ), + ), + + const SizedBox(width: 16), + + // Product Details + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.product.name ?? "-", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 6), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + (item.product.price ?? 0).toString().currencyFormatRpV2, + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade700, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + + const SizedBox(width: 16), + + // Quantity and Total + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Container( + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 6, + offset: const Offset(0, 2), + ), + ], + ), + child: Text( + '${item.quantity}x', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + const SizedBox(height: 8), + Text( + totalPrice.toString().currencyFormatRpV2, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildSectionTitle(String title) { + return Text( + title, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ); + } + + Widget _buildInfoCard({ + required IconData icon, + required String title, + required String value, + required List gradient, + }) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(20.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: gradient, + ), + borderRadius: BorderRadius.circular(16.0), + border: Border.all( + color: Colors.white.withOpacity(0.3), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + icon, + size: 20, + color: AppColors.primary, + ), + const SizedBox(width: 8), + Text( + title, + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + value, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + ); + } + + Widget _buildInfoRow({ + required IconData icon, + required String label, + required String value, + Color? valueColor, + bool showBadge = false, + }) { + return Row( + children: [ + Icon( + icon, + size: 18, + color: Colors.grey.shade600, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + label, + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + ), + if (showBadge && valueColor != null) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: valueColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.check_circle, + size: 14, + color: valueColor, + ), + const SizedBox(width: 4), + Text( + value, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: valueColor, + ), + ), + ], + ), + ) + else + Text( + value, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: valueColor ?? Colors.black87, + ), + ), + ], + ); + } + + Widget _buildBottomSection(Order order) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(24.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.grey.shade50, + Colors.white, + ], + ), + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(24), + ), + ), + child: Column( + children: [ + // Total Amount + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.05), + ], + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColors.primary.withOpacity(0.2), + width: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Total Pembayaran', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Text( + widget.nominalBayar.currencyFormatRpV2, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + ), + + const SizedBox(height: 24), + + // Action Buttons + Row( + children: [ + Expanded( + child: Container( + height: 50, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColors.primary.withOpacity(0.3), + width: 2, + ), + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(14), + child: InkWell( + borderRadius: BorderRadius.circular(14), + onTap: () { + context.push(DashboardPage()); + }, + child: const Center( + child: Text( + 'Kembali ke Beranda', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ), + ), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Container( + height: 50, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(16), + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: () async { + onPrintRecipt( + context, + order: order, + paymentMethod: widget.paymentMethod, + nominalBayar: widget.paymentMethod == "Cash" + ? widget.nominalBayar + : order.totalAmount ?? 0, + kembalian: + widget.nominalBayar - (order.totalAmount ?? 0), + ); + }, + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.print_rounded, + color: Colors.white, + size: 20, + ), + SizedBox(width: 8), + Text( + 'Cetak Struk', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ], + ), + ), + ), + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildSummaryFooter(Order order) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(24.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.grey.shade50, + Colors.white, + ], + ), + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(24), + ), + ), + child: Column( + children: [ + // Decorative Divider + Container( + height: 1, + margin: const EdgeInsets.only(bottom: 20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.transparent, + AppColors.primary.withOpacity(0.3), + Colors.transparent, + ], + ), + ), + ), + + // Subtotal Row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Icon( + Icons.shopping_cart_outlined, + size: 16, + color: Colors.grey.shade600, + ), + const SizedBox(width: 8), + Text( + 'Subtotal (${widget.productQuantity.length} items)', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + Text( + widget.nominalBayar.currencyFormatRpV2, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + + const SizedBox(height: 16), + + // Total Payment Row + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.05), + ], + ), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: AppColors.primary.withOpacity(0.2), + width: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.payments_rounded, + size: 16, + color: AppColors.primary, + ), + ), + const SizedBox(width: 12), + const Text( + 'Total Pembayaran', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Text( + widget.nominalBayar.currencyFormatRpV2, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/success/pages/success_save_order_page.dart b/lib/presentation/success/pages/success_save_order_page.dart new file mode 100644 index 0000000..5d11b7f --- /dev/null +++ b/lib/presentation/success/pages/success_save_order_page.dart @@ -0,0 +1,991 @@ +import 'package:enaklo_pos/core/constants/colors.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/string_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/presentation/home/models/product_quantity.dart'; +import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart'; +import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SuccessSaveOrderPage extends StatefulWidget { + final List productQuantity; + final String orderId; + const SuccessSaveOrderPage({ + super.key, + required this.orderId, + required this.productQuantity, + }); + + @override + State createState() => _SuccessSaveOrderPageState(); +} + +class _SuccessSaveOrderPageState extends State { + int totalPrice = 0; + + getPrice() { + setState(() { + totalPrice = widget.productQuantity.fold( + 0, + (previousValue, element) => + previousValue + + (element.product.price! * element.quantity) + + (element.variant?.priceModifier ?? 0), + ); + }); + } + + @override + void initState() { + super.initState(); + context + .read() + .add(OrderLoaderEvent.getById(widget.orderId)); + getPrice(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.background, + body: SafeArea( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + AppColors.primary.withOpacity(0.05), + AppColors.background, + AppColors.background, + ], + ), + ), + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => SizedBox.shrink(), + loading: () => Center( + child: CircularProgressIndicator(), + ), + loadedDetail: (order) { + return Padding( + padding: const EdgeInsets.all(24.0), + child: Row( + children: [ + // Left Panel - Success Message & Order Info + Expanded( + flex: 35, + child: _buildLeftPanel(order), + ), + + const SizedBox(width: 16), + + // Right Panel - Order Details + Expanded( + flex: 65, + child: _buildRightPanel(order), + ), + ], + ), + ); + }, + ); + }, + ), + ), + ), + ); + } + + Widget _buildLeftPanel(Order order) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.1), + blurRadius: 30, + offset: const Offset(0, 10), + ), + ], + ), + child: Column( + children: [ + // Success Header + Container( + width: double.infinity, + padding: const EdgeInsets.all(32.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.05), + ], + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(24), + ), + ), + child: Column( + children: [ + // Success Icon + Container( + padding: const EdgeInsets.all(20.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 20, + offset: const Offset(0, 10), + ), + ], + ), + child: const Icon( + Icons.check_rounded, + size: 48, + color: Colors.white, + ), + ), + + const SizedBox(height: 24), + + // Success Title + const Text( + 'Pesanan Berhasil!', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + + const SizedBox(height: 12), + + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + ), + child: const Text( + 'Pesanan telah diterima dan sedang diproses', + style: TextStyle( + fontSize: 14, + color: AppColors.primary, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + + // Order Information Section + Expanded( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionTitle('Informasi Pesanan'), + const SizedBox(height: 24), + + // Customer Card + _buildInfoCard( + icon: Icons.person_outline_rounded, + title: 'Nama Pelanggan', + value: order.metadata?['customer_name'] ?? "-", + gradient: [ + Colors.blue.withOpacity(0.1), + Colors.purple.withOpacity(0.1), + ], + ), + + const SizedBox(height: 16), + + // Order Details + Expanded( + child: Column( + children: [ + _buildInfoRow( + icon: Icons.receipt_long_outlined, + label: 'No. Pesanan', + value: order.orderNumber ?? "-", + ), + const SizedBox(height: 12), + _buildInfoRow( + icon: Icons.table_restaurant_outlined, + label: 'No. Meja', + value: order.tableNumber ?? "-", + ), + const SizedBox(height: 12), + _buildInfoRow( + icon: Icons.access_time_rounded, + label: 'Waktu', + value: (order.createdAt ?? DateTime.now()) + .toFormattedDate3(), + ), + const SizedBox(height: 12), + _buildInfoRow( + icon: Icons.check_circle_outline, + label: 'Status Pembayaran', + value: 'Lunas', + valueColor: Colors.green, + showBadge: true, + ), + ], + ), + ), + ], + ), + ), + ), + + // Total and Action Buttons + _buildBottomSection(order), + ], + ), + ); + } + + Widget _buildRightPanel(Order order) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 30, + offset: const Offset(0, 10), + ), + ], + ), + child: Column( + children: [ + // Header + Container( + width: double.infinity, + padding: const EdgeInsets.all(24.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.background, + Colors.grey.shade50, + ], + ), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(24), + ), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12.0), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary.withOpacity(0.2), + AppColors.primary.withOpacity(0.1), + ], + ), + borderRadius: BorderRadius.circular(16.0), + ), + child: Icon( + Icons.receipt_long_rounded, + color: AppColors.primary, + size: 28, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Detail Pesanan', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + 'Ringkasan item yang dipesan', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + ], + ), + ), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Text( + '${widget.productQuantity.length} Items', + style: const TextStyle( + fontSize: 13, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + + // Product List + Expanded( + child: ListView.separated( + padding: const EdgeInsets.all(24.0), + itemCount: widget.productQuantity.length, + separatorBuilder: (context, index) => const SizedBox(height: 12), + itemBuilder: (context, index) { + return _buildProductCard(index); + }, + ), + ), + + // Summary Footer + _buildSummaryFooter(order), + ], + ), + ); + } + + Widget _buildProductCard(int index) { + final item = widget.productQuantity[index]; + final totalPrice = (item.product.price ?? 0) * item.quantity + + (item.variant?.priceModifier ?? 0); + + return Container( + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Colors.grey.shade50, + Colors.white, + ], + ), + borderRadius: BorderRadius.circular(16.0), + border: Border.all( + color: Colors.grey.withOpacity(0.1), + width: 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + // Product Image + Container( + width: 70, + height: 70, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary.withOpacity(0.2), + AppColors.primary.withOpacity(0.1), + ], + ), + borderRadius: BorderRadius.circular(16.0), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.2), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Icon( + Icons.restaurant_rounded, + color: AppColors.primary, + size: 28, + ), + ), + + const SizedBox(width: 16), + + // Product Details + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.product.name ?? "-", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 6), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + ((item.product.price ?? 0) + + (item.variant?.priceModifier ?? 0)) + .toString() + .currencyFormatRpV2, + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade700, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + + const SizedBox(width: 16), + + // Quantity and Total + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Container( + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 6, + offset: const Offset(0, 2), + ), + ], + ), + child: Text( + '${item.quantity}x', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + const SizedBox(height: 8), + Text( + totalPrice.toString().currencyFormatRpV2, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildSectionTitle(String title) { + return Text( + title, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ); + } + + Widget _buildInfoCard({ + required IconData icon, + required String title, + required String value, + required List gradient, + }) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(20.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: gradient, + ), + borderRadius: BorderRadius.circular(16.0), + border: Border.all( + color: Colors.white.withOpacity(0.3), + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + icon, + size: 20, + color: AppColors.primary, + ), + const SizedBox(width: 8), + Text( + title, + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + value, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + ); + } + + Widget _buildInfoRow({ + required IconData icon, + required String label, + required String value, + Color? valueColor, + bool showBadge = false, + }) { + return Row( + children: [ + Icon( + icon, + size: 18, + color: Colors.grey.shade600, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + label, + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + ), + if (showBadge && valueColor != null) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: valueColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.check_circle, + size: 14, + color: valueColor, + ), + const SizedBox(width: 4), + Text( + value, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: valueColor, + ), + ), + ], + ), + ) + else + Text( + value, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: valueColor ?? Colors.black87, + ), + ), + ], + ); + } + + Widget _buildBottomSection(Order order) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(24.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.grey.shade50, + Colors.white, + ], + ), + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(24), + ), + ), + child: Column( + children: [ + // Total Amount + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.05), + ], + ), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColors.primary.withOpacity(0.2), + width: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Total Pembayaran', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Text( + totalPrice.toString().currencyFormatRpV2, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + ), + + const SizedBox(height: 24), + + // Action Buttons + Row( + children: [ + Expanded( + child: Container( + height: 50, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: AppColors.primary.withOpacity(0.3), + width: 2, + ), + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(14), + child: InkWell( + borderRadius: BorderRadius.circular(14), + onTap: () { + context.push(DashboardPage()); + }, + child: const Center( + child: Text( + 'Kembali ke Beranda', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ), + ), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Container( + height: 50, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(16), + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: () async { + onPrint( + context, + productQuantity: widget.productQuantity, + order: order, + ); + }, + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.print_rounded, + color: Colors.white, + size: 20, + ), + SizedBox(width: 8), + Text( + 'Cetak Struk', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ], + ), + ), + ), + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildSummaryFooter(Order order) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(24.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.grey.shade50, + Colors.white, + ], + ), + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(24), + ), + ), + child: Column( + children: [ + // Decorative Divider + Container( + height: 1, + margin: const EdgeInsets.only(bottom: 20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.transparent, + AppColors.primary.withOpacity(0.3), + Colors.transparent, + ], + ), + ), + ), + + // Subtotal Row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Icon( + Icons.shopping_cart_outlined, + size: 16, + color: Colors.grey.shade600, + ), + const SizedBox(width: 8), + Text( + 'Subtotal (${widget.productQuantity.length} items)', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + Text( + totalPrice.toString().currencyFormatRpV2, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + + const SizedBox(height: 16), + + // Total Payment Row + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.primary.withOpacity(0.1), + AppColors.primary.withOpacity(0.05), + ], + ), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: AppColors.primary.withOpacity(0.2), + width: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.payments_rounded, + size: 16, + color: AppColors.primary, + ), + ), + const SizedBox(width: 12), + const Text( + 'Total Pembayaran', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.8), + ], + ), + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Text( + totalPrice.toString().currencyFormatRpV2, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/table/blocs/change_position_table/change_position_table_bloc.dart b/lib/presentation/table/blocs/change_position_table/change_position_table_bloc.dart index fdb1b3f..2b4f58d 100644 --- a/lib/presentation/table/blocs/change_position_table/change_position_table_bloc.dart +++ b/lib/presentation/table/blocs/change_position_table/change_position_table_bloc.dart @@ -1,7 +1,7 @@ import 'dart:ui'; import 'package:bloc/bloc.dart'; -import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; +import 'package:enaklo_pos/data/datasources/table_remote_datasource.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'change_position_table_event.dart'; @@ -10,11 +10,19 @@ part 'change_position_table_bloc.freezed.dart'; class ChangePositionTableBloc extends Bloc { - ChangePositionTableBloc() : super(_Initial()) { + final TableRemoteDataSource _tableRemoteDataSource; + ChangePositionTableBloc(this._tableRemoteDataSource) + : super(ChangePositionTableState.initial()) { on<_ChangePositionTable>((event, emit) async { emit(_Loading()); - await ProductLocalDatasource.instance - .changePositionTable(event.tableId, event.position); + final result = await _tableRemoteDataSource.updatePosition( + tableId: event.tableId, + position: event.position, + ); + result.fold( + (l) => emit(_Error(l)), + (r) => emit(_Success('Generate Success')), + ); emit(_Success('Generate Success')); }); } diff --git a/lib/presentation/table/blocs/change_position_table/change_position_table_bloc.freezed.dart b/lib/presentation/table/blocs/change_position_table/change_position_table_bloc.freezed.dart index 9e0cb6d..8799bc5 100644 --- a/lib/presentation/table/blocs/change_position_table/change_position_table_bloc.freezed.dart +++ b/lib/presentation/table/blocs/change_position_table/change_position_table_bloc.freezed.dart @@ -19,19 +19,20 @@ mixin _$ChangePositionTableEvent { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(int tableId, Offset position) changePositionTable, + required TResult Function(String tableId, Offset position) + changePositionTable, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(int tableId, Offset position)? changePositionTable, + TResult? Function(String tableId, Offset position)? changePositionTable, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(int tableId, Offset position)? changePositionTable, + TResult Function(String tableId, Offset position)? changePositionTable, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -120,7 +121,8 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(int tableId, Offset position) changePositionTable, + required TResult Function(String tableId, Offset position) + changePositionTable, }) { return started(); } @@ -129,7 +131,7 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(int tableId, Offset position)? changePositionTable, + TResult? Function(String tableId, Offset position)? changePositionTable, }) { return started?.call(); } @@ -138,7 +140,7 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(int tableId, Offset position)? changePositionTable, + TResult Function(String tableId, Offset position)? changePositionTable, required TResult orElse(), }) { if (started != null) { @@ -189,7 +191,7 @@ abstract class _$$ChangePositionTableImplCopyWith<$Res> { $Res Function(_$ChangePositionTableImpl) then) = __$$ChangePositionTableImplCopyWithImpl<$Res>; @useResult - $Res call({int tableId, Offset position}); + $Res call({String tableId, Offset position}); } /// @nodoc @@ -213,7 +215,7 @@ class __$$ChangePositionTableImplCopyWithImpl<$Res> tableId: null == tableId ? _value.tableId : tableId // ignore: cast_nullable_to_non_nullable - as int, + as String, position: null == position ? _value.position : position // ignore: cast_nullable_to_non_nullable @@ -229,7 +231,7 @@ class _$ChangePositionTableImpl implements _ChangePositionTable { {required this.tableId, required this.position}); @override - final int tableId; + final String tableId; @override final Offset position; @@ -264,7 +266,8 @@ class _$ChangePositionTableImpl implements _ChangePositionTable { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(int tableId, Offset position) changePositionTable, + required TResult Function(String tableId, Offset position) + changePositionTable, }) { return changePositionTable(tableId, position); } @@ -273,7 +276,7 @@ class _$ChangePositionTableImpl implements _ChangePositionTable { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(int tableId, Offset position)? changePositionTable, + TResult? Function(String tableId, Offset position)? changePositionTable, }) { return changePositionTable?.call(tableId, position); } @@ -282,7 +285,7 @@ class _$ChangePositionTableImpl implements _ChangePositionTable { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(int tableId, Offset position)? changePositionTable, + TResult Function(String tableId, Offset position)? changePositionTable, required TResult orElse(), }) { if (changePositionTable != null) { @@ -325,10 +328,10 @@ class _$ChangePositionTableImpl implements _ChangePositionTable { abstract class _ChangePositionTable implements ChangePositionTableEvent { const factory _ChangePositionTable( - {required final int tableId, + {required final String tableId, required final Offset position}) = _$ChangePositionTableImpl; - int get tableId; + String get tableId; Offset get position; /// Create a copy of ChangePositionTableEvent @@ -345,6 +348,7 @@ mixin _$ChangePositionTableState { required TResult Function() initial, required TResult Function() loading, required TResult Function(String message) success, + required TResult Function(String message) error, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -352,6 +356,7 @@ mixin _$ChangePositionTableState { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(String message)? success, + TResult? Function(String message)? error, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -359,6 +364,7 @@ mixin _$ChangePositionTableState { TResult Function()? initial, TResult Function()? loading, TResult Function(String message)? success, + TResult Function(String message)? error, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -367,6 +373,7 @@ mixin _$ChangePositionTableState { required TResult Function(_Initial value) initial, required TResult Function(_Loading value) loading, required TResult Function(_Success value) success, + required TResult Function(_Error value) error, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -374,6 +381,7 @@ mixin _$ChangePositionTableState { TResult? Function(_Initial value)? initial, TResult? Function(_Loading value)? loading, TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -381,6 +389,7 @@ mixin _$ChangePositionTableState { TResult Function(_Initial value)? initial, TResult Function(_Loading value)? loading, TResult Function(_Success value)? success, + TResult Function(_Error value)? error, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -452,6 +461,7 @@ class _$InitialImpl implements _Initial { required TResult Function() initial, required TResult Function() loading, required TResult Function(String message) success, + required TResult Function(String message) error, }) { return initial(); } @@ -462,6 +472,7 @@ class _$InitialImpl implements _Initial { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(String message)? success, + TResult? Function(String message)? error, }) { return initial?.call(); } @@ -472,6 +483,7 @@ class _$InitialImpl implements _Initial { TResult Function()? initial, TResult Function()? loading, TResult Function(String message)? success, + TResult Function(String message)? error, required TResult orElse(), }) { if (initial != null) { @@ -486,6 +498,7 @@ class _$InitialImpl implements _Initial { required TResult Function(_Initial value) initial, required TResult Function(_Loading value) loading, required TResult Function(_Success value) success, + required TResult Function(_Error value) error, }) { return initial(this); } @@ -496,6 +509,7 @@ class _$InitialImpl implements _Initial { TResult? Function(_Initial value)? initial, TResult? Function(_Loading value)? loading, TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, }) { return initial?.call(this); } @@ -506,6 +520,7 @@ class _$InitialImpl implements _Initial { TResult Function(_Initial value)? initial, TResult Function(_Loading value)? loading, TResult Function(_Success value)? success, + TResult Function(_Error value)? error, required TResult orElse(), }) { if (initial != null) { @@ -563,6 +578,7 @@ class _$LoadingImpl implements _Loading { required TResult Function() initial, required TResult Function() loading, required TResult Function(String message) success, + required TResult Function(String message) error, }) { return loading(); } @@ -573,6 +589,7 @@ class _$LoadingImpl implements _Loading { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(String message)? success, + TResult? Function(String message)? error, }) { return loading?.call(); } @@ -583,6 +600,7 @@ class _$LoadingImpl implements _Loading { TResult Function()? initial, TResult Function()? loading, TResult Function(String message)? success, + TResult Function(String message)? error, required TResult orElse(), }) { if (loading != null) { @@ -597,6 +615,7 @@ class _$LoadingImpl implements _Loading { required TResult Function(_Initial value) initial, required TResult Function(_Loading value) loading, required TResult Function(_Success value) success, + required TResult Function(_Error value) error, }) { return loading(this); } @@ -607,6 +626,7 @@ class _$LoadingImpl implements _Loading { TResult? Function(_Initial value)? initial, TResult? Function(_Loading value)? loading, TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, }) { return loading?.call(this); } @@ -617,6 +637,7 @@ class _$LoadingImpl implements _Loading { TResult Function(_Initial value)? initial, TResult Function(_Loading value)? loading, TResult Function(_Success value)? success, + TResult Function(_Error value)? error, required TResult orElse(), }) { if (loading != null) { @@ -701,6 +722,7 @@ class _$SuccessImpl implements _Success { required TResult Function() initial, required TResult Function() loading, required TResult Function(String message) success, + required TResult Function(String message) error, }) { return success(message); } @@ -711,6 +733,7 @@ class _$SuccessImpl implements _Success { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(String message)? success, + TResult? Function(String message)? error, }) { return success?.call(message); } @@ -721,6 +744,7 @@ class _$SuccessImpl implements _Success { TResult Function()? initial, TResult Function()? loading, TResult Function(String message)? success, + TResult Function(String message)? error, required TResult orElse(), }) { if (success != null) { @@ -735,6 +759,7 @@ class _$SuccessImpl implements _Success { required TResult Function(_Initial value) initial, required TResult Function(_Loading value) loading, required TResult Function(_Success value) success, + required TResult Function(_Error value) error, }) { return success(this); } @@ -745,6 +770,7 @@ class _$SuccessImpl implements _Success { TResult? Function(_Initial value)? initial, TResult? Function(_Loading value)? loading, TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, }) { return success?.call(this); } @@ -755,6 +781,7 @@ class _$SuccessImpl implements _Success { TResult Function(_Initial value)? initial, TResult Function(_Loading value)? loading, TResult Function(_Success value)? success, + TResult Function(_Error value)? error, required TResult orElse(), }) { if (success != null) { @@ -775,3 +802,155 @@ abstract class _Success implements ChangePositionTableState { _$$SuccessImplCopyWith<_$SuccessImpl> 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 _$ChangePositionTableStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of ChangePositionTableState + /// 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 'ChangePositionTableState.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 ChangePositionTableState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(String message) success, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(String message)? success, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(String message)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements ChangePositionTableState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of ChangePositionTableState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/table/blocs/change_position_table/change_position_table_event.dart b/lib/presentation/table/blocs/change_position_table/change_position_table_event.dart index 3b58a34..431cdcd 100644 --- a/lib/presentation/table/blocs/change_position_table/change_position_table_event.dart +++ b/lib/presentation/table/blocs/change_position_table/change_position_table_event.dart @@ -4,7 +4,7 @@ part of 'change_position_table_bloc.dart'; class ChangePositionTableEvent with _$ChangePositionTableEvent { const factory ChangePositionTableEvent.started() = _Started; const factory ChangePositionTableEvent.changePositionTable({ - required int tableId, + required String tableId, required Offset position, }) = _ChangePositionTable; } diff --git a/lib/presentation/table/blocs/change_position_table/change_position_table_state.dart b/lib/presentation/table/blocs/change_position_table/change_position_table_state.dart index 5fb037b..0cd917d 100644 --- a/lib/presentation/table/blocs/change_position_table/change_position_table_state.dart +++ b/lib/presentation/table/blocs/change_position_table/change_position_table_state.dart @@ -7,4 +7,5 @@ class ChangePositionTableState with _$ChangePositionTableState { const factory ChangePositionTableState.loading() = _Loading; const factory ChangePositionTableState.success(String message) = _Success; + const factory ChangePositionTableState.error(String message) = _Error; } diff --git a/lib/presentation/table/blocs/create_table/create_table_bloc.dart b/lib/presentation/table/blocs/create_table/create_table_bloc.dart index ecdd58c..8613d7d 100644 --- a/lib/presentation/table/blocs/create_table/create_table_bloc.dart +++ b/lib/presentation/table/blocs/create_table/create_table_bloc.dart @@ -1,7 +1,5 @@ -import 'dart:ui'; - import 'package:bloc/bloc.dart'; -import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; +import 'package:enaklo_pos/data/datasources/table_remote_datasource.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'create_table_event.dart'; @@ -9,12 +7,19 @@ part 'create_table_state.dart'; part 'create_table_bloc.freezed.dart'; class CreateTableBloc extends Bloc { - CreateTableBloc() : super(_Initial()) { + final TableRemoteDataSource _tableRemoteDataSource; + CreateTableBloc(this._tableRemoteDataSource) + : super(CreateTableState.initial()) { on<_CreateTable>((event, emit) async { emit(_Loading()); - await ProductLocalDatasource.instance - .createTableManagement(event.tableName, event.position); - emit(_Success('Create Table Success')); + final result = await _tableRemoteDataSource.createTable( + tableName: event.tableName, + capacity: event.capacity, + location: event.location, + ); + + result.fold((l) => emit(_Error(l)), + (r) => emit(_Success('Meja berhasil dibuat'))); }); } } diff --git a/lib/presentation/table/blocs/create_table/create_table_bloc.freezed.dart b/lib/presentation/table/blocs/create_table/create_table_bloc.freezed.dart index 707ba61..821fef8 100644 --- a/lib/presentation/table/blocs/create_table/create_table_bloc.freezed.dart +++ b/lib/presentation/table/blocs/create_table/create_table_bloc.freezed.dart @@ -19,19 +19,22 @@ mixin _$CreateTableEvent { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(String tableName, Offset position) createTable, + required TResult Function(String tableName, int capacity, String location) + createTable, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(String tableName, Offset position)? createTable, + TResult? Function(String tableName, int capacity, String location)? + createTable, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(String tableName, Offset position)? createTable, + TResult Function(String tableName, int capacity, String location)? + createTable, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -119,7 +122,8 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(String tableName, Offset position) createTable, + required TResult Function(String tableName, int capacity, String location) + createTable, }) { return started(); } @@ -128,7 +132,8 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(String tableName, Offset position)? createTable, + TResult? Function(String tableName, int capacity, String location)? + createTable, }) { return started?.call(); } @@ -137,7 +142,8 @@ class _$StartedImpl implements _Started { @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(String tableName, Offset position)? createTable, + TResult Function(String tableName, int capacity, String location)? + createTable, required TResult orElse(), }) { if (started != null) { @@ -188,7 +194,7 @@ abstract class _$$CreateTableImplCopyWith<$Res> { _$CreateTableImpl value, $Res Function(_$CreateTableImpl) then) = __$$CreateTableImplCopyWithImpl<$Res>; @useResult - $Res call({String tableName, Offset position}); + $Res call({String tableName, int capacity, String location}); } /// @nodoc @@ -205,17 +211,22 @@ class __$$CreateTableImplCopyWithImpl<$Res> @override $Res call({ Object? tableName = null, - Object? position = null, + Object? capacity = null, + Object? location = null, }) { return _then(_$CreateTableImpl( - null == tableName + tableName: null == tableName ? _value.tableName : tableName // ignore: cast_nullable_to_non_nullable as String, - null == position - ? _value.position - : position // ignore: cast_nullable_to_non_nullable - as Offset, + capacity: null == capacity + ? _value.capacity + : capacity // ignore: cast_nullable_to_non_nullable + as int, + location: null == location + ? _value.location + : location // ignore: cast_nullable_to_non_nullable + as String, )); } } @@ -223,16 +234,21 @@ class __$$CreateTableImplCopyWithImpl<$Res> /// @nodoc class _$CreateTableImpl implements _CreateTable { - const _$CreateTableImpl(this.tableName, this.position); + const _$CreateTableImpl( + {required this.tableName, + required this.capacity, + required this.location}); @override final String tableName; @override - final Offset position; + final int capacity; + @override + final String location; @override String toString() { - return 'CreateTableEvent.createTable(tableName: $tableName, position: $position)'; + return 'CreateTableEvent.createTable(tableName: $tableName, capacity: $capacity, location: $location)'; } @override @@ -242,12 +258,14 @@ class _$CreateTableImpl implements _CreateTable { other is _$CreateTableImpl && (identical(other.tableName, tableName) || other.tableName == tableName) && - (identical(other.position, position) || - other.position == position)); + (identical(other.capacity, capacity) || + other.capacity == capacity) && + (identical(other.location, location) || + other.location == location)); } @override - int get hashCode => Object.hash(runtimeType, tableName, position); + int get hashCode => Object.hash(runtimeType, tableName, capacity, location); /// Create a copy of CreateTableEvent /// with the given fields replaced by the non-null parameter values. @@ -261,29 +279,32 @@ class _$CreateTableImpl implements _CreateTable { @optionalTypeArgs TResult when({ required TResult Function() started, - required TResult Function(String tableName, Offset position) createTable, + required TResult Function(String tableName, int capacity, String location) + createTable, }) { - return createTable(tableName, position); + return createTable(tableName, capacity, location); } @override @optionalTypeArgs TResult? whenOrNull({ TResult? Function()? started, - TResult? Function(String tableName, Offset position)? createTable, + TResult? Function(String tableName, int capacity, String location)? + createTable, }) { - return createTable?.call(tableName, position); + return createTable?.call(tableName, capacity, location); } @override @optionalTypeArgs TResult maybeWhen({ TResult Function()? started, - TResult Function(String tableName, Offset position)? createTable, + TResult Function(String tableName, int capacity, String location)? + createTable, required TResult orElse(), }) { if (createTable != null) { - return createTable(tableName, position); + return createTable(tableName, capacity, location); } return orElse(); } @@ -321,11 +342,14 @@ class _$CreateTableImpl implements _CreateTable { } abstract class _CreateTable implements CreateTableEvent { - const factory _CreateTable(final String tableName, final Offset position) = - _$CreateTableImpl; + const factory _CreateTable( + {required final String tableName, + required final int capacity, + required final String location}) = _$CreateTableImpl; String get tableName; - Offset get position; + int get capacity; + String get location; /// Create a copy of CreateTableEvent /// with the given fields replaced by the non-null parameter values. @@ -341,6 +365,7 @@ mixin _$CreateTableState { required TResult Function() initial, required TResult Function() loading, required TResult Function(String message) success, + required TResult Function(String message) error, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -348,6 +373,7 @@ mixin _$CreateTableState { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(String message)? success, + TResult? Function(String message)? error, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -355,6 +381,7 @@ mixin _$CreateTableState { TResult Function()? initial, TResult Function()? loading, TResult Function(String message)? success, + TResult Function(String message)? error, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -363,6 +390,7 @@ mixin _$CreateTableState { required TResult Function(_Initial value) initial, required TResult Function(_Loading value) loading, required TResult Function(_Success value) success, + required TResult Function(_Error value) error, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -370,6 +398,7 @@ mixin _$CreateTableState { TResult? Function(_Initial value)? initial, TResult? Function(_Loading value)? loading, TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -377,6 +406,7 @@ mixin _$CreateTableState { TResult Function(_Initial value)? initial, TResult Function(_Loading value)? loading, TResult Function(_Success value)? success, + TResult Function(_Error value)? error, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -447,6 +477,7 @@ class _$InitialImpl implements _Initial { required TResult Function() initial, required TResult Function() loading, required TResult Function(String message) success, + required TResult Function(String message) error, }) { return initial(); } @@ -457,6 +488,7 @@ class _$InitialImpl implements _Initial { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(String message)? success, + TResult? Function(String message)? error, }) { return initial?.call(); } @@ -467,6 +499,7 @@ class _$InitialImpl implements _Initial { TResult Function()? initial, TResult Function()? loading, TResult Function(String message)? success, + TResult Function(String message)? error, required TResult orElse(), }) { if (initial != null) { @@ -481,6 +514,7 @@ class _$InitialImpl implements _Initial { required TResult Function(_Initial value) initial, required TResult Function(_Loading value) loading, required TResult Function(_Success value) success, + required TResult Function(_Error value) error, }) { return initial(this); } @@ -491,6 +525,7 @@ class _$InitialImpl implements _Initial { TResult? Function(_Initial value)? initial, TResult? Function(_Loading value)? loading, TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, }) { return initial?.call(this); } @@ -501,6 +536,7 @@ class _$InitialImpl implements _Initial { TResult Function(_Initial value)? initial, TResult Function(_Loading value)? loading, TResult Function(_Success value)? success, + TResult Function(_Error value)? error, required TResult orElse(), }) { if (initial != null) { @@ -558,6 +594,7 @@ class _$LoadingImpl implements _Loading { required TResult Function() initial, required TResult Function() loading, required TResult Function(String message) success, + required TResult Function(String message) error, }) { return loading(); } @@ -568,6 +605,7 @@ class _$LoadingImpl implements _Loading { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(String message)? success, + TResult? Function(String message)? error, }) { return loading?.call(); } @@ -578,6 +616,7 @@ class _$LoadingImpl implements _Loading { TResult Function()? initial, TResult Function()? loading, TResult Function(String message)? success, + TResult Function(String message)? error, required TResult orElse(), }) { if (loading != null) { @@ -592,6 +631,7 @@ class _$LoadingImpl implements _Loading { required TResult Function(_Initial value) initial, required TResult Function(_Loading value) loading, required TResult Function(_Success value) success, + required TResult Function(_Error value) error, }) { return loading(this); } @@ -602,6 +642,7 @@ class _$LoadingImpl implements _Loading { TResult? Function(_Initial value)? initial, TResult? Function(_Loading value)? loading, TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, }) { return loading?.call(this); } @@ -612,6 +653,7 @@ class _$LoadingImpl implements _Loading { TResult Function(_Initial value)? initial, TResult Function(_Loading value)? loading, TResult Function(_Success value)? success, + TResult Function(_Error value)? error, required TResult orElse(), }) { if (loading != null) { @@ -696,6 +738,7 @@ class _$SuccessImpl implements _Success { required TResult Function() initial, required TResult Function() loading, required TResult Function(String message) success, + required TResult Function(String message) error, }) { return success(message); } @@ -706,6 +749,7 @@ class _$SuccessImpl implements _Success { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(String message)? success, + TResult? Function(String message)? error, }) { return success?.call(message); } @@ -716,6 +760,7 @@ class _$SuccessImpl implements _Success { TResult Function()? initial, TResult Function()? loading, TResult Function(String message)? success, + TResult Function(String message)? error, required TResult orElse(), }) { if (success != null) { @@ -730,6 +775,7 @@ class _$SuccessImpl implements _Success { required TResult Function(_Initial value) initial, required TResult Function(_Loading value) loading, required TResult Function(_Success value) success, + required TResult Function(_Error value) error, }) { return success(this); } @@ -740,6 +786,7 @@ class _$SuccessImpl implements _Success { TResult? Function(_Initial value)? initial, TResult? Function(_Loading value)? loading, TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, }) { return success?.call(this); } @@ -750,6 +797,7 @@ class _$SuccessImpl implements _Success { TResult Function(_Initial value)? initial, TResult Function(_Loading value)? loading, TResult Function(_Success value)? success, + TResult Function(_Error value)? error, required TResult orElse(), }) { if (success != null) { @@ -770,3 +818,155 @@ abstract class _Success implements CreateTableState { _$$SuccessImplCopyWith<_$SuccessImpl> 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 _$CreateTableStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of CreateTableState + /// 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 'CreateTableState.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 CreateTableState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(String message) success, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(String message)? success, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(String message)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements CreateTableState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of CreateTableState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/table/blocs/create_table/create_table_event.dart b/lib/presentation/table/blocs/create_table/create_table_event.dart index 1278f35..08bde88 100644 --- a/lib/presentation/table/blocs/create_table/create_table_event.dart +++ b/lib/presentation/table/blocs/create_table/create_table_event.dart @@ -3,6 +3,9 @@ part of 'create_table_bloc.dart'; @freezed class CreateTableEvent with _$CreateTableEvent { const factory CreateTableEvent.started() = _Started; - const factory CreateTableEvent.createTable( - String tableName, Offset position) = _CreateTable; + const factory CreateTableEvent.createTable({ + required String tableName, + required int capacity, + required String location, + }) = _CreateTable; } diff --git a/lib/presentation/table/blocs/create_table/create_table_state.dart b/lib/presentation/table/blocs/create_table/create_table_state.dart index e697088..e3c4bce 100644 --- a/lib/presentation/table/blocs/create_table/create_table_state.dart +++ b/lib/presentation/table/blocs/create_table/create_table_state.dart @@ -7,4 +7,5 @@ class CreateTableState with _$CreateTableState { const factory CreateTableState.loading() = _Loading; // success const factory CreateTableState.success(String message) = _Success; + const factory CreateTableState.error(String message) = _Error; } diff --git a/lib/presentation/table/blocs/get_table/get_table_bloc.dart b/lib/presentation/table/blocs/get_table/get_table_bloc.dart index 84c3d76..71f35bd 100644 --- a/lib/presentation/table/blocs/get_table/get_table_bloc.dart +++ b/lib/presentation/table/blocs/get_table/get_table_bloc.dart @@ -1,3 +1,4 @@ +import 'package:enaklo_pos/data/datasources/table_remote_datasource.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; import 'package:enaklo_pos/data/models/response/table_model.dart'; @@ -8,11 +9,17 @@ part 'get_table_state.dart'; part 'get_table_bloc.freezed.dart'; class GetTableBloc extends Bloc { - GetTableBloc() : super(_Initial()) { + final TableRemoteDataSource _tableRemoteDataSource; + GetTableBloc(this._tableRemoteDataSource) : super(GetTableState.initial()) { on<_GetTables>((event, emit) async { emit(_Loading()); - final tables = await ProductLocalDatasource.instance.getAllTable(); - emit(_Success(tables)); + final tables = await _tableRemoteDataSource.getTable(); + tables.fold( + (l) => emit(_Error(l)), + (r) => emit( + _Success(r.data!.tables!), + ), + ); }); } } diff --git a/lib/presentation/table/blocs/get_table/get_table_bloc.freezed.dart b/lib/presentation/table/blocs/get_table/get_table_bloc.freezed.dart index 332d5f8..ccfa93c 100644 --- a/lib/presentation/table/blocs/get_table/get_table_bloc.freezed.dart +++ b/lib/presentation/table/blocs/get_table/get_table_bloc.freezed.dart @@ -294,6 +294,7 @@ mixin _$GetTableState { required TResult Function() initial, required TResult Function() loading, required TResult Function(List tables) success, + required TResult Function(String message) error, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -301,6 +302,7 @@ mixin _$GetTableState { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(List tables)? success, + TResult? Function(String message)? error, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -308,6 +310,7 @@ mixin _$GetTableState { TResult Function()? initial, TResult Function()? loading, TResult Function(List tables)? success, + TResult Function(String message)? error, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -316,6 +319,7 @@ mixin _$GetTableState { required TResult Function(_Initial value) initial, required TResult Function(_Loading value) loading, required TResult Function(_Success value) success, + required TResult Function(_Error value) error, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -323,6 +327,7 @@ mixin _$GetTableState { TResult? Function(_Initial value)? initial, TResult? Function(_Loading value)? loading, TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -330,6 +335,7 @@ mixin _$GetTableState { TResult Function(_Initial value)? initial, TResult Function(_Loading value)? loading, TResult Function(_Success value)? success, + TResult Function(_Error value)? error, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -400,6 +406,7 @@ class _$InitialImpl implements _Initial { required TResult Function() initial, required TResult Function() loading, required TResult Function(List tables) success, + required TResult Function(String message) error, }) { return initial(); } @@ -410,6 +417,7 @@ class _$InitialImpl implements _Initial { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(List tables)? success, + TResult? Function(String message)? error, }) { return initial?.call(); } @@ -420,6 +428,7 @@ class _$InitialImpl implements _Initial { TResult Function()? initial, TResult Function()? loading, TResult Function(List tables)? success, + TResult Function(String message)? error, required TResult orElse(), }) { if (initial != null) { @@ -434,6 +443,7 @@ class _$InitialImpl implements _Initial { required TResult Function(_Initial value) initial, required TResult Function(_Loading value) loading, required TResult Function(_Success value) success, + required TResult Function(_Error value) error, }) { return initial(this); } @@ -444,6 +454,7 @@ class _$InitialImpl implements _Initial { TResult? Function(_Initial value)? initial, TResult? Function(_Loading value)? loading, TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, }) { return initial?.call(this); } @@ -454,6 +465,7 @@ class _$InitialImpl implements _Initial { TResult Function(_Initial value)? initial, TResult Function(_Loading value)? loading, TResult Function(_Success value)? success, + TResult Function(_Error value)? error, required TResult orElse(), }) { if (initial != null) { @@ -511,6 +523,7 @@ class _$LoadingImpl implements _Loading { required TResult Function() initial, required TResult Function() loading, required TResult Function(List tables) success, + required TResult Function(String message) error, }) { return loading(); } @@ -521,6 +534,7 @@ class _$LoadingImpl implements _Loading { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(List tables)? success, + TResult? Function(String message)? error, }) { return loading?.call(); } @@ -531,6 +545,7 @@ class _$LoadingImpl implements _Loading { TResult Function()? initial, TResult Function()? loading, TResult Function(List tables)? success, + TResult Function(String message)? error, required TResult orElse(), }) { if (loading != null) { @@ -545,6 +560,7 @@ class _$LoadingImpl implements _Loading { required TResult Function(_Initial value) initial, required TResult Function(_Loading value) loading, required TResult Function(_Success value) success, + required TResult Function(_Error value) error, }) { return loading(this); } @@ -555,6 +571,7 @@ class _$LoadingImpl implements _Loading { TResult? Function(_Initial value)? initial, TResult? Function(_Loading value)? loading, TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, }) { return loading?.call(this); } @@ -565,6 +582,7 @@ class _$LoadingImpl implements _Loading { TResult Function(_Initial value)? initial, TResult Function(_Loading value)? loading, TResult Function(_Success value)? success, + TResult Function(_Error value)? error, required TResult orElse(), }) { if (loading != null) { @@ -655,6 +673,7 @@ class _$SuccessImpl implements _Success { required TResult Function() initial, required TResult Function() loading, required TResult Function(List tables) success, + required TResult Function(String message) error, }) { return success(tables); } @@ -665,6 +684,7 @@ class _$SuccessImpl implements _Success { TResult? Function()? initial, TResult? Function()? loading, TResult? Function(List tables)? success, + TResult? Function(String message)? error, }) { return success?.call(tables); } @@ -675,6 +695,7 @@ class _$SuccessImpl implements _Success { TResult Function()? initial, TResult Function()? loading, TResult Function(List tables)? success, + TResult Function(String message)? error, required TResult orElse(), }) { if (success != null) { @@ -689,6 +710,7 @@ class _$SuccessImpl implements _Success { required TResult Function(_Initial value) initial, required TResult Function(_Loading value) loading, required TResult Function(_Success value) success, + required TResult Function(_Error value) error, }) { return success(this); } @@ -699,6 +721,7 @@ class _$SuccessImpl implements _Success { TResult? Function(_Initial value)? initial, TResult? Function(_Loading value)? loading, TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, }) { return success?.call(this); } @@ -709,6 +732,7 @@ class _$SuccessImpl implements _Success { TResult Function(_Initial value)? initial, TResult Function(_Loading value)? loading, TResult Function(_Success value)? success, + TResult Function(_Error value)? error, required TResult orElse(), }) { if (success != null) { @@ -729,3 +753,155 @@ abstract class _Success implements GetTableState { _$$SuccessImplCopyWith<_$SuccessImpl> 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 _$GetTableStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of GetTableState + /// 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 'GetTableState.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 GetTableState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List tables) success, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List tables)? success, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List tables)? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements GetTableState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of GetTableState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/table/blocs/get_table/get_table_state.dart b/lib/presentation/table/blocs/get_table/get_table_state.dart index ef24094..e0ac76c 100644 --- a/lib/presentation/table/blocs/get_table/get_table_state.dart +++ b/lib/presentation/table/blocs/get_table/get_table_state.dart @@ -5,4 +5,5 @@ class GetTableState with _$GetTableState { const factory GetTableState.initial() = _Initial; const factory GetTableState.loading() = _Loading; const factory GetTableState.success(List tables) = _Success; + const factory GetTableState.error(String message) = _Error; } diff --git a/lib/presentation/table/blocs/transfer_table/transfer_table_bloc.dart b/lib/presentation/table/blocs/transfer_table/transfer_table_bloc.dart new file mode 100644 index 0000000..d9109a4 --- /dev/null +++ b/lib/presentation/table/blocs/transfer_table/transfer_table_bloc.dart @@ -0,0 +1,26 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/table_remote_datasource.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'transfer_table_event.dart'; +part 'transfer_table_state.dart'; +part 'transfer_table_bloc.freezed.dart'; + +class TransferTableBloc extends Bloc { + final TableRemoteDataSource _dataSource; + TransferTableBloc(this._dataSource) : super(TransferTableState.initial()) { + on<_TransferTable>((event, emit) async { + emit(_Loading()); + + final result = await _dataSource.transferTable( + fromTableId: event.fromTableId, + toTableId: event.toTableId, + ); + + result.fold( + (l) => emit(_Error(l)), + (r) => emit(_Success()), + ); + }); + } +} diff --git a/lib/presentation/table/blocs/transfer_table/transfer_table_bloc.freezed.dart b/lib/presentation/table/blocs/transfer_table/transfer_table_bloc.freezed.dart new file mode 100644 index 0000000..73a1962 --- /dev/null +++ b/lib/presentation/table/blocs/transfer_table/transfer_table_bloc.freezed.dart @@ -0,0 +1,832 @@ +// 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 'transfer_table_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$TransferTableEvent { + String get fromTableId => throw _privateConstructorUsedError; + String get toTableId => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(String fromTableId, String toTableId) + transferTable, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String fromTableId, String toTableId)? transferTable, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String fromTableId, String toTableId)? transferTable, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_TransferTable value) transferTable, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_TransferTable value)? transferTable, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_TransferTable value)? transferTable, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Create a copy of TransferTableEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $TransferTableEventCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TransferTableEventCopyWith<$Res> { + factory $TransferTableEventCopyWith( + TransferTableEvent value, $Res Function(TransferTableEvent) then) = + _$TransferTableEventCopyWithImpl<$Res, TransferTableEvent>; + @useResult + $Res call({String fromTableId, String toTableId}); +} + +/// @nodoc +class _$TransferTableEventCopyWithImpl<$Res, $Val extends TransferTableEvent> + implements $TransferTableEventCopyWith<$Res> { + _$TransferTableEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of TransferTableEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? fromTableId = null, + Object? toTableId = null, + }) { + return _then(_value.copyWith( + fromTableId: null == fromTableId + ? _value.fromTableId + : fromTableId // ignore: cast_nullable_to_non_nullable + as String, + toTableId: null == toTableId + ? _value.toTableId + : toTableId // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TransferTableImplCopyWith<$Res> + implements $TransferTableEventCopyWith<$Res> { + factory _$$TransferTableImplCopyWith( + _$TransferTableImpl value, $Res Function(_$TransferTableImpl) then) = + __$$TransferTableImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String fromTableId, String toTableId}); +} + +/// @nodoc +class __$$TransferTableImplCopyWithImpl<$Res> + extends _$TransferTableEventCopyWithImpl<$Res, _$TransferTableImpl> + implements _$$TransferTableImplCopyWith<$Res> { + __$$TransferTableImplCopyWithImpl( + _$TransferTableImpl _value, $Res Function(_$TransferTableImpl) _then) + : super(_value, _then); + + /// Create a copy of TransferTableEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? fromTableId = null, + Object? toTableId = null, + }) { + return _then(_$TransferTableImpl( + fromTableId: null == fromTableId + ? _value.fromTableId + : fromTableId // ignore: cast_nullable_to_non_nullable + as String, + toTableId: null == toTableId + ? _value.toTableId + : toTableId // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$TransferTableImpl implements _TransferTable { + const _$TransferTableImpl( + {required this.fromTableId, required this.toTableId}); + + @override + final String fromTableId; + @override + final String toTableId; + + @override + String toString() { + return 'TransferTableEvent.transferTable(fromTableId: $fromTableId, toTableId: $toTableId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TransferTableImpl && + (identical(other.fromTableId, fromTableId) || + other.fromTableId == fromTableId) && + (identical(other.toTableId, toTableId) || + other.toTableId == toTableId)); + } + + @override + int get hashCode => Object.hash(runtimeType, fromTableId, toTableId); + + /// Create a copy of TransferTableEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$TransferTableImplCopyWith<_$TransferTableImpl> get copyWith => + __$$TransferTableImplCopyWithImpl<_$TransferTableImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String fromTableId, String toTableId) + transferTable, + }) { + return transferTable(fromTableId, toTableId); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String fromTableId, String toTableId)? transferTable, + }) { + return transferTable?.call(fromTableId, toTableId); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String fromTableId, String toTableId)? transferTable, + required TResult orElse(), + }) { + if (transferTable != null) { + return transferTable(fromTableId, toTableId); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_TransferTable value) transferTable, + }) { + return transferTable(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_TransferTable value)? transferTable, + }) { + return transferTable?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_TransferTable value)? transferTable, + required TResult orElse(), + }) { + if (transferTable != null) { + return transferTable(this); + } + return orElse(); + } +} + +abstract class _TransferTable implements TransferTableEvent { + const factory _TransferTable( + {required final String fromTableId, + required final String toTableId}) = _$TransferTableImpl; + + @override + String get fromTableId; + @override + String get toTableId; + + /// Create a copy of TransferTableEvent + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$TransferTableImplCopyWith<_$TransferTableImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$TransferTableState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TransferTableStateCopyWith<$Res> { + factory $TransferTableStateCopyWith( + TransferTableState value, $Res Function(TransferTableState) then) = + _$TransferTableStateCopyWithImpl<$Res, TransferTableState>; +} + +/// @nodoc +class _$TransferTableStateCopyWithImpl<$Res, $Val extends TransferTableState> + implements $TransferTableStateCopyWith<$Res> { + _$TransferTableStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of TransferTableState + /// 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 _$TransferTableStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of TransferTableState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'TransferTableState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements TransferTableState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$TransferTableStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of TransferTableState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'TransferTableState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class _Loading implements TransferTableState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$SuccessImplCopyWith<$Res> { + factory _$$SuccessImplCopyWith( + _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = + __$$SuccessImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SuccessImplCopyWithImpl<$Res> + extends _$TransferTableStateCopyWithImpl<$Res, _$SuccessImpl> + implements _$$SuccessImplCopyWith<$Res> { + __$$SuccessImplCopyWithImpl( + _$SuccessImpl _value, $Res Function(_$SuccessImpl) _then) + : super(_value, _then); + + /// Create a copy of TransferTableState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SuccessImpl implements _Success { + const _$SuccessImpl(); + + @override + String toString() { + return 'TransferTableState.success()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SuccessImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return success(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return success?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return success(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return success?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(this); + } + return orElse(); + } +} + +abstract class _Success implements TransferTableState { + const factory _Success() = _$SuccessImpl; +} + +/// @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 _$TransferTableStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of TransferTableState + /// 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 'TransferTableState.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 TransferTableState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements TransferTableState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of TransferTableState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/table/blocs/transfer_table/transfer_table_event.dart b/lib/presentation/table/blocs/transfer_table/transfer_table_event.dart new file mode 100644 index 0000000..c8e1a1c --- /dev/null +++ b/lib/presentation/table/blocs/transfer_table/transfer_table_event.dart @@ -0,0 +1,8 @@ +part of 'transfer_table_bloc.dart'; + +@freezed +class TransferTableEvent with _$TransferTableEvent { + const factory TransferTableEvent.transferTable( + {required String fromTableId, + required String toTableId}) = _TransferTable; +} diff --git a/lib/presentation/table/blocs/transfer_table/transfer_table_state.dart b/lib/presentation/table/blocs/transfer_table/transfer_table_state.dart new file mode 100644 index 0000000..57411ea --- /dev/null +++ b/lib/presentation/table/blocs/transfer_table/transfer_table_state.dart @@ -0,0 +1,9 @@ +part of 'transfer_table_bloc.dart'; + +@freezed +class TransferTableState with _$TransferTableState { + const factory TransferTableState.initial() = _Initial; + const factory TransferTableState.loading() = _Loading; + const factory TransferTableState.success() = _Success; + const factory TransferTableState.error(String message) = _Error; +} diff --git a/lib/presentation/table/dialogs/form_table_new_dialog.dart b/lib/presentation/table/dialogs/form_table_new_dialog.dart new file mode 100644 index 0000000..8996adf --- /dev/null +++ b/lib/presentation/table/dialogs/form_table_new_dialog.dart @@ -0,0 +1,86 @@ +import 'package:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/components/custom_text_field.dart'; +import 'package:enaklo_pos/core/components/flushbar.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/presentation/table/blocs/create_table/create_table_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class FormTableNewDialog extends StatefulWidget { + const FormTableNewDialog({super.key}); + + @override + State createState() => _FormTableNewDialogState(); +} + +class _FormTableNewDialogState extends State { + TextEditingController tableNameController = TextEditingController(); + TextEditingController capacityController = TextEditingController(); + TextEditingController locationController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Tambah Meja', + subtitle: 'Silahkan isi data meja', + contentPadding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0), + child: Column( + children: [ + CustomTextField( + controller: tableNameController, + label: 'Nama Meja', + ), + SpaceHeight(16), + CustomTextField( + controller: capacityController, + label: 'Kapasitas', + keyboardType: TextInputType.number, + ), + SpaceHeight(16), + CustomTextField( + controller: locationController, + label: 'Lokasi', + ), + SpaceHeight(24), + BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: (message) { + context.pop(); + AppFlushbar.showSuccess(context, message); + }, + error: (message) { + AppFlushbar.showError(context, message); + }, + ); + }, + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Button.filled( + onPressed: () { + context.read().add( + CreateTableEvent.createTable( + capacity: int.parse(capacityController.text), + location: locationController.text, + tableName: tableNameController.text, + ), + ); + }, + label: 'Simpan', + ), + loading: () => + Center(child: const CircularProgressIndicator()), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/table/dialogs/transfer_table_dialog.dart b/lib/presentation/table/dialogs/transfer_table_dialog.dart new file mode 100644 index 0000000..7a6e228 --- /dev/null +++ b/lib/presentation/table/dialogs/transfer_table_dialog.dart @@ -0,0 +1,319 @@ +import 'dart:developer'; + +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/components/custom_modal_dialog.dart'; +import 'package:enaklo_pos/core/components/flushbar.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.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/presentation/home/bloc/get_table_status/get_table_status_bloc.dart'; +import 'package:enaklo_pos/presentation/table/blocs/transfer_table/transfer_table_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class TransferTableDialog extends StatefulWidget { + final TableModel fromTable; + const TransferTableDialog({ + super.key, + required this.fromTable, + }); + + @override + State createState() => _TransferTableDialogState(); +} + +class _TransferTableDialogState extends State { + TableModel? selectTable; + + @override + void initState() { + super.initState(); + context + .read() + .add(GetTableStatusEvent.getTablesStatus('available')); + } + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: () { + context.pop(); + }, + ); + }, + child: CustomModalDialog( + title: 'Transfer Meja', + subtitle: 'Pilih meja yang tersedia', + contentPadding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0), + minWidth: context.deviceWidth * 0.4, + minHeight: context.deviceHeight * 0.4, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Pilih Meja', + style: TextStyle( + color: AppColors.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SpaceHeight(6.0), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => + Center(child: const CircularProgressIndicator()), + loading: () => + Center(child: const CircularProgressIndicator()), + success: (tables) { + final availableTables = tables; + + if (selectTable == null && availableTables.isNotEmpty) { + selectTable = availableTables.first; + } + + if (availableTables.isEmpty) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.orange[50], + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Colors.orange, + width: 1, + ), + ), + child: const Text( + 'Tidak ada meja yang tersedia. Silakan pilih opsi lain.', + style: TextStyle(color: Colors.orange), + ), + ); + } + + return DropdownSearch( + items: tables, + selectedItem: selectTable, + + // Dropdown properties + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + hintText: "Pilih meja", + hintStyle: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + ), + prefixIcon: Icon( + Icons.category_outlined, + color: Colors.grey.shade500, + size: 20, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, + width: 1.5, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, + width: 1.5, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.blue.shade400, + width: 2, + ), + ), + filled: true, + fillColor: Colors.white, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + ), + ), + + // Popup properties + popupProps: PopupProps.menu( + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Cari meja...", + prefixIcon: const Icon(Icons.search), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + ), + menuProps: MenuProps( + backgroundColor: Colors.white, + elevation: 8, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + itemBuilder: (context, category, isSelected) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + decoration: BoxDecoration( + color: isSelected + ? Colors.blue.shade50 + : Colors.transparent, + border: Border( + bottom: BorderSide( + color: Colors.grey.shade100, + width: 0.5, + ), + ), + ), + child: Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: isSelected + ? Colors.blue.shade600 + : Colors.grey.shade400, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + category.tableName ?? "", + style: TextStyle( + fontSize: 14, + fontWeight: isSelected + ? FontWeight.w600 + : FontWeight.w500, + color: isSelected + ? Colors.blue.shade700 + : Colors.black87, + ), + ), + ), + if (isSelected) + Icon( + Icons.check, + color: Colors.blue.shade600, + size: 18, + ), + ], + ), + ); + }, + emptyBuilder: (context, searchEntry) { + return Container( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.search_off, + color: Colors.grey.shade400, + size: 48, + ), + const SizedBox(height: 12), + Text( + searchEntry.isEmpty + ? "Tidak ada meja tersedia" + : "Tidak ditemukan meja dengan '${searchEntry}'", + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + ], + ), + ); + }, + ), + + // Item as string (for search functionality) + itemAsString: (TableModel table) => + table.tableName ?? "", + + // Comparison function + compareFn: (TableModel? item1, TableModel? item2) { + return item1?.id == item2?.id; + }, + + // On changed callback + onChanged: (TableModel? selectedTable) { + if (selectedTable != null) { + setState(() { + selectTable = selectedTable; + }); + log("selectTable: ${selectTable!.tableName}"); + } + }, + + // Validator (optional) + validator: (TableModel? value) { + if (value == null) { + return "Meja harus dipilih"; + } + return null; + }, + ); + }, + ); + }), + ], + ), + SpaceHeight(24), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => Button.filled( + onPressed: () { + if (selectTable == null) { + AppFlushbar.showError( + context, 'Silahkan Pilih Meja Tujuan'); + return; + } + + context.read().add( + TransferTableEvent.transferTable( + fromTableId: widget.fromTable.id ?? "", + toTableId: selectTable!.id ?? "", + ), + ); + }, + label: "Transfer", + ), + loading: () => Center( + child: const CircularProgressIndicator(), + ), + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/table/models/draft_order_item.dart b/lib/presentation/table/models/draft_order_item.dart index afe8d66..694e5c0 100644 --- a/lib/presentation/table/models/draft_order_item.dart +++ b/lib/presentation/table/models/draft_order_item.dart @@ -26,7 +26,7 @@ class DraftOrderItem { Map toMapForLocal(int orderId) { return { 'id_draft_order': orderId, - 'id_product': product.productId, + 'id_product': product.id, 'quantity': quantity, 'price': product.price, }; diff --git a/lib/presentation/table/pages/new_table_management_page.dart b/lib/presentation/table/pages/new_table_management_page.dart index 95fb0d9..fd55a8c 100644 --- a/lib/presentation/table/pages/new_table_management_page.dart +++ b/lib/presentation/table/pages/new_table_management_page.dart @@ -1,18 +1,12 @@ -import 'dart:developer'; - +import 'package:enaklo_pos/presentation/table/dialogs/form_table_new_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:enaklo_pos/core/components/components.dart'; import 'package:enaklo_pos/core/constants/colors.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/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/get_table/get_table_bloc.dart'; import 'package:enaklo_pos/presentation/table/widgets/table_widget.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:hive/hive.dart'; -import 'package:path_provider/path_provider.dart'; class TableManagementScreen extends StatefulWidget { const TableManagementScreen({super.key}); @@ -64,55 +58,9 @@ class _TableManagementScreenState extends State { onPressed: () { // show dialaog adn input table name showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text('Add Table'), - content: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: 180, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - CustomTextField( - controller: tableNameController!, - label: 'Table Name', - ), - SpaceHeight(16), - Row( - children: [ - Expanded( - child: Button.outlined( - onPressed: () { - context.pop(); - }, - label: 'close', - ), - ), - SpaceWidth(16), - Expanded( - child: Button.filled( - onPressed: () { - context.read().add( - CreateTableEvent.createTable( - tableNameController!.text, - Offset(200, 200))); - context - .pop(); // close dialog after adding - }, - label: 'Add', - ), - ), - ], - ) - ], - ), - ), - ), - actions: []); - }); + context: context, + builder: (context) => FormTableNewDialog(), + ); }, ), ), @@ -145,8 +93,8 @@ class _TableManagementScreenState extends State { return Stack( children: tables.map((table) { return Positioned( - left: table.position.dx - 116, - top: table.position.dy - 80, + left: (table.positionX ?? 0) - 116, + top: (table.positionY ?? 0) - 80, child: BlocListener( listener: (context, state) { @@ -165,7 +113,7 @@ class _TableManagementScreenState extends State { context .read() .add(ChangePositionTableEvent.changePositionTable( - tableId: table.id!, + tableId: table.id ?? "", position: details.offset, )); }, diff --git a/lib/presentation/table/pages/payment_table_page.dart b/lib/presentation/table/pages/payment_table_page.dart index 7dbbbf5..d7c4ef7 100644 --- a/lib/presentation/table/pages/payment_table_page.dart +++ b/lib/presentation/table/pages/payment_table_page.dart @@ -31,10 +31,10 @@ class PaymentTablePage extends StatefulWidget { final DraftOrderModel? draftOrder; final TableModel? table; const PaymentTablePage({ - Key? key, + super.key, this.draftOrder, this.table, - }) : super(key: key); + }); @override State createState() => _PaymentTablePageState(); @@ -46,35 +46,36 @@ class _PaymentTablePageState extends State { PaymentMethod? selectedPaymentMethod; int totalPriceFinal = 0; int discountAmountFinal = 0; - + // Helper method to handle post-payment cleanup Future _handlePostPaymentCleanup() async { if (widget.table != null && widget.draftOrder?.id != null) { // Update table status to available - final newTable = TableModel( - id: widget.table!.id, - tableName: widget.table!.tableName, - status: 'available', - orderId: 0, - paymentAmount: 0, - startTime: DateTime.now().toIso8601String(), - position: widget.table!.position, - ); - + // final newTable = TableModel( + // id: widget.table!.id, + // tableName: widget.table!.tableName, + // status: 'available', + // orderId: 0, + // paymentAmount: 0, + // startTime: DateTime.now().toIso8601String(), + // position: widget.table!.position, + // ); + // Update table status - await ProductLocalDatasource.instance.updateStatusTable(newTable); - + // await ProductLocalDatasource.instance.updateStatusTable(newTable); + // Remove draft order - await ProductLocalDatasource.instance.removeDraftOrderById(widget.draftOrder!.id!); - + await ProductLocalDatasource.instance + .removeDraftOrderById(widget.draftOrder!.id!); + // Refresh table status context.read().add( - GetTableStatusEvent.getTablesStatus('all'), - ); - + GetTableStatusEvent.getTablesStatus('all'), + ); + log("Table ${widget.table!.tableName} freed up and draft order removed"); } - + // Safely navigate back - pop multiple times to get to table management // Pop the success dialog first, then the payment page if (Navigator.of(context).canPop()) { @@ -84,6 +85,7 @@ class _PaymentTablePageState extends State { } } } + @override void initState() { context @@ -93,14 +95,16 @@ class _PaymentTablePageState extends State { .read() .add(PaymentMethodsEvent.fetchPaymentMethods()); super.initState(); - + // Set a default payment method in case API fails selectedPaymentMethod = PaymentMethod( - id: 1, - name: 'Cash', - description: 'Cash payment', + id: "4b1c0d21-c98a-4fc0-a2f9-8d90a0c9d905", + organizationId: "3e8b1793-d18b-40c4-a03d-0c6480b630c7", + name: "CASH", + type: "cash", isActive: true, - sortOrder: 1, + createdAt: DateTime.tryParse('2025-07-18T03:43:13.857048+07:00'), + updatedAt: DateTime.tryParse('2025-07-18T03:43:13.857048+07:00'), ); } @@ -135,7 +139,8 @@ class _PaymentTablePageState extends State { builder: (BuildContext context) { return AlertDialog( title: const Text('Kembali'), - content: const Text('Apakah Anda yakin ingin kembali? Order akan tetap tersimpan.'), + content: const Text( + 'Apakah Anda yakin ingin kembali? Order akan tetap tersimpan.'), actions: [ TextButton( onPressed: () { @@ -146,7 +151,8 @@ class _PaymentTablePageState extends State { TextButton( onPressed: () { Navigator.of(context).pop(); // Close dialog - Navigator.of(context).pop(); // Go back to previous page + Navigator.of(context) + .pop(); // Go back to previous page }, child: const Text('Ya'), ), @@ -157,7 +163,8 @@ class _PaymentTablePageState extends State { }, child: const Text( 'Kembali', - style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + style: TextStyle( + color: Colors.white, fontWeight: FontWeight.bold), ), ), ], @@ -243,15 +250,19 @@ class _PaymentTablePageState extends State { orElse: () => const Center( child: Text('No Items'), ), - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) { + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { if (products.isEmpty) { return const Center( child: Text('No Items'), @@ -286,21 +297,24 @@ class _PaymentTablePageState extends State { builder: (context, state) { final price = state.maybeWhen( orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) => + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => products.fold( 0, (previousValue, element) => previousValue + - (element.product.price! - .toIntegerFromText * + (element.product.price! * element.quantity), )); return Text( @@ -327,16 +341,19 @@ class _PaymentTablePageState extends State { builder: (context, state) { final discount = state.maybeWhen( orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) { + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { log("discountAmount: $discountAmount"); return discountAmount; }); @@ -366,49 +383,60 @@ class _PaymentTablePageState extends State { builder: (context, state) { final tax = state.maybeWhen( orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) => + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => tax, ); final price = state.maybeWhen( orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) => + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => products.fold( 0, (previousValue, element) => previousValue + - (element.product.price! - .toIntegerFromText * + (element.product.price! * element.quantity), ), ); final discount = state.maybeWhen( orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) { + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { return discountAmount; }); @@ -436,65 +464,80 @@ class _PaymentTablePageState extends State { ), BlocBuilder( builder: (context, state) { - final tax = state.maybeWhen( + state.maybeWhen( orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) => + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => tax, ); final price = state.maybeWhen( orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) => + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => products.fold( 0, (previousValue, element) => previousValue + - (element.product.price! - .toIntegerFromText * + (element.product.price! * element.quantity), ), ); final discount = state.maybeWhen( orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) { + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { return discountAmount; }); final serviceCharge = state.maybeWhen( orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) => + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => serviceCharge, ); @@ -528,64 +571,79 @@ class _PaymentTablePageState extends State { builder: (context, state) { final price = state.maybeWhen( orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) => + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => products.fold( 0, (previousValue, element) => previousValue + - (element.product.price! - .toIntegerFromText * + (element.product.price! * element.quantity), ), ); final discount = state.maybeWhen( orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) { + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { return discountAmount; }); final serviceCharge = state.maybeWhen( orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) => + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => serviceCharge, ); final tax = state.maybeWhen( orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) => + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => tax, ); @@ -658,15 +716,19 @@ class _PaymentTablePageState extends State { orElse: () { return SizedBox.shrink(); }, - loaded: (items, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, orderType) { + loaded: ( + items, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { customerController.text = draftName; return TextFormField( readOnly: true, @@ -699,7 +761,8 @@ class _PaymentTablePageState extends State { ), ), const SpaceHeight(12.0), - BlocBuilder( + BlocBuilder( builder: (context, state) { return state.maybeWhen( orElse: () => const Center( @@ -717,14 +780,16 @@ class _PaymentTablePageState extends State { error: (message) => Column( children: [ Center( - child: Text('Error loading payment methods: $message'), + child: Text( + 'Error loading payment methods: $message'), ), const SpaceHeight(16.0), Button.filled( onPressed: () { context .read() - .add(PaymentMethodsEvent.fetchPaymentMethods()); + .add(PaymentMethodsEvent + .fetchPaymentMethods()); }, label: 'Retry', ), @@ -739,32 +804,39 @@ class _PaymentTablePageState extends State { return Column( children: [ const Center( - child: Text('No payment methods available'), + child: Text( + 'No payment methods available'), ), const SpaceHeight(16.0), Button.filled( onPressed: () { context .read() - .add(PaymentMethodsEvent.fetchPaymentMethods()); + .add(PaymentMethodsEvent + .fetchPaymentMethods()); }, label: 'Retry', ), ], ); } - + // Set default selected payment method if none selected or if current selection is not in the list - if (selectedPaymentMethod == null || - !paymentMethods.any((method) => method.id == selectedPaymentMethod?.id)) { - selectedPaymentMethod = paymentMethods.first; + if (selectedPaymentMethod == null || + !paymentMethods.any((method) => + method.id == + selectedPaymentMethod?.id)) { + selectedPaymentMethod = + paymentMethods.first; } - + return Wrap( spacing: 12.0, runSpacing: 8.0, children: paymentMethods.map((method) { - final isSelected = selectedPaymentMethod?.id == method.id; + final isSelected = + selectedPaymentMethod?.id == + method.id; return Container( constraints: const BoxConstraints( minWidth: 120.0, @@ -775,33 +847,42 @@ class _PaymentTablePageState extends State { color: AppColors.primary, width: 2.0, ), - borderRadius: BorderRadius.circular(8.0), + borderRadius: + BorderRadius.circular( + 8.0), ) : null, - child: Tooltip( - message: method.description ?? 'No description available', - child: isSelected - ? Button.filled( - width: double.infinity, - height: 50.0, - onPressed: () { - setState(() { - selectedPaymentMethod = method; - }); - }, - label: method.name?.isNotEmpty == true ? method.name! : 'Unknown', - ) - : Button.outlined( - width: double.infinity, - height: 50.0, - onPressed: () { - setState(() { - selectedPaymentMethod = method; - }); - }, - label: method.name?.isNotEmpty == true ? method.name! : 'Unknown', - ), - ), + child: isSelected + ? Button.filled( + width: double.infinity, + height: 50.0, + onPressed: () { + setState(() { + selectedPaymentMethod = + method; + }); + }, + label: method.name + ?.isNotEmpty == + true + ? method.name! + : 'Unknown', + ) + : Button.outlined( + width: double.infinity, + height: 50.0, + onPressed: () { + setState(() { + selectedPaymentMethod = + method; + }); + }, + label: method.name + ?.isNotEmpty == + true + ? method.name! + : 'Unknown', + ), ); }).toList(), ); @@ -881,30 +962,41 @@ class _PaymentTablePageState extends State { builder: (context) => AlertDialog( title: Row( children: [ - Icon(Icons.warning, color: AppColors.red), + Icon(Icons.warning, + color: AppColors.red), SizedBox(width: 8), - Text('Batalkan Pesanan?'), + Text('Batalkan Pesanan?'), ], ), content: Text( 'Apakah anda yakin ingin membatalkan pesanan untuk meja ${widget.table?.tableName ?? "ini"}?\n\nPesanan akan dihapus secara permanen.'), actions: [ TextButton( - onPressed: () => Navigator.pop(context), - child: Text('Tidak', - style: TextStyle(color: AppColors.primary)), + onPressed: () => + Navigator.pop(context), + child: Text('Tidak', + style: TextStyle( + color: + AppColors.primary)), ), - BlocListener( + BlocListener( listener: (context, state) { state.maybeWhen( orElse: () {}, success: () { - Navigator.pop(context); // Close void dialog - Navigator.pop(context); // Close payment page - ScaffoldMessenger.of(context).showSnackBar( + Navigator.pop( + context); // Close void dialog + Navigator.pop( + context); // Close payment page + ScaffoldMessenger.of( + context) + .showSnackBar( const SnackBar( - content: Text('Pesanan berhasil dibatalkan'), - backgroundColor: AppColors.primary, + content: Text( + 'Pesanan berhasil dibatalkan'), + backgroundColor: + AppColors.primary, ), ); }, @@ -912,34 +1004,47 @@ class _PaymentTablePageState extends State { }, child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: AppColors.red, + backgroundColor: + AppColors.red, ), onPressed: () { // Void the order if (widget.table != null) { - final newTable = TableModel( - id: widget.table!.id, - tableName: widget.table!.tableName, - status: 'available', - orderId: 0, - paymentAmount: 0, - startTime: DateTime.now().toIso8601String(), - position: widget.table!.position, - ); - context.read().add( - StatusTableEvent.statusTabel(newTable), - ); + // final newTable = TableModel( + // id: widget.table!.id, + // tableName: widget + // .table!.tableName, + // status: 'available', + // orderId: 0, + // paymentAmount: 0, + // startTime: DateTime.now() + // .toIso8601String(), + // position: widget + // .table!.position, + // ); + // context + // .read() + // .add( + // StatusTableEvent + // .statusTabel( + // newTable), + // ); } // Remove draft order from local storage - if (widget.draftOrder?.id != null) { - ProductLocalDatasource.instance - .removeDraftOrderById(widget.draftOrder!.id!); + if (widget.draftOrder?.id != + null) { + ProductLocalDatasource + .instance + .removeDraftOrderById( + widget.draftOrder! + .id!); } log("Voided order from payment page"); }, child: const Text( "Ya, Batalkan", - style: TextStyle(color: Colors.white), + style: TextStyle( + color: Colors.white), ), ), ), @@ -953,21 +1058,21 @@ class _PaymentTablePageState extends State { const SpaceWidth(8.0), BlocListener( listener: (context, state) { - final newTable = TableModel( - id: widget.table!.id, - tableName: widget.table!.tableName, - status: 'available', - orderId: 0, - paymentAmount: 0, - startTime: - DateTime.now().toIso8601String(), - position: widget.table!.position, - ); - context.read().add( - StatusTableEvent.statusTabel( - newTable, - ), - ); + // final newTable = TableModel( + // id: widget.table!.id, + // tableName: widget.table!.tableName, + // status: 'available', + // orderId: 0, + // paymentAmount: 0, + // startTime: + // DateTime.now().toIso8601String(), + // position: widget.table!.position, + // ); + // context.read().add( + // StatusTableEvent.statusTabel( + // newTable, + // ), + // ); ProductLocalDatasource.instance .removeDraftOrderById( widget.draftOrder!.id!); @@ -977,58 +1082,66 @@ class _PaymentTablePageState extends State { builder: (context, state) { final discount = state.maybeWhen( orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) { + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) { if (discountModel == null) { return 0; } - return discountModel!.value! + return discountModel.value! .replaceAll('.00', '') .toIntegerFromText; }); final price = state.maybeWhen( orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => products.fold( 0, (previousValue, element) => previousValue + - (element.product.price! - .toIntegerFromText * + (element.product.price! * element.quantity), ), ); final tax = state.maybeWhen( orElse: () => 0, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => tax, ); @@ -1041,16 +1154,19 @@ class _PaymentTablePageState extends State { List items = state.maybeWhen( orElse: () => [], - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => products, ); final totalQty = items.fold( @@ -1061,57 +1177,67 @@ class _PaymentTablePageState extends State { final orderType = state.maybeWhen( orElse: () => OrderType.dineIn, - loaded: (products, - discountModel, - discount, - discountAmount, - tax, - serviceCharge, - totalQuantity, - totalPrice, - draftName, - orderType) => + loaded: ( + products, + discountModel, + discount, + discountAmount, + tax, + serviceCharge, + totalQuantity, + totalPrice, + draftName, + orderType, + deliveryType, + ) => orderType, ); - final totalPrice = subTotal + finalTax; - return Flexible( child: Button.filled( onPressed: () async { if (selectedPaymentMethod == null) { - ScaffoldMessenger.of(context).showSnackBar( + ScaffoldMessenger.of(context) + .showSnackBar( const SnackBar( - content: Text('Please select a payment method'), + content: Text( + 'Please select a payment method'), backgroundColor: Colors.red, ), ); return; } - - final paymentMethodName = selectedPaymentMethod?.name?.toLowerCase() ?? ''; + + final paymentMethodName = + selectedPaymentMethod?.name + ?.toLowerCase() ?? + ''; log("Selected payment method: ${selectedPaymentMethod?.name} (normalized: $paymentMethodName)"); - - if (paymentMethodName == 'cash' || + + if (paymentMethodName == 'cash' || paymentMethodName == 'tunai' || - paymentMethodName == 'uang tunai' || - paymentMethodName == 'cash payment') { - context.read().add( - OrderEvent.order( - items, - discount, - discountAmountFinal, - finalTax.toInt(), - 0, - totalPriceController.text - .toIntegerFromText, - customerController.text, - widget.table?.id ?? 0, - 'completed', - 'paid', - selectedPaymentMethod?.name ?? 'Cash', - totalPriceFinal, - orderType)); + paymentMethodName == + 'uang tunai' || + paymentMethodName == + 'cash payment') { + // context.read().add( + // OrderEvent.order( + // items, + // discount, + // discountAmountFinal, + // finalTax.toInt(), + // 0, + // totalPriceController.text + // .toIntegerFromText, + // customerController.text, + // widget.table?.id ?? 0, + // 'completed', + // 'paid', + // selectedPaymentMethod + // ?.name ?? + // 'Cash', + // totalPriceFinal, + // orderType)); await showDialog( context: context, @@ -1136,22 +1262,24 @@ class _PaymentTablePageState extends State { await _handlePostPaymentCleanup(); } else { log("Processing non-cash payment: ${selectedPaymentMethod?.name}"); - context.read().add( - OrderEvent.order( - items, - discount, - discountAmountFinal, - finalTax.toInt(), - 0, - totalPriceController.text - .toIntegerFromText, - customerController.text, - widget.table?.id ?? 0, - 'completed', - 'paid', - selectedPaymentMethod?.name ?? 'Unknown Payment Method', - totalPriceFinal, - orderType)); + // context.read().add( + // OrderEvent.order( + // items, + // discount, + // discountAmountFinal, + // finalTax.toInt(), + // 0, + // totalPriceController.text + // .toIntegerFromText, + // customerController.text, + // widget.table?.id ?? 0, + // 'completed', + // 'paid', + // selectedPaymentMethod + // ?.name ?? + // 'Unknown Payment Method', + // totalPriceFinal, + // orderType)); await showDialog( context: context, @@ -1173,7 +1301,7 @@ class _PaymentTablePageState extends State { customerController.text, ), ); - + // Handle post-payment cleanup await _handlePostPaymentCleanup(); } diff --git a/lib/presentation/table/pages/table_page.dart b/lib/presentation/table/pages/table_page.dart index 570d051..b31e397 100644 --- a/lib/presentation/table/pages/table_page.dart +++ b/lib/presentation/table/pages/table_page.dart @@ -1,10 +1,35 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter/material.dart'; -import 'package:enaklo_pos/core/components/components.dart'; +import 'package:enaklo_pos/core/components/flushbar.dart'; import 'package:enaklo_pos/core/constants/colors.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/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/create_table/create_table_bloc.dart'; import 'package:enaklo_pos/presentation/table/blocs/get_table/get_table_bloc.dart'; -import 'package:enaklo_pos/presentation/table/dialogs/form_table_dialog.dart'; -import 'package:enaklo_pos/presentation/table/widgets/card_table_widget.dart'; +import 'package:enaklo_pos/presentation/table/blocs/transfer_table/transfer_table_bloc.dart'; +import 'package:enaklo_pos/presentation/table/dialogs/form_table_new_dialog.dart'; +import 'package:enaklo_pos/presentation/table/dialogs/transfer_table_dialog.dart'; +import 'package:enaklo_pos/presentation/table/widgets/table_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +// Enum status meja +enum TableStatus { available, occupied, billed, availableSoon, unknown } + +TableStatus parseStatus(String? status) { + switch (status) { + case 'available': + return TableStatus.available; + case 'occupied': + return TableStatus.occupied; + case 'billed': + return TableStatus.billed; + case 'available_soon': + return TableStatus.availableSoon; + default: + return TableStatus.unknown; + } +} class TablePage extends StatefulWidget { const TablePage({super.key}); @@ -14,6 +39,26 @@ class TablePage extends StatefulWidget { } class _TablePageState extends State { + TableModel? selectedTable; + + // Untuk drag + TableModel? draggingTable; + + Offset? _tapPosition; + + // Ubah function toggleSelectTable menjadi selectTable + void selectTable(TableModel table) { + if (table.status == 'occupied') return; + setState(() { + if (selectedTable == table) { + selectedTable = null; // Deselect jika table yang sama diklik + } else { + selectedTable = + table; // Select table baru (akan mengganti selection sebelumnya) + } + }); + } + @override void initState() { context.read().add(const GetTableEvent.getTables()); @@ -22,74 +67,391 @@ class _TablePageState extends State { @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(24), - child: ListView( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Table Management", - style: TextStyle( - fontSize: 24.0, - fontWeight: FontWeight.bold, - color: AppColors.primary, - ), - ), - Button.filled( - onPressed: () { - showDialog( - context: context, - builder: (context) => FormTableDialog(), - ); - }, - label: 'Generate Table', - height: 48.0, - width: 200.0, - ), - ], - ), - SpaceHeight(24.0), - BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () { - return SizedBox.shrink(); - }, - loading: () { - return const CircularProgressIndicator(); - }, - success: (tables) { - if (tables.isEmpty) { - return const Center( - child: Text('No table available'), - ); - } - return GridView.builder( - padding: EdgeInsets.zero, - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - childAspectRatio: 1.0, - crossAxisCount: 4, - mainAxisSpacing: 16, - crossAxisSpacing: 16, - ), - itemCount: tables.length, - shrinkWrap: true, - physics: const ScrollPhysics(), - itemBuilder: (BuildContext context, int index) { - return CardTableWidget( - table: tables[index], - ); - }, - ); - }, - ); + final double mapWidth = context.deviceWidth * 2; + final double mapHeight = context.deviceHeight * 1.5; + + return Scaffold( + appBar: AppBar( + title: const Text("Layout Meja"), + actions: [ + BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: (message) { + context + .read() + .add(const GetTableEvent.getTables()); + }); }, + child: IconButton( + icon: const Icon(Icons.add), + onPressed: () { + showDialog( + context: context, + builder: (context) => FormTableNewDialog(), + ); + }, + ), ), ], + backgroundColor: Colors.white, + foregroundColor: Colors.black, + elevation: 0.5, + bottom: PreferredSize( + preferredSize: const Size.fromHeight(60), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + children: [ + _buildLegendDot(Colors.blue[200]!, "Available"), + const SizedBox(width: 16), + _buildLegendDot(Colors.orange[200]!, "Occupied"), + const SizedBox(width: 16), + _buildLegendDot(Colors.green[200]!, "Billed"), + const SizedBox(width: 16), + _buildLegendDot(Colors.yellow[200]!, "Available soon"), + ], + ), + ), + ), + ), + backgroundColor: const Color(0xFFF7F8FA), + body: BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: () { + AppFlushbar.showSuccess(context, 'Transfer Table Success'); + context.read().add(const GetTableEvent.getTables()); + }, + error: (message) { + AppFlushbar.showError(context, message); + }, + ); + }, + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => SizedBox.shrink(), + loading: () => const Center(child: CircularProgressIndicator()), + success: (tables) => SafeArea( + child: Stack( + children: [ + // Main content area + Row( + children: [ + // Area meja (zoom & pan & drag) + Expanded( + flex: 5, + child: InteractiveViewer( + panEnabled: true, + scaleEnabled: true, + constrained: false, + boundaryMargin: const EdgeInsets.all(80), + minScale: 0.3, + maxScale: 3.0, + alignment: Alignment.topLeft, + child: Container( + width: mapWidth, + height: mapHeight, + decoration: BoxDecoration( + color: const Color(0xFFF7F8FA), + border: Border.all( + color: Colors.grey[300]!, width: 2), + ), + child: Stack( + children: [ + // Optional: Grid background + ...List.generate( + 20, + (i) => Positioned( + left: i * 100.0, + top: 0, + bottom: 0, + child: Container( + width: 1, + color: Colors.grey[200]), + )), + ...List.generate( + 15, + (i) => Positioned( + top: i * 100.0, + left: 0, + right: 0, + child: Container( + height: 1, + color: Colors.grey[200]), + )), + + // Tables + ...tables.map((table) { + final isSelected = selectedTable == table; + return Positioned( + left: table.positionX, + top: table.positionY, + child: Draggable( + data: table, + feedback: Material( + color: Colors.transparent, + child: TableWidget( + table: table, + isSelected: isSelected, + ), + ), + childWhenDragging: Opacity( + opacity: 0.5, + child: TableWidget( + table: table, + isSelected: isSelected, + ), + ), + onDragStarted: () { + setState(() { + draggingTable = table; + }); + }, + onDraggableCanceled: + (velocity, offset) { + setState(() { + draggingTable = null; + }); + }, + onDragEnd: (details) { + setState(() { + draggingTable = null; + final RenderBox box = + context.findRenderObject() + as RenderBox; + final Offset local = box + .globalToLocal(details.offset); + table.positionX = local.dx + .clamp(0, mapWidth - 120); + table.positionY = local.dy + .clamp(0, mapHeight - 80); + }); + context + .read() + .add(ChangePositionTableEvent + .changePositionTable( + tableId: table.id ?? "", + position: Offset( + table.positionX ?? 0.0, + table.positionY ?? 0.0, + ), + )); + }, + child: GestureDetector( + onTap: () => selectTable(table), + onTapDown: (details) => _tapPosition = + details.globalPosition, + onLongPress: () { + if (table.status == 'occupied') { + _showPopupMenu(context, table); + } + }, + child: TableWidget( + table: table, + isSelected: isSelected, + ), + ), + ), + ); + }).toList(), + ], + ), + ), + ), + ), + // Sidebar bar tables + ], + ), + + // Floating bottom bar - hanya muncul jika ada table yang dipilih + buildAlternativeFloatingBar(), + ], + ), + ), + ); + }, + ), ), ); } + + Widget buildAlternativeFloatingBar() { + return Positioned( + bottom: 20, // Jarak dari bawah + left: 16, // Jarak dari kiri + right: 16, + child: AnimatedSlide( + duration: const Duration(milliseconds: 400), + curve: Curves.elasticOut, + offset: selectedTable == null ? const Offset(0, 2) : Offset.zero, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 200), + opacity: selectedTable == null ? 0.0 : 1.0, + child: IgnorePointer( + ignoring: selectedTable == null, + child: Container( + margin: const EdgeInsets.all(16), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.primary, + AppColors.primary.withOpacity(0.6) + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: AppColors.primary.withOpacity(0.3), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], + ), + child: Row( + children: [ + const Icon( + Icons.table_bar, + color: Colors.white, + size: 24, + ), + const SizedBox(width: 12), + Text( + "1 Meja Dipilih", + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const SizedBox(width: 16), + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row(children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(16), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + selectedTable?.tableName ?? "", + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(width: 4), + GestureDetector( + onTap: () { + setState(() { + selectedTable = null; + }); + }, + child: const Icon( + Icons.close, + color: Colors.white, + size: 16, + ), + ), + ], + ), + ), + ), + ]), + ), + ), + const SizedBox(width: 16), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: AppColors.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 12, + ), + ), + onPressed: () { + if (selectedTable?.status == 'available') { + context.push(DashboardPage( + table: selectedTable!, + )); + } else {} + }, + child: const Text( + "Tempatkan Pesanan", + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + ], + ), + ), + ), + ), + ), + ); + } + + Widget _buildLegendDot(Color color, String label) { + return Row( + children: [ + CircleAvatar(radius: 7, backgroundColor: color), + const SizedBox(width: 6), + Text(label, style: const TextStyle(fontSize: 14)), + ], + ); + } + + void _showPopupMenu(BuildContext context, TableModel table) { + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; + + showMenu( + context: context, + position: RelativeRect.fromRect( + _tapPosition != null + ? Rect.fromLTWH(_tapPosition!.dx, _tapPosition!.dy, 0, 0) + : Rect.fromLTWH(100, 100, 0, 0), + Offset.zero & overlay.size, + ), + color: AppColors.white, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + elevation: 1, + items: [ + PopupMenuItem( + onTap: () { + showDialog( + context: context, + builder: (context) => TransferTableDialog(fromTable: table), + ); + }, + child: Row( + children: [ + Icon(Icons.swap_horiz), + SizedBox(width: 8), + Text('Transfer'), + ], + ), + ), + ], + ); + } } diff --git a/lib/presentation/table/pages/table_page.dart.backup b/lib/presentation/table/pages/table_page.dart.backup new file mode 100644 index 0000000..570d051 --- /dev/null +++ b/lib/presentation/table/pages/table_page.dart.backup @@ -0,0 +1,95 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:enaklo_pos/core/components/components.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/presentation/table/blocs/get_table/get_table_bloc.dart'; +import 'package:enaklo_pos/presentation/table/dialogs/form_table_dialog.dart'; +import 'package:enaklo_pos/presentation/table/widgets/card_table_widget.dart'; + +class TablePage extends StatefulWidget { + const TablePage({super.key}); + + @override + State createState() => _TablePageState(); +} + +class _TablePageState extends State { + @override + void initState() { + context.read().add(const GetTableEvent.getTables()); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(24), + child: ListView( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Table Management", + style: TextStyle( + fontSize: 24.0, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + Button.filled( + onPressed: () { + showDialog( + context: context, + builder: (context) => FormTableDialog(), + ); + }, + label: 'Generate Table', + height: 48.0, + width: 200.0, + ), + ], + ), + SpaceHeight(24.0), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () { + return SizedBox.shrink(); + }, + loading: () { + return const CircularProgressIndicator(); + }, + success: (tables) { + if (tables.isEmpty) { + return const Center( + child: Text('No table available'), + ); + } + return GridView.builder( + padding: EdgeInsets.zero, + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + childAspectRatio: 1.0, + crossAxisCount: 4, + mainAxisSpacing: 16, + crossAxisSpacing: 16, + ), + itemCount: tables.length, + shrinkWrap: true, + physics: const ScrollPhysics(), + itemBuilder: (BuildContext context, int index) { + return CardTableWidget( + table: tables[index], + ); + }, + ); + }, + ); + }, + ), + ], + ), + ); + } +} diff --git a/lib/presentation/table/widgets/card_table_widget.dart b/lib/presentation/table/widgets/card_table_widget.dart index 024a311..4b18d5a 100644 --- a/lib/presentation/table/widgets/card_table_widget.dart +++ b/lib/presentation/table/widgets/card_table_widget.dart @@ -39,8 +39,8 @@ class _CardTableWidgetState extends State { loadData() async { if (widget.table.status != 'available') { - data = await ProductLocalDatasource.instance - .getDraftOrderById(widget.table.orderId); + // data = await ProductLocalDatasource.instance + // .getDraftOrderById(widget.table.orderId); } } @@ -70,9 +70,10 @@ class _CardTableWidgetState extends State { ), ), Text( - widget.table.status == 'available' - ? widget.table.status - : "${widget.table.status} - ${DateTime.parse(widget.table.startTime).toFormattedTime()}", + // widget.table.status == 'available' + // ? widget.table.status + // : "${widget.table.status} - ${DateTime.parse(widget.table.startTime).toFormattedTime()}", + "", style: TextStyle( color: AppColors.black, fontSize: 24, diff --git a/lib/presentation/table/widgets/table_widget.dart b/lib/presentation/table/widgets/table_widget.dart index ea94ca8..1547457 100644 --- a/lib/presentation/table/widgets/table_widget.dart +++ b/lib/presentation/table/widgets/table_widget.dart @@ -1,377 +1,180 @@ -// 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:enaklo_pos/core/components/buttons.dart'; -import 'package:enaklo_pos/core/components/custom_text_field.dart'; -import 'package:enaklo_pos/core/components/spaces.dart'; import 'package:enaklo_pos/core/constants/colors.dart'; -import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; -import 'package:enaklo_pos/core/utils/date_formatter.dart'; -import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; - import 'package:enaklo_pos/data/models/response/table_model.dart'; -import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart'; -import 'package:enaklo_pos/presentation/home/bloc/status_table/status_table_bloc.dart'; -import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart'; -import 'package:enaklo_pos/presentation/table/blocs/create_table/create_table_bloc.dart'; -import 'package:enaklo_pos/presentation/table/blocs/get_table/get_table_bloc.dart'; -import 'package:enaklo_pos/presentation/table/blocs/update_table/update_table_bloc.dart'; -import 'package:enaklo_pos/presentation/table/models/draft_order_model.dart'; +import 'package:enaklo_pos/presentation/table/pages/table_page.dart'; +import 'package:flutter/material.dart'; -import '../pages/payment_table_page.dart'; - -class TableWidget extends StatefulWidget { +class TableWidget extends StatelessWidget { final TableModel table; - const TableWidget({ - super.key, - required this.table, - }); + final bool isSelected; - @override - State createState() => _TableWidgetState(); -} + const TableWidget({super.key, required this.table, this.isSelected = false}); -class _TableWidgetState extends State { - TextEditingController? tableNameController; - DraftOrderModel? data; - @override - void initState() { - super.initState(); - loadData(); - tableNameController = TextEditingController(text: widget.table.tableName); + // Fungsi untuk menentukan jumlah kursi di tiap sisi + Map getChairDistribution(int capacity) { + if (capacity == 1) { + return {'top': 0, 'bottom': 0, 'left': 1, 'right': 0}; + } else if (capacity == 2) { + return {'top': 0, 'bottom': 0, 'left': 1, 'right': 1}; + } else if (capacity == 3) { + return {'top': 1, 'bottom': 0, 'left': 1, 'right': 1}; + } else if (capacity == 4) { + return {'top': 1, 'bottom': 1, 'left': 1, 'right': 1}; + } else if (capacity == 5) { + return {'top': 2, 'bottom': 1, 'left': 1, 'right': 1}; + } else if (capacity == 6) { + return {'top': 2, 'bottom': 2, 'left': 1, 'right': 1}; + } else if (capacity == 7) { + return {'top': 3, 'bottom': 2, 'left': 1, 'right': 1}; + } else if (capacity == 8) { + return {'top': 3, 'bottom': 3, 'left': 1, 'right': 1}; + } else if (capacity == 9) { + return {'top': 4, 'bottom': 3, 'left': 1, 'right': 1}; + } else if (capacity == 10) { + return {'top': 4, 'bottom': 4, 'left': 1, 'right': 1}; + } else { + int side = ((capacity - 2) / 2).floor(); + return {'top': side, 'bottom': side, 'left': 1, 'right': 1}; + } } - @override - void dispose() { - tableNameController!.dispose(); - super.dispose(); + Color getStatusColor() { + switch (parseStatus(table.status)) { + case TableStatus.available: + return Colors.blue[100]!; + case TableStatus.occupied: + return Colors.orange[100]!; + case TableStatus.billed: + return Colors.green[100]!; + case TableStatus.availableSoon: + return Colors.yellow[100]!; + default: + return Colors.grey[200]!; + } } - loadData() async { - if (widget.table.status != 'available') { - data = await ProductLocalDatasource.instance - .getDraftOrderById(widget.table.orderId); + Color getBorderColor() { + if (isSelected) return AppColors.primary; + switch (parseStatus(table.status)) { + case TableStatus.available: + return Colors.blue; + case TableStatus.occupied: + return Colors.orange; + case TableStatus.billed: + return Colors.green; + case TableStatus.availableSoon: + return Colors.yellow[700]!; + default: + return Colors.grey; } } @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () async { - if (widget.table.status == 'available') { - context.push(DashboardPage( - table: widget.table, - )); - } else { - // Handle occupied table click - load draft order and navigate to payment - context.read().add( - CheckoutEvent.loadDraftOrder(data!), - ); - log("Data Draft Order: ${data!.toMap()}"); - context.push(PaymentTablePage( - table: widget.table, - draftOrder: data!, - )); - } - }, - onLongPress: () { - // dialog info table - showDialog( - context: context, - builder: (context) { - return AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16)), - title: Row( - children: [ - Icon(Icons.table_bar, color: AppColors.primary), - SizedBox(width: 8), - Text('Table ${widget.table.tableName}'), - Spacer(), - BlocListener( - listener: (context, state) { - state.maybeWhen( - orElse: () {}, - success: (message) { - context - .read() - .add(const GetTableEvent.getTables()); - context.pop(); - }); - }, - child: IconButton( - onPressed: () { - // show dialaog adn input table name - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text('Update Table'), - content: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: 180, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - CustomTextField( - controller: tableNameController!, - label: 'Table Name', - ), - SpaceHeight(16), - Row( - children: [ - Expanded( - child: Button.outlined( - onPressed: () { - context.pop(); - }, - label: 'close', - ), - ), - SpaceWidth(16), - Expanded( - child: Button.filled( - onPressed: () { - final newData = - TableModel( - id: widget.table.id, - tableName: - tableNameController! - .text, - status: - widget.table.status, - startTime: widget - .table.startTime, - orderId: widget - .table.orderId, - paymentAmount: widget - .table - .paymentAmount, - position: widget - .table.position, - ); - context - .read< - UpdateTableBloc>() - .add( - UpdateTableEvent - .updateTable( - newData, - ), - ); - context - .pop(); // close dialog after adding - }, - label: 'Update', - ), - ) - ], - ) - ], - ), - ), - ), - actions: []); - }); - }, - icon: Icon(Icons.edit)), - ), - ], - ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildInfoRow( - 'Status:', - widget.table.status == 'available' - ? 'Available' - : 'Occupied', - color: widget.table.status == 'available' - ? Colors.green - : Colors.red), - widget.table.status == 'available' - ? SizedBox.shrink() - : _buildInfoRow( - 'Start Time:', - DateFormatter.formatDateTime2( - widget.table.startTime)), - widget.table.status == 'available' - ? SizedBox.shrink() - : _buildInfoRow( - 'Order ID:', widget.table.orderId.toString()), - widget.table.status == 'available' - ? SizedBox.shrink() - : SpaceHeight(16), - widget.table.status == 'available' - ? SizedBox.shrink() - : Row( - children: [ - Expanded( - child: Button.outlined( - onPressed: () { - // Show void confirmation dialog - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Row( - children: [ - Icon(Icons.warning, color: AppColors.red), - SizedBox(width: 8), - Text('Void Order?'), - ], - ), - content: Text( - 'Apakah anda yakin ingin membatalkan pesanan untuk meja ${widget.table.tableName}?\n\nPesanan akan dihapus secara permanen.'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text('Tidak', - style: TextStyle(color: AppColors.primary)), - ), - BlocListener( - listener: (context, state) { - state.maybeWhen( - orElse: () {}, - success: () { - context - .read() - .add(const GetTableEvent.getTables()); - Navigator.pop(context); // Close void dialog - Navigator.pop(context); // Close table info dialog - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Pesanan berhasil dibatalkan'), - backgroundColor: AppColors.primary, - ), - ); - }, - ); - }, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.red, - ), - onPressed: () { - // Void the order - final newTable = TableModel( - id: widget.table.id, - tableName: widget.table.tableName, - status: 'available', - orderId: 0, - paymentAmount: 0, - startTime: DateTime.now().toIso8601String(), - position: widget.table.position, - ); - context.read().add( - StatusTableEvent.statusTabel(newTable), - ); - // Remove draft order from local storage - ProductLocalDatasource.instance - .removeDraftOrderById(widget.table.orderId); - log("Voided order for table: ${widget.table.tableName}"); - }, - child: const Text( - "Ya, Batalkan", - style: TextStyle(color: Colors.white), - ), - ), - ), - ], - ), - ); - }, - label: 'Void Order', - color: AppColors.red, - textColor: AppColors.red, - ), - ), - SizedBox(width: 12), - Expanded( - child: BlocConsumer( - listener: (context, state) { - state.maybeWhen( - orElse: () {}, - success: () { - context - .read() - .add(const GetTableEvent.getTables()); - context.pop(); - }); - }, - builder: (context, state) { - return Button.filled( - onPressed: () { - context.pop(); - context.read().add( - CheckoutEvent.loadDraftOrder(data!), - ); - context.push(PaymentTablePage( - table: widget.table, - draftOrder: data!, - )); - }, - label: 'Selesai'); - }, - ), - ), - ], - ), - ], - ), - actions: [ - TextButton( - child: - Text('Close', style: TextStyle(color: AppColors.primary)), - onPressed: () => Navigator.of(context).pop(), - ), - ], - ); - }, - ); - }, - child: Container( - padding: const EdgeInsets.all(16.0), - alignment: Alignment.center, - decoration: BoxDecoration( - color: widget.table.status == 'available' - ? AppColors.primary - : AppColors.red, - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(10), - ), - child: Text('${widget.table.tableName}', - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.w600, - )), - ), - ); - } + final int capacity = table.capacity ?? 0; + final chairDist = getChairDistribution(capacity); - Widget _buildInfoRow(String label, String value, {Color? color}) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), - child: Row( - children: [ - Text( - label, - style: TextStyle(fontWeight: FontWeight.w600), + Widget chair() => Container( + width: 20, + height: 10, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(4), ), - SizedBox(width: 8), - Expanded( - child: Text( - value, - style: TextStyle( - color: color ?? Colors.black87, + ); + + return Container( + width: (table.capacity ?? 0) > 16 + ? 240 + : (table.capacity ?? 0) > 8 + ? 180 + : 120, + height: 80, + child: Stack( + alignment: Alignment.center, + children: [ + // Meja utama + Container( + width: (table.capacity ?? 0) > 16 + ? 220 + : (table.capacity ?? 0) > 8 + ? 160 + : 100, + height: 60, + decoration: BoxDecoration( + color: Colors.white, + border: Border.all( + color: getBorderColor(), + width: 2, + ), + borderRadius: BorderRadius.circular(16), + ), + child: Center( + child: CircleAvatar( + radius: 24, + backgroundColor: getStatusColor(), + child: Text( + table.tableName ?? "", + style: TextStyle( + color: getBorderColor(), + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), ), ), ), + // Kursi atas + if (chairDist['top']! > 0) + Positioned( + top: 0, + left: 10, + right: 10, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: List.generate(chairDist['top']!, (_) => chair()), + ), + ), + // Kursi bawah + if (chairDist['bottom']! > 0) + Positioned( + bottom: 0, + left: 10, + right: 10, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: List.generate(chairDist['bottom']!, (_) => chair()), + ), + ), + // Kursi kiri + if (chairDist['left']! > 0) + Positioned( + left: 0, + top: 15, + bottom: 15, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: List.generate(chairDist['left']!, (_) => chair()), + ), + ), + // Kursi kanan + if (chairDist['right']! > 0) + Positioned( + right: 0, + top: 15, + bottom: 15, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: List.generate(chairDist['right']!, (_) => chair()), + ), + ), + // Icon info kecil di pojok kanan atas jika status reserved + if (parseStatus(table.status) == TableStatus.occupied) + const Positioned( + top: 6, + right: 6, + child: + Icon(Icons.info_outline, size: 16, color: Colors.redAccent), + ), ], ), ); diff --git a/lib/presentation/table/widgets/table_widget.dart.backup b/lib/presentation/table/widgets/table_widget.dart.backup new file mode 100644 index 0000000..01f7917 --- /dev/null +++ b/lib/presentation/table/widgets/table_widget.dart.backup @@ -0,0 +1,399 @@ +// 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:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/components/custom_text_field.dart'; +import 'package:enaklo_pos/core/components/spaces.dart'; +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; +import 'package:enaklo_pos/core/utils/date_formatter.dart'; +import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; + +import 'package:enaklo_pos/data/models/response/table_model.dart'; +import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart'; +import 'package:enaklo_pos/presentation/home/bloc/status_table/status_table_bloc.dart'; +import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart'; +import 'package:enaklo_pos/presentation/table/blocs/create_table/create_table_bloc.dart'; +import 'package:enaklo_pos/presentation/table/blocs/get_table/get_table_bloc.dart'; +import 'package:enaklo_pos/presentation/table/blocs/update_table/update_table_bloc.dart'; +import 'package:enaklo_pos/presentation/table/models/draft_order_model.dart'; + +import '../pages/payment_table_page.dart'; + +class TableWidget extends StatefulWidget { + final TableModel table; + const TableWidget({ + super.key, + required this.table, + }); + + @override + State createState() => _TableWidgetState(); +} + +class _TableWidgetState extends State { + TextEditingController? tableNameController; + DraftOrderModel? data; + @override + void initState() { + super.initState(); + loadData(); + tableNameController = TextEditingController(text: widget.table.tableName); + } + + @override + void dispose() { + tableNameController!.dispose(); + super.dispose(); + } + + loadData() async { + if (widget.table.status != 'available') { + // data = await ProductLocalDatasource.instance + // .getDraftOrderById(widget.table.orderId); + } + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () async { + if (widget.table.status == 'available') { + context.push(DashboardPage( + table: widget.table, + )); + } else { + // Handle occupied table click - load draft order and navigate to payment + context.read().add( + CheckoutEvent.loadDraftOrder(data!), + ); + log("Data Draft Order: ${data!.toMap()}"); + context.push(PaymentTablePage( + table: widget.table, + draftOrder: data!, + )); + } + }, + onLongPress: () { + // dialog info table + showDialog( + context: context, + builder: (context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16)), + title: Row( + children: [ + Icon(Icons.table_bar, color: AppColors.primary), + SizedBox(width: 8), + Text('Table ${widget.table.tableName}'), + Spacer(), + BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: (message) { + context + .read() + .add(const GetTableEvent.getTables()); + context.pop(); + }); + }, + child: IconButton( + onPressed: () { + // show dialaog adn input table name + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text('Update Table'), + content: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: 180, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CustomTextField( + controller: tableNameController!, + label: 'Table Name', + ), + SpaceHeight(16), + Row( + children: [ + Expanded( + child: Button.outlined( + onPressed: () { + context.pop(); + }, + label: 'close', + ), + ), + SpaceWidth(16), + Expanded( + child: Button.filled( + onPressed: () { + // final newData = + // TableModel( + // id: widget.table.id, + // tableName: + // tableNameController! + // .text, + // status: + // widget.table.status, + // startTime: widget + // .table.startTime, + // orderId: widget + // .table.orderId, + // paymentAmount: widget + // .table + // .paymentAmount, + // position: widget + // .table.position, + // ); + // context + // .read< + // UpdateTableBloc>() + // .add( + // UpdateTableEvent + // .updateTable( + // newData, + // ), + // ); + context + .pop(); // close dialog after adding + }, + label: 'Update', + ), + ) + ], + ) + ], + ), + ), + ), + actions: []); + }); + }, + icon: Icon(Icons.edit)), + ), + ], + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildInfoRow( + 'Status:', + widget.table.status == 'available' + ? 'Available' + : 'Occupied', + color: widget.table.status == 'available' + ? Colors.green + : Colors.red), + // widget.table.status == 'available' + // ? SizedBox.shrink() + // : _buildInfoRow( + // 'Start Time:', + // DateFormatter.formatDateTime2( + // widget.table.startTime)), + // widget.table.status == 'available' + // ? SizedBox.shrink() + // : _buildInfoRow( + // 'Order ID:', widget.table.orderId.toString()), + widget.table.status == 'available' + ? SizedBox.shrink() + : SpaceHeight(16), + widget.table.status == 'available' + ? SizedBox.shrink() + : Row( + children: [ + Expanded( + child: Button.outlined( + onPressed: () { + // Show void confirmation dialog + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Row( + children: [ + Icon(Icons.warning, + color: AppColors.red), + SizedBox(width: 8), + Text('Void Order?'), + ], + ), + content: Text( + 'Apakah anda yakin ingin membatalkan pesanan untuk meja ${widget.table.tableName}?\n\nPesanan akan dihapus secara permanen.'), + actions: [ + TextButton( + onPressed: () => + Navigator.pop(context), + child: Text('Tidak', + style: TextStyle( + color: AppColors.primary)), + ), + BlocListener( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: () { + context + .read() + .add(const GetTableEvent + .getTables()); + Navigator.pop( + context); // Close void dialog + Navigator.pop( + context); // Close table info dialog + ScaffoldMessenger.of(context) + .showSnackBar( + const SnackBar( + content: Text( + 'Pesanan berhasil dibatalkan'), + backgroundColor: + AppColors.primary, + ), + ); + }, + ); + }, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.red, + ), + onPressed: () { + // // Void the order + // final newTable = TableModel( + // id: widget.table.id, + // tableName: + // widget.table.tableName, + // status: 'available', + // orderId: 0, + // paymentAmount: 0, + // startTime: DateTime.now() + // .toIso8601String(), + // position: widget.table.position, + // ); + // context + // .read() + // .add( + // StatusTableEvent + // .statusTabel(newTable), + // ); + // // Remove draft order from local storage + // ProductLocalDatasource.instance + // .removeDraftOrderById( + // widget.table.orderId); + // log("Voided order for table: ${widget.table.tableName}"); + }, + child: const Text( + "Ya, Batalkan", + style: TextStyle( + color: Colors.white), + ), + ), + ), + ], + ), + ); + }, + label: 'Void Order', + color: AppColors.red, + textColor: AppColors.red, + ), + ), + SizedBox(width: 12), + Expanded( + child: BlocConsumer( + listener: (context, state) { + state.maybeWhen( + orElse: () {}, + success: () { + context.read().add( + const GetTableEvent.getTables()); + context.pop(); + }); + }, + builder: (context, state) { + return Button.filled( + onPressed: () { + context.pop(); + context.read().add( + CheckoutEvent.loadDraftOrder( + data!), + ); + context.push(PaymentTablePage( + table: widget.table, + draftOrder: data!, + )); + }, + label: 'Selesai'); + }, + ), + ), + ], + ), + ], + ), + actions: [ + TextButton( + child: + Text('Close', style: TextStyle(color: AppColors.primary)), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ); + }, + ); + }, + child: Container( + padding: const EdgeInsets.all(16.0), + alignment: Alignment.center, + decoration: BoxDecoration( + color: widget.table.status == 'available' + ? AppColors.primary + : AppColors.red, + shape: BoxShape.rectangle, + borderRadius: BorderRadius.circular(10), + ), + child: Text('${widget.table.tableName}', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.w600, + )), + ), + ); + } + + Widget _buildInfoRow(String label, String value, {Color? color}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Row( + children: [ + Text( + label, + style: TextStyle(fontWeight: FontWeight.w600), + ), + SizedBox(width: 8), + Expanded( + child: Text( + value, + style: TextStyle( + color: color ?? Colors.black87, + ), + ), + ), + ], + ), + ); + } +} + + diff --git a/lib/presentation/void/bloc/void_order_bloc.dart b/lib/presentation/void/bloc/void_order_bloc.dart new file mode 100644 index 0000000..fb27267 --- /dev/null +++ b/lib/presentation/void/bloc/void_order_bloc.dart @@ -0,0 +1,36 @@ +import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'void_order_event.dart'; +part 'void_order_state.dart'; +part 'void_order_bloc.freezed.dart'; + +class VoidOrderBloc extends Bloc { + final OrderRemoteDatasource _orderRemoteDatasource; + + VoidOrderBloc(this._orderRemoteDatasource) + : super(const VoidOrderState.initial()) { + on<_VoidOrder>(_onVoidOrder); + } + + Future _onVoidOrder( + _VoidOrder event, + Emitter emit, + ) async { + emit(const VoidOrderState.loading()); + + final result = await _orderRemoteDatasource.voidOrder( + orderId: event.orderId, + reason: event.reason, + type: event.type, + orderItems: event.orderItems, + ); + + result.fold( + (error) => emit(VoidOrderState.error(error)), + (success) => emit(const VoidOrderState.success()), + ); + } +} diff --git a/lib/presentation/void/bloc/void_order_bloc.freezed.dart b/lib/presentation/void/bloc/void_order_bloc.freezed.dart new file mode 100644 index 0000000..a0af50a --- /dev/null +++ b/lib/presentation/void/bloc/void_order_bloc.freezed.dart @@ -0,0 +1,889 @@ +// 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 'void_order_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$VoidOrderEvent { + String get orderId => throw _privateConstructorUsedError; + String get reason => throw _privateConstructorUsedError; + String get type => throw _privateConstructorUsedError; // "ALL" or "ITEM" + List get orderItems => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(String orderId, String reason, String type, + List orderItems) + voidOrder, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String orderId, String reason, String type, + List orderItems)? + voidOrder, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String orderId, String reason, String type, + List orderItems)? + voidOrder, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_VoidOrder value) voidOrder, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_VoidOrder value)? voidOrder, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_VoidOrder value)? voidOrder, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Create a copy of VoidOrderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $VoidOrderEventCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $VoidOrderEventCopyWith<$Res> { + factory $VoidOrderEventCopyWith( + VoidOrderEvent value, $Res Function(VoidOrderEvent) then) = + _$VoidOrderEventCopyWithImpl<$Res, VoidOrderEvent>; + @useResult + $Res call( + {String orderId, String reason, String type, List orderItems}); +} + +/// @nodoc +class _$VoidOrderEventCopyWithImpl<$Res, $Val extends VoidOrderEvent> + implements $VoidOrderEventCopyWith<$Res> { + _$VoidOrderEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of VoidOrderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? orderId = null, + Object? reason = null, + Object? type = null, + Object? orderItems = null, + }) { + return _then(_value.copyWith( + orderId: null == orderId + ? _value.orderId + : orderId // ignore: cast_nullable_to_non_nullable + as String, + reason: null == reason + ? _value.reason + : reason // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + orderItems: null == orderItems + ? _value.orderItems + : orderItems // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$VoidOrderImplCopyWith<$Res> + implements $VoidOrderEventCopyWith<$Res> { + factory _$$VoidOrderImplCopyWith( + _$VoidOrderImpl value, $Res Function(_$VoidOrderImpl) then) = + __$$VoidOrderImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String orderId, String reason, String type, List orderItems}); +} + +/// @nodoc +class __$$VoidOrderImplCopyWithImpl<$Res> + extends _$VoidOrderEventCopyWithImpl<$Res, _$VoidOrderImpl> + implements _$$VoidOrderImplCopyWith<$Res> { + __$$VoidOrderImplCopyWithImpl( + _$VoidOrderImpl _value, $Res Function(_$VoidOrderImpl) _then) + : super(_value, _then); + + /// Create a copy of VoidOrderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? orderId = null, + Object? reason = null, + Object? type = null, + Object? orderItems = null, + }) { + return _then(_$VoidOrderImpl( + orderId: null == orderId + ? _value.orderId + : orderId // ignore: cast_nullable_to_non_nullable + as String, + reason: null == reason + ? _value.reason + : reason // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as String, + orderItems: null == orderItems + ? _value._orderItems + : orderItems // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +class _$VoidOrderImpl implements _VoidOrder { + const _$VoidOrderImpl( + {required this.orderId, + required this.reason, + required this.type, + required final List orderItems}) + : _orderItems = orderItems; + + @override + final String orderId; + @override + final String reason; + @override + final String type; +// "ALL" or "ITEM" + final List _orderItems; +// "ALL" or "ITEM" + @override + List get orderItems { + if (_orderItems is EqualUnmodifiableListView) return _orderItems; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_orderItems); + } + + @override + String toString() { + return 'VoidOrderEvent.voidOrder(orderId: $orderId, reason: $reason, type: $type, orderItems: $orderItems)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$VoidOrderImpl && + (identical(other.orderId, orderId) || other.orderId == orderId) && + (identical(other.reason, reason) || other.reason == reason) && + (identical(other.type, type) || other.type == type) && + const DeepCollectionEquality() + .equals(other._orderItems, _orderItems)); + } + + @override + int get hashCode => Object.hash(runtimeType, orderId, reason, type, + const DeepCollectionEquality().hash(_orderItems)); + + /// Create a copy of VoidOrderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$VoidOrderImplCopyWith<_$VoidOrderImpl> get copyWith => + __$$VoidOrderImplCopyWithImpl<_$VoidOrderImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String orderId, String reason, String type, + List orderItems) + voidOrder, + }) { + return voidOrder(orderId, reason, type, orderItems); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String orderId, String reason, String type, + List orderItems)? + voidOrder, + }) { + return voidOrder?.call(orderId, reason, type, orderItems); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String orderId, String reason, String type, + List orderItems)? + voidOrder, + required TResult orElse(), + }) { + if (voidOrder != null) { + return voidOrder(orderId, reason, type, orderItems); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_VoidOrder value) voidOrder, + }) { + return voidOrder(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_VoidOrder value)? voidOrder, + }) { + return voidOrder?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_VoidOrder value)? voidOrder, + required TResult orElse(), + }) { + if (voidOrder != null) { + return voidOrder(this); + } + return orElse(); + } +} + +abstract class _VoidOrder implements VoidOrderEvent { + const factory _VoidOrder( + {required final String orderId, + required final String reason, + required final String type, + required final List orderItems}) = _$VoidOrderImpl; + + @override + String get orderId; + @override + String get reason; + @override + String get type; // "ALL" or "ITEM" + @override + List get orderItems; + + /// Create a copy of VoidOrderEvent + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$VoidOrderImplCopyWith<_$VoidOrderImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$VoidOrderState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $VoidOrderStateCopyWith<$Res> { + factory $VoidOrderStateCopyWith( + VoidOrderState value, $Res Function(VoidOrderState) then) = + _$VoidOrderStateCopyWithImpl<$Res, VoidOrderState>; +} + +/// @nodoc +class _$VoidOrderStateCopyWithImpl<$Res, $Val extends VoidOrderState> + implements $VoidOrderStateCopyWith<$Res> { + _$VoidOrderStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of VoidOrderState + /// 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 _$VoidOrderStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of VoidOrderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'VoidOrderState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements VoidOrderState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$VoidOrderStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of VoidOrderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'VoidOrderState.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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class _Loading implements VoidOrderState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$SuccessImplCopyWith<$Res> { + factory _$$SuccessImplCopyWith( + _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = + __$$SuccessImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SuccessImplCopyWithImpl<$Res> + extends _$VoidOrderStateCopyWithImpl<$Res, _$SuccessImpl> + implements _$$SuccessImplCopyWith<$Res> { + __$$SuccessImplCopyWithImpl( + _$SuccessImpl _value, $Res Function(_$SuccessImpl) _then) + : super(_value, _then); + + /// Create a copy of VoidOrderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SuccessImpl implements _Success { + const _$SuccessImpl(); + + @override + String toString() { + return 'VoidOrderState.success()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SuccessImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return success(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return success?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return success(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return success?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (success != null) { + return success(this); + } + return orElse(); + } +} + +abstract class _Success implements VoidOrderState { + const factory _Success() = _$SuccessImpl; +} + +/// @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 _$VoidOrderStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of VoidOrderState + /// 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 'VoidOrderState.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 VoidOrderState + /// 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({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function() success, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function()? success, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function()? success, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Success value) success, + required TResult Function(_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Success value)? success, + TResult? Function(_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Success value)? success, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements VoidOrderState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of VoidOrderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/void/bloc/void_order_event.dart b/lib/presentation/void/bloc/void_order_event.dart new file mode 100644 index 0000000..78b2557 --- /dev/null +++ b/lib/presentation/void/bloc/void_order_event.dart @@ -0,0 +1,11 @@ +part of 'void_order_bloc.dart'; + +@freezed +class VoidOrderEvent with _$VoidOrderEvent { + const factory VoidOrderEvent.voidOrder({ + required String orderId, + required String reason, + required String type, // "ALL" or "ITEM" + required List orderItems, + }) = _VoidOrder; +} diff --git a/lib/presentation/void/bloc/void_order_state.dart b/lib/presentation/void/bloc/void_order_state.dart new file mode 100644 index 0000000..b9842a0 --- /dev/null +++ b/lib/presentation/void/bloc/void_order_state.dart @@ -0,0 +1,9 @@ +part of 'void_order_bloc.dart'; + +@freezed +class VoidOrderState with _$VoidOrderState { + const factory VoidOrderState.initial() = _Initial; + const factory VoidOrderState.loading() = _Loading; + const factory VoidOrderState.success() = _Success; + const factory VoidOrderState.error(String message) = _Error; +} diff --git a/lib/presentation/void/dialog/confirm_void_dialog.dart b/lib/presentation/void/dialog/confirm_void_dialog.dart new file mode 100644 index 0000000..2963020 --- /dev/null +++ b/lib/presentation/void/dialog/confirm_void_dialog.dart @@ -0,0 +1,404 @@ +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/int_ext.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:flutter/material.dart'; + +class ConfirmVoidDialog extends StatelessWidget { + final String message; + final String voidType; + final Order order; + final Map selectedItemQuantities; + final String voidReason; + final Function() onTap; + const ConfirmVoidDialog( + {super.key, + required this.message, + required this.voidType, + required this.order, + required this.selectedItemQuantities, + required this.voidReason, + required this.onTap}); + + @override + Widget build(BuildContext context) { + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 8, + child: Container( + width: MediaQuery.of(context).size.width * 0.9, + constraints: BoxConstraints( + maxWidth: 600, + maxHeight: MediaQuery.of(context).size.height * 0.8, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Header + Container( + width: double.infinity, + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + const Color.fromARGB(255, 77, 45, 120), + AppColors.primary + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: Column( + children: [ + Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + shape: BoxShape.circle, + ), + child: Icon( + Icons.warning_rounded, + color: Colors.white, + size: 28, + ), + ), + SizedBox(height: 12), + Text( + 'Konfirmasi Void', + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'Tindakan ini tidak dapat dibatalkan', + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 12, + ), + ), + ], + ), + ), + + // Scrollable Content + Flexible( + child: SingleChildScrollView( + padding: EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Main message + Container( + width: double.infinity, + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey[200]!), + ), + child: Text( + message, + style: TextStyle( + fontSize: 14, + height: 1.4, + color: Colors.grey[800], + ), + ), + ), + + if (voidType == 'item' && + selectedItemQuantities.isNotEmpty) ...[ + SizedBox(height: 16), + + // Items section + Container( + width: double.infinity, + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: AppColors.primary, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + Container( + padding: EdgeInsets.all(6), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.2), + borderRadius: BorderRadius.circular(6), + ), + child: Icon( + Icons.list_alt_rounded, + color: AppColors.primary, + size: 16, + ), + ), + SizedBox(width: 8), + Text( + 'Item yang akan divoid:', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + color: AppColors.primary, + ), + ), + ], + ), + ), + Container( + constraints: BoxConstraints(maxHeight: 120), + child: Scrollbar( + child: SingleChildScrollView( + padding: EdgeInsets.only( + left: 16, right: 16, bottom: 16), + child: Column( + children: selectedItemQuantities.entries + .map((entry) { + final item = order.orderItems!.firstWhere( + (item) => item.id == entry.key); + return Container( + margin: EdgeInsets.only(bottom: 6), + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular(6), + boxShadow: [ + BoxShadow( + color: Colors.black + .withOpacity(0.03), + blurRadius: 2, + offset: Offset(0, 1), + ), + ], + ), + child: Row( + children: [ + Container( + width: 6, + height: 6, + decoration: BoxDecoration( + color: AppColors.primary, + shape: BoxShape.circle, + ), + ), + SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + item.productName ?? + 'Unknown Product', + style: TextStyle( + fontWeight: + FontWeight.w600, + fontSize: 12, + ), + maxLines: 1, + overflow: + TextOverflow.ellipsis, + ), + Text( + '${entry.value} qty', + style: TextStyle( + fontSize: 10, + color: Colors.grey[600], + ), + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: AppColors.primary + .withOpacity(0.2), + borderRadius: + BorderRadius.circular(4), + ), + child: Text( + ((item.unitPrice ?? 0) * + entry.value) + .currencyFormatRpV2, + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ), + ], + ), + ); + }).toList(), + ), + ), + ), + ), + ], + ), + ), + ], + + SizedBox(height: 16), + + // Reason section + Container( + width: double.infinity, + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.blue[50], + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.blue[200]!), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.all(6), + decoration: BoxDecoration( + color: Colors.blue[100], + borderRadius: BorderRadius.circular(6), + ), + child: Icon( + Icons.note_alt_rounded, + color: Colors.blue[700], + size: 16, + ), + ), + SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Alasan:', + style: TextStyle( + fontWeight: FontWeight.w600, + color: Colors.blue[800], + fontSize: 12, + ), + ), + SizedBox(height: 2), + Text( + voidReason, + style: TextStyle( + fontStyle: FontStyle.italic, + color: Colors.blue[700], + fontSize: 12, + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ), + + // Action buttons + Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(16), + bottomRight: Radius.circular(16), + ), + ), + child: Row( + children: [ + Expanded( + child: Container( + height: 44, + child: ElevatedButton( + onPressed: () => Navigator.pop(context), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.grey[300], + foregroundColor: Colors.grey[700], + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.close_rounded, size: 18), + SizedBox(width: 6), + Text( + 'Batal', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ), + SizedBox(width: 12), + Expanded( + child: SizedBox( + height: 44, + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + onTap(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + foregroundColor: Colors.white, + elevation: 2, + shadowColor: AppColors.primary.withOpacity(0.3), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.delete_forever_rounded, size: 18), + SizedBox(width: 6), + Text( + voidType == 'all' ? 'Void Pesanan' : 'Void Item', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/void/pages/success_void_page.dart b/lib/presentation/void/pages/success_void_page.dart new file mode 100644 index 0000000..c740d9c --- /dev/null +++ b/lib/presentation/void/pages/success_void_page.dart @@ -0,0 +1,636 @@ +import 'package:enaklo_pos/core/components/buttons.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/date_time_ext.dart'; +import 'package:enaklo_pos/core/extensions/int_ext.dart'; +import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart'; +import 'package:enaklo_pos/presentation/void/painter/pattern_painter.dart'; +import 'package:flutter/material.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; + +class SuccessVoidPage extends StatefulWidget { + final Order voidedOrder; + final String voidType; + final int voidAmount; + final List? voidedItems; + final String voidReason; + + const SuccessVoidPage({ + super.key, + required this.voidedOrder, + required this.voidType, + required this.voidAmount, + this.voidedItems, + required this.voidReason, + }); + + @override + State createState() => _SuccessVoidPageState(); +} + +class _SuccessVoidPageState extends State + with TickerProviderStateMixin { + final Color primaryColor = Color(0xFF36175E); + late AnimationController _animationController; + late Animation _scaleAnimation; + late Animation _fadeAnimation; + late Animation _slideAnimation; + + @override + void initState() { + super.initState(); + _setupAnimations(); + _startAnimations(); + } + + void _setupAnimations() { + _animationController = AnimationController( + duration: Duration(milliseconds: 1500), + vsync: this, + ); + + _scaleAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _animationController, + curve: Curves.elasticOut, + )); + + _fadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _animationController, + curve: Interval(0.3, 1.0, curve: Curves.easeInOut), + )); + + _slideAnimation = Tween( + begin: Offset(0, 0.5), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: _animationController, + curve: Interval(0.5, 1.0, curve: Curves.easeOutCubic), + )); + } + + void _startAnimations() { + _animationController.forward(); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.grey[50], + body: Stack( + children: [ + _buildBackgroundPattern(), + SafeArea( + child: Padding( + padding: EdgeInsets.all(24.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Left Panel - Success Animation & Message + Flexible( + flex: 2, + child: _buildSuccessPanel(), + ), + SizedBox(width: 24), + // Right Panel - Void Details (FIXED SCROLL) + Flexible( + flex: 3, + child: _buildDetailsPanel(), + ), + ], + ), + ), + ), + // REMOVED: Confetti overlay yang mengganggu scroll + ], + ), + ); + } + + Widget _buildSuccessPanel() { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 20, + offset: Offset(0, 10), + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(32), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + // Success Icon with Animation + ScaleTransition( + scale: _scaleAnimation, + child: Container( + width: 120, + height: 120, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Colors.green.shade400, Colors.green.shade600], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.green.withOpacity(0.3), + blurRadius: 20, + offset: Offset(0, 10), + ), + ], + ), + child: Icon( + Icons.check_rounded, + color: Colors.white, + size: 60, + ), + ), + ), + + SizedBox(height: 32), + + // Success Title + FadeTransition( + opacity: _fadeAnimation, + child: Text( + 'Void Berhasil!', + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, + color: Colors.green.shade700, + ), + textAlign: TextAlign.center, + ), + ), + + SizedBox(height: 16), + + // Success Message + SlideTransition( + position: _slideAnimation, + child: FadeTransition( + opacity: _fadeAnimation, + child: Text( + widget.voidType == 'all' + ? 'Seluruh pesanan telah dibatalkan dengan sukses' + : 'Item terpilih telah dibatalkan dengan sukses', + style: TextStyle( + fontSize: 16, + color: Colors.grey[600], + height: 1.5, + ), + textAlign: TextAlign.center, + ), + ), + ), + + SizedBox(height: 32), + + // Order Info Card + SlideTransition( + position: _slideAnimation, + child: FadeTransition( + opacity: _fadeAnimation, + child: Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: primaryColor.withOpacity(0.2)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: primaryColor.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.receipt_long, + color: primaryColor, + size: 20, + ), + ), + SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Pesanan #${widget.voidedOrder.orderNumber}', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: primaryColor, + ), + overflow: TextOverflow.ellipsis, + ), + Text( + 'Meja: ${widget.voidedOrder.tableNumber ?? 'N/A'}', + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + if (widget.voidAmount > 0) ...[ + SizedBox(height: 16), + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Jumlah Void:', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + Text( + (widget.voidAmount).currencyFormatRpV2, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.red.shade700, + ), + ), + ], + ), + ), + ], + ], + ), + ), + ), + ), + ], + ), + ), + ); + } + + // FIXED: Panel kanan dengan scroll yang benar + Widget _buildDetailsPanel() { + return LayoutBuilder( + builder: (context, constraints) { + return Container( + height: constraints.maxHeight, // PENTING: Gunakan height dari parent + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 20, + offset: Offset(0, 10), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header - Fixed + Container( + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + color: primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + child: Row( + children: [ + Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: primaryColor.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.info_outline, + color: primaryColor, + size: 24, + ), + ), + SizedBox(width: 12), + Expanded( + child: Text( + 'Detail Void', + style: TextStyle( + color: primaryColor, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + + // FIXED: Scrollable Content + Expanded( + child: Scrollbar( + thumbVisibility: true, + child: SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + padding: EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Void Type Info + _buildInfoCard( + icon: Icons.category, + title: 'Tipe Void', + content: widget.voidType == 'all' + ? 'Seluruh Pesanan' + : 'Item Tertentu', + ), + + SizedBox(height: 16), + + // Void Reason + _buildInfoCard( + icon: Icons.note_alt, + title: 'Alasan Void', + content: widget.voidReason, + ), + + SizedBox(height: 16), + + // Voided Items (if item void) + if (widget.voidType == 'item' && + widget.voidedItems != null) ...[ + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade50, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade300), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: EdgeInsets.all(6), + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(6), + ), + child: Icon( + Icons.list_alt, + color: Colors.grey.shade700, + size: 18, + ), + ), + SizedBox(width: 8), + Expanded( + child: Text( + 'Item yang Divoid', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.grey.shade800, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + SizedBox(height: 12), + // Items list (tidak nested scroll) + ...widget.voidedItems! + .map((item) => _buildVoidedItemCard(item)) + .toList(), + ], + ), + ), + SizedBox(height: 16), + ], + + // Timestamp + _buildInfoCard( + icon: Icons.access_time, + title: 'Waktu Void', + content: (DateTime.now()).toFormattedDate3(), + ), + + SizedBox(height: 32), + ], + ), + ), + ), + ), + + Padding( + padding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 20, + ), + child: Row( + children: [ + Expanded( + child: Button.outlined( + height: 50, + onPressed: () { + context.pushReplacement(DashboardPage()); + }, + icon: Icon(Icons.home, size: 20), + label: 'Kembali ke Beranda', + ), + ), + SpaceWidth(12), + Expanded( + child: Button.filled( + height: 50, + onPressed: _printVoidReceipt, + icon: Icon(Icons.print, size: 20), + label: 'Cetak Struk', + ), + ), + ], + ), + ), + ], + ), + ); + }, + ); + } + + Widget _buildInfoCard({ + required IconData icon, + required String title, + required String content, + }) { + return Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade50, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade300), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + icon, + color: Colors.grey.shade700, + size: 20, + ), + ), + SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.grey.shade800, + ), + ), + SizedBox(height: 4), + Text( + content, + style: TextStyle( + fontSize: 14, + color: Colors.grey[700], + height: 1.4, + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildVoidedItemCard(OrderItem item) { + return Container( + margin: EdgeInsets.only(bottom: 8), + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey.shade300), + ), + child: Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: Colors.grey.shade400, + shape: BoxShape.circle, + ), + ), + SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.productName ?? 'Unknown Product', + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 14, + ), + overflow: TextOverflow.ellipsis, + ), + Text( + 'Qty: ${item.quantity}', + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(6), + ), + child: Text( + (item.totalPrice ?? 0).currencyFormatRpV2, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.grey.shade700, + ), + ), + ), + ], + ), + ); + } + + Widget _buildBackgroundPattern() { + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Colors.grey.shade50, + Colors.grey.shade100, + ], + ), + ), + child: CustomPaint( + painter: PatternPainter(), + size: Size.infinite, + ), + ); + } + + void _printVoidReceipt() {} +} diff --git a/lib/presentation/void/pages/void_page.dart b/lib/presentation/void/pages/void_page.dart new file mode 100644 index 0000000..5de4ad0 --- /dev/null +++ b/lib/presentation/void/pages/void_page.dart @@ -0,0 +1,806 @@ +import 'package:enaklo_pos/core/components/buttons.dart'; +import 'package:enaklo_pos/core/components/spaces.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/int_ext.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:enaklo_pos/presentation/void/bloc/void_order_bloc.dart'; +import 'package:enaklo_pos/presentation/void/dialog/confirm_void_dialog.dart'; +import 'package:enaklo_pos/presentation/void/pages/success_void_page.dart'; +import 'package:enaklo_pos/presentation/void/widgets/product_card.dart'; +import 'package:enaklo_pos/presentation/void/widgets/void_radio.dart'; +import 'package:enaklo_pos/presentation/void/widgets/void_loading.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class VoidPage extends StatefulWidget { + final Order selectedOrder; + + const VoidPage({super.key, required this.selectedOrder}); + + @override + State createState() => _VoidPageState(); +} + +class _VoidPageState extends State { + String voidType = 'all'; // 'all' or 'item' + Map selectedItemQuantities = {}; // itemId -> voidQuantity + String voidReason = ''; + final TextEditingController reasonController = TextEditingController(); + final ScrollController _leftPanelController = ScrollController(); + final ScrollController _rightPanelController = ScrollController(); + + @override + void dispose() { + _leftPanelController.dispose(); + _rightPanelController.dispose(); + reasonController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + state.when( + initial: () {}, + loading: () { + // Show loading indicator if needed + }, + success: () { + context.pushReplacement( + SuccessVoidPage( + voidedOrder: widget.selectedOrder, + voidType: voidType, + voidAmount: _calculateVoidAmount(), + voidReason: voidReason, + voidedItems: voidType == 'item' ? _getVoidedItems() : null, + ), + ); + }, + error: (message) { + _showErrorDialog(message); + }, + ); + }, + child: Scaffold( + backgroundColor: Colors.grey[100], + body: BlocBuilder( + builder: (context, state) { + return Stack( + children: [ + OrientationBuilder( + builder: (context, orientation) { + return Padding( + padding: EdgeInsets.all(24.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Left Panel - Order Details & Items + Expanded( + flex: 3, + child: _buildOrderDetailsPanel(), + ), + SpaceWidth(24), + // Right Panel - Void Configuration + Expanded( + flex: 2, + child: _buildVoidConfigPanel(), + ), + ], + ), + ); + }, + ), + // Loading Overlay + state.when( + initial: () => SizedBox.shrink(), + loading: () => VoidLoading(), + success: () => SizedBox.shrink(), + error: (message) => SizedBox.shrink(), + ), + ], + ); + }, + ), + ), + ); + } + + Widget _buildOrderDetailsPanel() { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Order Header - Fixed + Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.1), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + ), + ), + child: Row( + children: [ + Icon(Icons.receipt_long, color: AppColors.primary, size: 24), + SpaceWidth(12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Pesanan #${widget.selectedOrder.orderNumber}', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + Text( + 'Meja: ${widget.selectedOrder.tableNumber ?? 'N/A'} • ${widget.selectedOrder.orderType ?? 'N/A'}', + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: _getStatusColor(widget.selectedOrder.status) + .withOpacity(0.2), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + widget.selectedOrder.status?.toUpperCase() ?? 'UNKNOWN', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: _getStatusColor(widget.selectedOrder.status), + ), + ), + ), + ], + ), + ), + + // Scrollable Content + Expanded( + child: Scrollbar( + controller: _leftPanelController, + thumbVisibility: true, + child: SingleChildScrollView( + controller: _leftPanelController, + padding: EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Order Summary - Fixed in scroll + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey[300]!), + ), + child: Column( + children: [ + _buildSummaryRow( + 'Subtotal:', + (widget.selectedOrder.subtotal ?? 0) + .currencyFormatRpV2), + _buildSummaryRow( + 'Pajak:', + (widget.selectedOrder.taxAmount ?? 0) + .currencyFormatRpV2), + _buildSummaryRow('Diskon:', + '- ${(widget.selectedOrder.discountAmount ?? 0).currencyFormatRpV2}'), + Divider(thickness: 1), + _buildSummaryRow( + 'Total:', + (widget.selectedOrder.totalAmount ?? 0) + .currencyFormatRpV2, + isTotal: true, + ), + if (voidType == 'item' && + selectedItemQuantities.isNotEmpty) ...[ + Divider(thickness: 1, color: Colors.red[300]), + _buildSummaryRow( + 'Total Void:', + '- ${(_calculateVoidAmount().currencyFormatRpV2)}', + isVoid: true, + ), + ], + ], + ), + ), + + SpaceHeight(24), + + // Order Items Section Title + Row( + children: [ + Icon(Icons.shopping_cart, + color: AppColors.primary, size: 20), + SpaceWidth(8), + Text( + 'Produk Pesanan (${_getPendingItem().length})', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + ], + ), + + SpaceHeight(16), + + // Order Items List - Scrollable + ...List.generate( + _getPendingItem().length, + (index) { + final item = _getPendingItem()[index]; + final voidQty = selectedItemQuantities[item.id] ?? 0; + final isSelected = voidQty > 0; + final canSelect = voidType == 'item'; + + return VoidProductCard( + isSelected: isSelected, + item: item, + voidQty: voidQty, + canSelect: canSelect, + onTapDecrease: voidQty > 0 + ? () { + setState(() { + if (voidQty == 1) { + selectedItemQuantities.remove(item.id); + } else { + selectedItemQuantities[item.id!] = + voidQty - 1; + } + }); + } + : null, + onTapIncrease: voidQty < (item.quantity ?? 0) + ? () { + setState(() { + selectedItemQuantities[item.id!] = + voidQty + 1; + }); + } + : null, + onTapAll: () { + setState(() { + selectedItemQuantities[item.id!] = + item.quantity ?? 0; + }); + }, + onTapClear: voidQty > 0 + ? () { + setState(() { + selectedItemQuantities.remove(item.id); + }); + } + : null, + ); + }, + ), + ], + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildVoidConfigPanel() { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header - Fixed + Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.1), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + ), + ), + child: Row( + children: [ + Icon(Icons.cancel, color: Colors.red, size: 24), + SpaceWidth(12), + Text( + 'Konfigurasi Void', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.red, + ), + ), + ], + ), + ), + + // Scrollable Content + Expanded( + child: Scrollbar( + controller: _rightPanelController, + thumbVisibility: true, + child: SingleChildScrollView( + controller: _rightPanelController, + padding: EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Void Type Selection + Text( + 'Tipe Void *', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + SpaceHeight(12), + + // Void All Option + VoidRadio( + voidType: voidType, + value: 'all', + title: 'Batalkan Seluruh Pesanan', + subtitle: "Batalkan pesanan lengkap dan semua item", + onChanged: (String? value) { + setState(() { + voidType = value!; + selectedItemQuantities.clear(); + }); + }, + ), + + SpaceHeight(12), + + // Void Items Option + VoidRadio( + voidType: voidType, + value: 'item', + title: 'Batalkan Barang/Jumlah Tertentu', + subtitle: + "Mengurangi atau membatalkan jumlah item tertentu", + onChanged: (String? value) { + setState(() { + voidType = value!; + selectedItemQuantities.clear(); + }); + }, + ), + + SpaceHeight(24), + + // Selected Items Summary (only show for item void) + if (voidType == 'item') ...[ + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: selectedItemQuantities.isEmpty + ? Colors.orange[50] + : Colors.green[50], + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: selectedItemQuantities.isEmpty + ? Colors.orange[300]! + : Colors.green[300]!, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + selectedItemQuantities.isEmpty + ? Icons.warning + : Icons.check_circle, + color: selectedItemQuantities.isEmpty + ? Colors.orange[700] + : Colors.green[700], + size: 20, + ), + SpaceWidth(8), + Expanded( + child: Text( + selectedItemQuantities.isEmpty + ? 'Silakan pilih item dan jumlah yang akan dibatalkan' + : 'Item yang dipilih untuk dibatalkan:', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: selectedItemQuantities.isEmpty + ? Colors.orange[700] + : Colors.green[700], + ), + ), + ), + ], + ), + if (selectedItemQuantities.isNotEmpty) ...[ + SpaceHeight(12), + Container( + constraints: BoxConstraints(maxHeight: 150), + child: Scrollbar( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: selectedItemQuantities.entries + .map((entry) { + final item = widget + .selectedOrder.orderItems! + .firstWhere( + (item) => item.id == entry.key); + return Container( + margin: EdgeInsets.only(bottom: 8), + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular(6), + border: Border.all( + color: Colors.green[200]!), + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + item.productName ?? + 'Unknown', + style: TextStyle( + fontSize: 12, + fontWeight: + FontWeight.w500, + ), + ), + Text( + 'Qty: ${entry.value}', + style: TextStyle( + fontSize: 11, + color: Colors.grey[600], + ), + ), + ], + ), + ), + Text( + ((item.unitPrice ?? 0) * + entry.value) + .currencyFormatRpV2, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.red[700], + ), + ), + ], + ), + ); + }).toList(), + ), + ), + ), + ), + Divider(height: 16, thickness: 1), + Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Jumlah Total Void:', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + Text( + _calculateVoidAmount().currencyFormatRpV2, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.red[700], + ), + ), + ], + ), + ), + ], + ], + ), + ), + SpaceHeight(24), + ], + + // Void Reason + Text( + 'Alasan Void *', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColors.primary, + ), + ), + SpaceHeight(8), + TextField( + controller: reasonController, + maxLines: 4, + decoration: InputDecoration( + hintText: 'Harap berikan alasan untuk membatalkan...', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide(color: Colors.grey[300]!), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: + BorderSide(color: AppColors.primary, width: 2), + ), + contentPadding: EdgeInsets.all(16), + ), + onChanged: (value) { + setState(() { + voidReason = value; + }); + }, + ), + + SpaceHeight(32), + + // Action Buttons + Row( + children: [ + Expanded( + child: Button.outlined( + onPressed: () => context.pop(), + label: 'Batal', + ), + ), + SpaceWidth(12), + Expanded( + child: Button.filled( + onPressed: _canProcessVoid() ? _processVoid : null, + label: voidType == 'all' + ? 'Void Pesanan' + : 'Void Produk', + ), + ), + ], + ), + ], + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildSummaryRow(String label, String value, + {bool isTotal = false, bool isVoid = false}) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: TextStyle( + fontSize: isTotal ? 16 : 14, + fontWeight: isTotal ? FontWeight.bold : FontWeight.normal, + color: isVoid + ? Colors.red + : (isTotal ? AppColors.primary : Colors.grey[700]), + ), + ), + Text( + value, + style: TextStyle( + fontSize: isTotal ? 16 : 14, + fontWeight: isTotal ? FontWeight.bold : FontWeight.w500, + color: isVoid + ? Colors.red + : (isTotal ? AppColors.primary : Colors.grey[700]), + ), + ), + ], + ), + ); + } + + Color _getStatusColor(String? status) { + switch (status?.toLowerCase()) { + case 'completed': + return Colors.green; + case 'pending': + return Colors.orange; + case 'cancelled': + return Colors.red; + case 'processing': + return Colors.blue; + default: + return Colors.grey; + } + } + + int _calculateVoidAmount() { + int total = 0; + selectedItemQuantities.forEach((itemId, voidQty) { + final item = widget.selectedOrder.orderItems! + .firstWhere((item) => item.id == itemId); + total += (item.unitPrice ?? 0) * voidQty; + }); + return total; + } + + bool _canProcessVoid() { + if (voidReason.trim().isEmpty) return false; + if (voidType == 'item' && selectedItemQuantities.isEmpty) return false; + return true; + } + + List _getPendingItem() { + return widget.selectedOrder.orderItems! + .where((item) => item.status == 'pending') + .toList(); + } + + List _getVoidedItems() { + List voidedItems = []; + + selectedItemQuantities.forEach((itemId, voidQty) { + final originalItem = widget.selectedOrder.orderItems! + .firstWhere((item) => item.id == itemId); + + // Buat OrderItem baru dengan quantity yang di-void + voidedItems.add(OrderItem( + id: originalItem.id, + orderId: originalItem.orderId, + productId: originalItem.productId, + productName: originalItem.productName, + productVariantId: originalItem.productVariantId, + productVariantName: originalItem.productVariantName, + quantity: voidQty, // Hanya quantity yang di-void + unitPrice: originalItem.unitPrice, + totalPrice: (originalItem.unitPrice ?? 0) * voidQty, + modifiers: originalItem.modifiers, + notes: originalItem.notes, + status: originalItem.status, + createdAt: originalItem.createdAt, + updatedAt: originalItem.updatedAt, + )); + }); + + return voidedItems; + } + + void _processVoid() { + String confirmMessage; + if (voidType == 'all') { + confirmMessage = + 'Apakah Anda yakin ingin membatalkan seluruh pesanan #${widget.selectedOrder.orderNumber}?\n\nIni akan membatalkan semua item dalam pesanan.'; + } else { + int totalItems = + selectedItemQuantities.values.fold(0, (sum, qty) => sum + qty); + confirmMessage = + 'Apakah Anda yakin ingin membatalkan $totalItems item dari pesanan #${widget.selectedOrder.orderNumber}?\n\nJumlah Batal: ${(_calculateVoidAmount()).currencyFormatRpV2}'; + } + + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return ConfirmVoidDialog( + message: confirmMessage, + onTap: _performVoid, + order: widget.selectedOrder, + voidType: voidType, + selectedItemQuantities: selectedItemQuantities, + voidReason: voidReason, + ); + }, + ); + } + + void _performVoid() { + // Prepare order items for void + List voidItems = []; + + if (voidType == 'item') { + selectedItemQuantities.forEach((itemId, voidQty) { + final originalItem = widget.selectedOrder.orderItems! + .firstWhere((item) => item.id == itemId); + + // Create new OrderItem with void quantity + voidItems.add(OrderItem( + id: originalItem.id, + orderId: originalItem.orderId, + productId: originalItem.productId, + productName: originalItem.productName, + productVariantId: originalItem.productVariantId, + productVariantName: originalItem.productVariantName, + quantity: voidQty, // This is the void quantity + unitPrice: originalItem.unitPrice, + totalPrice: (originalItem.unitPrice ?? 0) * voidQty, + modifiers: originalItem.modifiers, + notes: originalItem.notes, + status: originalItem.status, + createdAt: originalItem.createdAt, + updatedAt: originalItem.updatedAt, + )); + }); + } + + // Trigger void order event + context.read().add( + VoidOrderEvent.voidOrder( + orderId: widget.selectedOrder.id!, + reason: voidReason, + type: voidType.toUpperCase(), + orderItems: voidItems, + ), + ); + } + + void _showErrorDialog(String message) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Row( + children: [ + Icon(Icons.error, color: Colors.red), + SpaceWidth(8), + Text('Void Gagal'), + ], + ), + content: Text(message), + actions: [ + ElevatedButton( + onPressed: () => Navigator.pop(context), + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + foregroundColor: Colors.white, + ), + child: Text('OK'), + ), + ], + ); + }, + ); + } +} diff --git a/lib/presentation/void/painter/pattern_painter.dart b/lib/presentation/void/painter/pattern_painter.dart new file mode 100644 index 0000000..92f0ebb --- /dev/null +++ b/lib/presentation/void/painter/pattern_painter.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +class PatternPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = Colors.white.withOpacity(0.05) + ..strokeWidth = 1; + + // Simplified pattern - tidak terlalu banyak + for (int i = 0; i < size.width; i += 100) { + for (int j = 0; j < size.height; j += 100) { + canvas.drawCircle(Offset(i.toDouble(), j.toDouble()), 1, paint); + } + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} diff --git a/lib/presentation/void/widgets/product_card.dart b/lib/presentation/void/widgets/product_card.dart new file mode 100644 index 0000000..5709b29 --- /dev/null +++ b/lib/presentation/void/widgets/product_card.dart @@ -0,0 +1,293 @@ +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/core/extensions/int_ext.dart'; +import 'package:enaklo_pos/data/models/response/order_response_model.dart'; +import 'package:flutter/material.dart'; + +class VoidProductCard extends StatelessWidget { + final bool isSelected; + final OrderItem item; + final int voidQty; + final bool canSelect; + final Function()? onTapDecrease; + final Function()? onTapIncrease; + final Function()? onTapAll; + final Function()? onTapClear; + const VoidProductCard({ + super.key, + required this.isSelected, + required this.item, + required this.voidQty, + required this.canSelect, + required this.onTapDecrease, + required this.onTapIncrease, + required this.onTapAll, + required this.onTapClear, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(bottom: 12), + decoration: BoxDecoration( + color: isSelected ? AppColors.primary.withOpacity(0.1) : Colors.white, + border: Border.all( + color: isSelected ? AppColors.primary : Colors.grey[300]!, + width: isSelected ? 2 : 1, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + children: [ + Row( + children: [ + // Product Icon + Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: AppColors.primary.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.fastfood, + color: AppColors.primary, + size: 24, + ), + ), + SizedBox(width: 12), + + // Product Info + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.productName ?? 'Produk Tidak Diketahui', + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + ), + ), + if (item.productVariantName != null) + Text( + 'Varian: ${item.productVariantName}', + style: + TextStyle(fontSize: 12, color: Colors.grey[600]), + ), + SizedBox(height: 4), + Row( + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: Colors.blue.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + 'Qty: ${item.quantity}', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.blue[700], + ), + ), + ), + SizedBox(width: 8), + Text( + (item.unitPrice ?? 0).currencyFormatRpV2, + style: TextStyle( + fontSize: 12, color: Colors.grey[600]), + ), + ], + ), + if (item.notes != null && item.notes!.isNotEmpty) + Padding( + padding: EdgeInsets.only(top: 4), + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: Colors.orange.withOpacity(0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + 'Catatan: ${item.notes}', + style: TextStyle( + fontSize: 11, + color: Colors.orange[700], + ), + ), + ), + ), + ], + ), + ), + + // Price + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + (item.totalPrice ?? 0).currencyFormatRpV2, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: AppColors.primary, + ), + ), + if (isSelected) + Container( + padding: + EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.red.withOpacity(0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + 'Void: ${((item.unitPrice ?? 0) * voidQty).currencyFormatRpV2}', + style: TextStyle( + fontSize: 12, + color: Colors.red[700], + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ], + ), + + // Quantity Controls (only show for item void) + if (canSelect) ...[ + SizedBox(height: 16), + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey[300]!), + ), + child: Row( + children: [ + Text( + 'Kuantitas:', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.grey[700], + ), + ), + Spacer(), + + // Quantity Controls + Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.grey[300]!), + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + // Decrease Button + InkWell( + onTap: onTapDecrease, + child: Container( + padding: EdgeInsets.all(8), + child: Icon( + Icons.remove, + size: 16, + color: voidQty > 0 + ? AppColors.primary + : Colors.grey[400], + ), + ), + ), + + // Quantity Display + Container( + padding: EdgeInsets.symmetric( + horizontal: 16, vertical: 8), + decoration: BoxDecoration( + border: Border.symmetric( + vertical: BorderSide(color: Colors.grey[300]!), + ), + ), + child: Text( + '$voidQty', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + + // Increase Button + InkWell( + onTap: onTapIncrease, + child: Container( + padding: EdgeInsets.all(8), + child: Icon( + Icons.add, + size: 16, + color: voidQty < (item.quantity ?? 0) + ? AppColors.primary + : Colors.grey[400], + ), + ), + ), + ], + ), + ), + + SizedBox(width: 12), + + // Quick Actions + Row( + mainAxisSize: MainAxisSize.min, + children: [ + TextButton( + onPressed: onTapAll, + style: TextButton.styleFrom( + padding: EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + minimumSize: Size(0, 0), + ), + child: Text( + 'Semua', + style: TextStyle( + fontSize: 12, + color: AppColors.primary, + ), + ), + ), + TextButton( + onPressed: onTapClear, + style: TextButton.styleFrom( + padding: EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + minimumSize: Size(0, 0), + ), + child: Text( + 'Hapus', + style: TextStyle( + fontSize: 12, + color: + voidQty > 0 ? Colors.red : Colors.grey[400], + ), + ), + ), + ], + ), + ], + ), + ), + ], + ], + ), + ), + ); + } +} diff --git a/lib/presentation/void/widgets/void_loading.dart b/lib/presentation/void/widgets/void_loading.dart new file mode 100644 index 0000000..3fdcfab --- /dev/null +++ b/lib/presentation/void/widgets/void_loading.dart @@ -0,0 +1,40 @@ +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:flutter/material.dart'; + +class VoidLoading extends StatelessWidget { + const VoidLoading({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.black.withOpacity(0.3), + child: Center( + child: Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(AppColors.primary), + ), + SizedBox(height: 16), + Text( + 'Processing void...', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/void/widgets/void_radio.dart b/lib/presentation/void/widgets/void_radio.dart new file mode 100644 index 0000000..88b7e14 --- /dev/null +++ b/lib/presentation/void/widgets/void_radio.dart @@ -0,0 +1,44 @@ +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:flutter/material.dart'; + +class VoidRadio extends StatelessWidget { + final String voidType; + final String value; + final Function(String?)? onChanged; + final String title; + final String subtitle; + const VoidRadio({ + super.key, + required this.voidType, + required this.value, + this.onChanged, + required this.title, + required this.subtitle, + }); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + border: Border.all( + color: voidType == value ? AppColors.primary : Colors.grey[300]!, + width: voidType == value ? 2 : 1, + ), + borderRadius: BorderRadius.circular(8), + ), + child: RadioListTile( + title: Text( + title, + style: TextStyle(fontWeight: FontWeight.w600), + ), + subtitle: Text( + subtitle, + style: TextStyle(fontSize: 12, color: Colors.grey[600]), + ), + value: value, + groupValue: voidType, + activeColor: AppColors.primary, + onChanged: onChanged), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 5cc967c..3efa4dc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -22,6 +22,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.11.0" + another_flushbar: + dependency: "direct main" + description: + name: another_flushbar + sha256: "19bf9520230ec40b300aaf9dd2a8fefcb277b25ecd1c4838f530566965befc2a" + url: "https://pub.dev" + source: hosted + version: "1.12.30" archive: dependency: transitive description: @@ -46,14 +54,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" - barcode: - dependency: transitive + awesome_dio_interceptor: + dependency: "direct main" description: - name: barcode - sha256: ab180ce22c6555d77d45f0178a523669db67f95856e3378259ef2ffeb43e6003 + name: awesome_dio_interceptor + sha256: "4aef4adfdd9d8fda159870277b898a97986c6624baaf42f8a986d3130860d007" url: "https://pub.dev" source: hosted - version: "2.2.8" + version: "1.3.0" + barcode: + dependency: "direct main" + description: + name: barcode + sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4" + url: "https://pub.dev" + source: hosted + version: "2.2.9" + barcode_image: + dependency: "direct main" + description: + name: barcode_image + sha256: b93f6ec7fa3d29373cff404ea99fb2afe895d2b4f36704e563f861e7a0818483 + url: "https://pub.dev" + source: hosted + version: "2.0.3" bidi: dependency: transitive description: @@ -222,6 +246,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + colorize: + dependency: transitive + description: + name: colorize + sha256: "584746cd6ba1cba0633b6720f494fe6f9601c4170f0666c1579d2aa2a61071ba" + url: "https://pub.dev" + source: hosted + version: "3.0.0" connectivity_plus: dependency: "direct main" description: @@ -326,6 +358,38 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.2" + dio: + dependency: "direct main" + description: + name: dio + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + url: "https://pub.dev" + source: hosted + version: "5.8.0+1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + dropdown_search: + dependency: "direct main" + description: + name: dropdown_search + sha256: "55106e8290acaa97ed15bea1fdad82c3cf0c248dd410e651f5a8ac6870f783ab" + url: "https://pub.dev" + source: hosted + version: "5.0.6" + equatable: + dependency: transitive + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" esc_pos_utils_plus: dependency: "direct main" description: @@ -398,6 +462,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: "577aeac8ca414c25333334d7c4bb246775234c0e44b38b10a82b559dd4d764e7" + url: "https://pub.dev" + source: hosted + version: "1.0.0" flutter: dependency: "direct main" description: flutter @@ -455,10 +527,10 @@ packages: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" url: "https://pub.dev" source: hosted - version: "0.13.1" + version: "0.14.4" flutter_lints: dependency: "direct dev" description: @@ -483,6 +555,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.6.1" + flutter_screenutil: + dependency: "direct main" + description: + name: flutter_screenutil + sha256: "8239210dd68bee6b0577aa4a090890342d04a136ce1c81f98ee513fc0ce891de" + url: "https://pub.dev" + source: hosted + version: "5.9.3" flutter_svg: dependency: "direct main" description: @@ -541,14 +621,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" - google_fonts: - dependency: "direct main" - description: - name: google_fonts - sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82 - url: "https://pub.dev" - source: hosted - version: "6.2.1" graphs: dependency: transitive description: @@ -1500,4 +1572,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.7.0-0 <4.0.0" - flutter: ">=3.24.0" + flutter: ">=3.27.4" diff --git a/pubspec.yaml b/pubspec.yaml index ac08c96..4f4e6cc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,8 +1,8 @@ name: enaklo_pos -description: "EnakloPOS - Point of Sale Application" +description: "ApskelPOS - Point of Sale Application" # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 @@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: '>=3.2.4 <4.0.0' + sdk: ">=3.2.4 <4.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -41,7 +41,6 @@ dependencies: flutter_svg: ^2.0.9 freezed: ^2.4.6 freezed_annotation: ^2.4.1 - google_fonts: ^6.1.0 hive: ^2.2.3 horizontal_data_table: ^4.3.1 http: ^1.2.0 @@ -59,6 +58,14 @@ dependencies: sqflite: ^2.3.2 widgets_to_image: ^1.0.0 flutter_esc_pos_network: ^1.0.3 + flutter_screenutil: ^5.9.3 + dio: ^5.8.0+1 + awesome_dio_interceptor: ^1.3.0 + another_flushbar: ^1.12.30 + dropdown_search: ^5.0.6 + fl_chart: ^1.0.0 + barcode: ^2.2.9 + barcode_image: ^2.0.3 # imin_printer: ^0.6.10 dev_dependencies: @@ -67,7 +74,7 @@ dev_dependencies: flutter_lints: ^5.0.0 flutter_test: sdk: flutter - flutter_launcher_icons: ^0.13.1 + flutter_launcher_icons: ^0.14.4 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter packages. @@ -98,7 +105,6 @@ flutter_launcher_icons: image_path: "assets/logo/logo.png" flutter: - # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. @@ -118,17 +124,16 @@ flutter: # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages + fonts: + - family: Quicksand + fonts: + - asset: assets/fonts/quicksand/Quicksand-Light.ttf + weight: 300 + - asset: assets/fonts/quicksand/Quicksand-Regular.ttf + weight: 400 + - asset: assets/fonts/quicksand/Quicksand-Medium.ttf + weight: 500 + - asset: assets/fonts/quicksand/Quicksand-SemiBold.ttf + weight: 600 + - asset: assets/fonts/quicksand/Quicksand-Bold.ttf + weight: 700 diff --git a/web/favicon.png b/web/favicon.png index c09cfc2..d1c02c4 100644 Binary files a/web/favicon.png and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png index bbe6bc1..43a4915 100644 Binary files a/web/icons/Icon-192.png and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png index 4ad0bb0..c2b3de3 100644 Binary files a/web/icons/Icon-512.png and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png index bbe6bc1..43a4915 100644 Binary files a/web/icons/Icon-maskable-192.png and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png index 4ad0bb0..c2b3de3 100644 Binary files a/web/icons/Icon-maskable-512.png and b/web/icons/Icon-maskable-512.png differ