Compare commits
13
Commits
b98462ee8c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36bc9e0f9e | ||
|
|
ac991431c8 | ||
|
|
587caa44c5 | ||
|
|
2e4c77888e | ||
|
|
f9bfb69254 | ||
|
|
d02d3b5fbd | ||
|
|
7661319b4f | ||
|
|
75415cc6ff | ||
|
|
843c11b200 | ||
|
|
0917c5132b | ||
|
|
b07af60778 | ||
|
|
8d801e52d9 | ||
|
|
7137cd2335 |
@@ -18,6 +18,9 @@ jobs:
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
# Dipin biar CI nggak ikut naik versi diam-diam.
|
||||
# Flutter 3.47 mensyaratkan iOS deployment target minimal 15.0.
|
||||
flutter-version: "3.47.0"
|
||||
cache: true
|
||||
|
||||
- name: Install Dependencies
|
||||
@@ -69,7 +72,7 @@ jobs:
|
||||
- name: Install CocoaPods Dependencies
|
||||
run: |
|
||||
cd ios
|
||||
rm -f Podfile.lock
|
||||
rm -rf Pods Podfile.lock
|
||||
pod install --repo-update
|
||||
|
||||
# ── Build & Archive ─────────────────────────────────────────────────────
|
||||
|
||||
@@ -12,7 +12,7 @@ A POS (Point of Sale) application for business owners, built with Flutter. The p
|
||||
- Architecture Overview
|
||||
- Project Structure
|
||||
- Key Dependencies & Purpose
|
||||
- Code Generation
|
||||
- Code Generation (see docs/codegen.md)
|
||||
- Internationalization (i18n)
|
||||
- Theming & Assets
|
||||
- Environment Configuration (`env.dart`)
|
||||
@@ -40,6 +40,7 @@ A POS (Point of Sale) application for business owners, built with Flutter. The p
|
||||
|
||||
- Flutter SDK installed as per the official guide (`https://flutter.dev/docs/get-started/install`)
|
||||
- Dart version per constraint: ^3.8.1
|
||||
- For code generation only: FVM + Flutter 3.41.9 (`dart pub global activate fvm && fvm install 3.41.9`) — see [docs/codegen.md](docs/codegen.md)
|
||||
|
||||
### Installation
|
||||
|
||||
@@ -51,8 +52,16 @@ flutter pub get
|
||||
|
||||
### Code generation (required after clone or when annotations change)
|
||||
|
||||
```bash
|
||||
flutter pub run build_runner build --delete-conflicting-outputs
|
||||
> **Do not run codegen with Flutter 3.47 / Dart 3.13** — every builder crashes with
|
||||
> `Missing implementation of visitDotShorthandPropertyAccess`. Codegen must run on Flutter 3.41.9
|
||||
> (Dart 3.11.5) via FVM. Full setup, commands, and troubleshooting: **[docs/codegen.md](docs/codegen.md)**.
|
||||
|
||||
```powershell
|
||||
$FVM = "$env:USERPROFILE\fvm\versions\3.41.9\bin"
|
||||
|
||||
& "$FVM\flutter.bat" pub get
|
||||
& "$FVM\dart.bat" run build_runner build --delete-conflicting-outputs
|
||||
flutter pub get # restore lockfile resolution for the global SDK
|
||||
```
|
||||
|
||||
> Re-run whenever you change files using `@RoutePage()`, `@injectable`, `@freezed`, or `@JsonSerializable`.
|
||||
@@ -179,14 +188,27 @@ Refer to `pubspec.yaml` for full version constraints.
|
||||
|
||||
## Code Generation
|
||||
|
||||
**Full guide: [docs/codegen.md](docs/codegen.md)** — read it before your first codegen run.
|
||||
|
||||
Codegen runs on a pinned SDK (Flutter 3.41.9 / Dart 3.11.5 via FVM), not on the global Flutter 3.47:
|
||||
`freezed 2.5.8` caps `analyzer` below 8.0.0, and analyzer 7.7.1 crashes on the dot-shorthand syntax
|
||||
used by Dart 3.13 sources. The app itself still runs and builds with the global SDK.
|
||||
|
||||
Common commands:
|
||||
|
||||
```bash
|
||||
flutter pub run build_runner build --delete-conflicting-outputs
|
||||
```powershell
|
||||
$FVM = "$env:USERPROFILE\fvm\versions\3.41.9\bin"
|
||||
|
||||
& "$FVM\flutter.bat" pub get
|
||||
& "$FVM\dart.bat" run build_runner build --delete-conflicting-outputs
|
||||
# or watch
|
||||
flutter pub run build_runner watch --delete-conflicting-outputs
|
||||
& "$FVM\dart.bat" run build_runner watch --delete-conflicting-outputs
|
||||
flutter pub get # restore lockfile resolution for the global SDK
|
||||
```
|
||||
|
||||
> `--delete-conflicting-outputs` wipes every generated file first. If you cancel the build midway,
|
||||
> restore them with `git checkout` — see the troubleshooting table in [docs/codegen.md](docs/codegen.md).
|
||||
|
||||
### Asset generation (flutter_gen_runner)
|
||||
|
||||
`flutter_gen_runner` is integrated with `build_runner`. Running the commands above will also generate typed accessors for assets based on the `flutter_gen` section in `pubspec.yaml`:
|
||||
@@ -255,15 +277,20 @@ flutter pub get
|
||||
flutter format .
|
||||
flutter analyze
|
||||
|
||||
# Code generation (single run / watch)
|
||||
flutter pub run build_runner build --delete-conflicting-outputs
|
||||
flutter pub run build_runner watch --delete-conflicting-outputs
|
||||
# Localization (safe on the global SDK)
|
||||
flutter gen-l10n
|
||||
|
||||
# Run by device
|
||||
flutter devices
|
||||
flutter run -d <device-id>
|
||||
```
|
||||
|
||||
Code generation uses the pinned FVM SDK — see [docs/codegen.md](docs/codegen.md):
|
||||
|
||||
```powershell
|
||||
& "$env:USERPROFILE\fvm\versions\3.41.9\bin\dart.bat" run build_runner build --delete-conflicting-outputs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Additional Notes
|
||||
@@ -316,7 +343,7 @@ Aplikasi Point of Sale (POS) untuk pemilik usaha, dibangun dengan Flutter. Proye
|
||||
- Arsitektur & Alur
|
||||
- Struktur Proyek
|
||||
- Dependensi Utama & Fungsinya
|
||||
- Code Generation
|
||||
- Code Generation (lihat docs/codegen.md)
|
||||
- Internationalization (i18n)
|
||||
- Theming & Assets
|
||||
- Konfigurasi Lingkungan (`env.dart`)
|
||||
@@ -344,6 +371,7 @@ Aplikasi Point of Sale (POS) untuk pemilik usaha, dibangun dengan Flutter. Proye
|
||||
|
||||
- Flutter SDK terpasang sesuai panduan resmi (`https://flutter.dev/docs/get-started/install`)
|
||||
- Versi Dart sesuai constraint: ^3.8.1
|
||||
- Khusus untuk code generation: FVM + Flutter 3.41.9 (`dart pub global activate fvm && fvm install 3.41.9`) — lihat [docs/codegen.md](docs/codegen.md)
|
||||
|
||||
### Instalasi
|
||||
|
||||
@@ -355,8 +383,16 @@ flutter pub get
|
||||
|
||||
### Generate kode (wajib setelah clone atau mengubah anotasi)
|
||||
|
||||
```bash
|
||||
flutter pub run build_runner build --delete-conflicting-outputs
|
||||
> **Jangan jalankan codegen dengan Flutter 3.47 / Dart 3.13** — semua builder crash dengan
|
||||
> `Missing implementation of visitDotShorthandPropertyAccess`. Codegen harus memakai Flutter 3.41.9
|
||||
> (Dart 3.11.5) via FVM. Setup, perintah, dan troubleshooting lengkap: **[docs/codegen.md](docs/codegen.md)**.
|
||||
|
||||
```powershell
|
||||
$FVM = "$env:USERPROFILE\fvm\versions\3.41.9\bin"
|
||||
|
||||
& "$FVM\flutter.bat" pub get
|
||||
& "$FVM\dart.bat" run build_runner build --delete-conflicting-outputs
|
||||
flutter pub get # kembalikan resolusi lockfile ke SDK global
|
||||
```
|
||||
|
||||
> Jalankan ulang perintah di atas setiap kali Anda mengubah file yang menggunakan anotasi `@RoutePage()`, `@injectable`, `@freezed`, atau `@JsonSerializable`.
|
||||
@@ -472,14 +508,27 @@ Catatan versi lengkap tersedia di `pubspec.yaml`.
|
||||
|
||||
## Code Generation
|
||||
|
||||
**Panduan lengkap: [docs/codegen.md](docs/codegen.md)** — baca dulu sebelum codegen pertama kali.
|
||||
|
||||
Codegen dijalankan dengan SDK terpisah (Flutter 3.41.9 / Dart 3.11.5 via FVM), bukan Flutter global 3.47:
|
||||
`freezed 2.5.8` mengunci `analyzer` di bawah 8.0.0, dan analyzer 7.7.1 crash pada sintaks *dot-shorthand*
|
||||
milik Dart 3.13. Aplikasinya sendiri tetap di-`run`/`build` dengan SDK global.
|
||||
|
||||
Perintah umum:
|
||||
|
||||
```bash
|
||||
flutter pub run build_runner build --delete-conflicting-outputs
|
||||
```powershell
|
||||
$FVM = "$env:USERPROFILE\fvm\versions\3.41.9\bin"
|
||||
|
||||
& "$FVM\flutter.bat" pub get
|
||||
& "$FVM\dart.bat" run build_runner build --delete-conflicting-outputs
|
||||
# atau untuk watch
|
||||
flutter pub run build_runner watch --delete-conflicting-outputs
|
||||
& "$FVM\dart.bat" run build_runner watch --delete-conflicting-outputs
|
||||
flutter pub get # kembalikan resolusi lockfile ke SDK global
|
||||
```
|
||||
|
||||
> `--delete-conflicting-outputs` menghapus semua file generated lebih dulu. Kalau build dibatalkan di
|
||||
> tengah jalan, pulihkan dengan `git checkout` — lihat tabel troubleshooting di [docs/codegen.md](docs/codegen.md).
|
||||
|
||||
Generator yang digunakan:
|
||||
|
||||
- AutoRoute: menghasilkan deklarasi router & route.
|
||||
@@ -533,15 +582,20 @@ flutter pub get
|
||||
flutter format .
|
||||
flutter analyze
|
||||
|
||||
# Code generation (sekali jalan / watch)
|
||||
flutter pub run build_runner build --delete-conflicting-outputs
|
||||
flutter pub run build_runner watch --delete-conflicting-outputs
|
||||
# Localization (aman dengan SDK global)
|
||||
flutter gen-l10n
|
||||
|
||||
# Run by device
|
||||
flutter devices
|
||||
flutter run -d <device-id>
|
||||
```
|
||||
|
||||
Code generation memakai SDK FVM yang di-pin — lihat [docs/codegen.md](docs/codegen.md):
|
||||
|
||||
```powershell
|
||||
& "$env:USERPROFILE\fvm\versions\3.41.9\bin\dart.bat" run build_runner build --delete-conflicting-outputs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Catatan Tambahan
|
||||
|
||||
@@ -19,3 +19,10 @@ analyzer:
|
||||
- test/generated/**
|
||||
- "**/**.g.dart"
|
||||
- "**/**.freezed.dart"
|
||||
- build/**
|
||||
- android/**
|
||||
- ios/**
|
||||
- web/**
|
||||
- windows/**
|
||||
- macos/**
|
||||
- linux/**
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
|
||||
|
||||
@@ -18,8 +18,8 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.7.3" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||
id("com.android.application") version "8.11.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
|
||||
id("com.google.gms.google-services") version "4.4.2" apply false
|
||||
}
|
||||
|
||||
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
# Code Generation (build_runner)
|
||||
|
||||
> **PENTING:** `build_runner` di proyek ini **tidak bisa** dijalankan dengan Flutter 3.47 / Dart 3.13.
|
||||
> Gunakan SDK Flutter 3.41.9 (Dart 3.11.5) lewat FVM. Aplikasinya sendiri tetap di-`run`/`build`
|
||||
> dengan Flutter versi global (3.47).
|
||||
|
||||
---
|
||||
|
||||
## TL;DR
|
||||
|
||||
PowerShell (Windows):
|
||||
|
||||
```powershell
|
||||
$FVM = "$env:USERPROFILE\fvm\versions\3.41.9\bin"
|
||||
|
||||
& "$FVM\flutter.bat" pub get # 1. resolve paket dgn Dart 3.11.5
|
||||
& "$FVM\dart.bat" run build_runner build --delete-conflicting-outputs # 2. generate
|
||||
flutter pub get # 3. balik ke SDK global 3.47
|
||||
```
|
||||
|
||||
Git Bash:
|
||||
|
||||
```bash
|
||||
FVM=~/fvm/versions/3.41.9/bin
|
||||
|
||||
"$FVM/flutter.bat" pub get
|
||||
"$FVM/dart.bat" run build_runner build --delete-conflicting-outputs
|
||||
flutter pub get
|
||||
```
|
||||
|
||||
Durasi normal: **±90–120 detik** untuk full build (146 output).
|
||||
|
||||
Setelah itu jalankan `flutter analyze` untuk memastikan hasil generate bersih.
|
||||
|
||||
---
|
||||
|
||||
## Kenapa harus SDK terpisah?
|
||||
|
||||
Kalau codegen dijalankan dengan Flutter global (3.47 / Dart 3.13), semua builder gagal dengan:
|
||||
|
||||
```
|
||||
W SDK language version 3.13.0 is newer than `analyzer` language version 3.9.0.
|
||||
E freezed on lib/application/.../category_analytic_loader_bloc.dart:
|
||||
Exception: Missing implementation of visitDotShorthandPropertyAccess
|
||||
```
|
||||
|
||||
Rantai penyebabnya:
|
||||
|
||||
1. `freezed 2.5.8` membatasi `analyzer <8.0.0`, jadi pub me-resolve ke **analyzer 7.7.1** (language version 3.9).
|
||||
2. Dart 3.13 memakai sintaks *dot-shorthand* (`.value`) di sumber SDK/framework.
|
||||
3. Analyzer 7.7.1 bisa mem-*parse*-nya tapi belum bisa menuliskannya ke summary → crash saat resolve.
|
||||
4. Semua generator (`freezed`, `json_serializable`, `injectable_generator`, `auto_route_generator`) berbagi analyzer yang sama, jadi semuanya ikut mati — termasuk saat men-generate file lama yang tidak diubah.
|
||||
|
||||
Dart 3.11.5 (Flutter 3.41.9) adalah versi tertinggi yang **masih cocok dengan analyzer 7.7.1** sekaligus memenuhi batas `pubspec.lock` (`dart >=3.11.0-0`).
|
||||
|
||||
---
|
||||
|
||||
## Setup sekali di mesin baru
|
||||
|
||||
```powershell
|
||||
dart pub global activate fvm
|
||||
```
|
||||
|
||||
Tambahkan folder binari pub ke PATH agar perintah `fvm` dikenali:
|
||||
`%LOCALAPPDATA%\Pub\Cache\bin`
|
||||
|
||||
Lalu unduh SDK-nya (±1 GB, sekali saja):
|
||||
|
||||
```powershell
|
||||
fvm install 3.41.9
|
||||
fvm list # verifikasi
|
||||
```
|
||||
|
||||
SDK tersimpan di `%USERPROFILE%\fvm\versions\3.41.9` dan **tidak** mengubah Flutter global.
|
||||
|
||||
> Kita sengaja **tidak** menjalankan `fvm use`, supaya repo tidak punya `.fvmrc`/`.fvm/`
|
||||
> dan IDE tetap memakai Flutter 3.47 untuk menjalankan aplikasi.
|
||||
> Kalau lebih suka pin per-project, jalankan `fvm use 3.41.9` lalu pakai `fvm dart run build_runner ...`,
|
||||
> dan tambahkan `.fvm/` ke `.gitignore`.
|
||||
|
||||
---
|
||||
|
||||
## Perintah harian
|
||||
|
||||
Semua contoh memakai variabel `$FVM` dari bagian TL;DR.
|
||||
|
||||
```powershell
|
||||
# build sekali (paling sering dipakai)
|
||||
& "$FVM\dart.bat" run build_runner build --delete-conflicting-outputs
|
||||
|
||||
# mode watch saat banyak mengubah anotasi
|
||||
& "$FVM\dart.bat" run build_runner watch --delete-conflicting-outputs
|
||||
|
||||
# hanya membersihkan output generated
|
||||
& "$FVM\dart.bat" run build_runner clean
|
||||
```
|
||||
|
||||
Wajib generate ulang setiap kali mengubah/menambah file dengan anotasi:
|
||||
|
||||
| Anotasi | Generator | Output |
|
||||
| --- | --- | --- |
|
||||
| `@freezed` | freezed | `*.freezed.dart` |
|
||||
| `@JsonSerializable` / `fromJson` di DTO | json_serializable | `*.g.dart` |
|
||||
| `@injectable`, `@LazySingleton` | injectable_generator | `lib/injection.config.dart` |
|
||||
| `@RoutePage()` | auto_route_generator | `lib/presentation/router/app_router.gr.dart` |
|
||||
| asset baru di `assets/` | flutter_gen_runner | `lib/presentation/components/assets/assets.gen.dart` |
|
||||
|
||||
### Localization (i18n) — tidak lewat build_runner
|
||||
|
||||
`flutter gen-l10n` memakai tooling Flutter, bukan analyzer, jadi **aman dijalankan dengan Flutter 3.47**:
|
||||
|
||||
```powershell
|
||||
flutter gen-l10n
|
||||
```
|
||||
|
||||
Jalankan setiap kali `lib/l10n/app_id.arb` / `app_en.arb` berubah. Hasilnya: `lib/l10n/app_localizations*.dart`.
|
||||
|
||||
---
|
||||
|
||||
## Aturan penting soal `--delete-conflicting-outputs`
|
||||
|
||||
Flag ini **menghapus dulu** semua file generated (±55 file `*.freezed.dart` / `*.g.dart`) sebelum membuat ulang.
|
||||
Kalau proses dibatalkan di tengah jalan, project akan penuh error karena file-nya sudah hilang.
|
||||
|
||||
Pemulihan (semua file generated ikut ter-commit di git):
|
||||
|
||||
```bash
|
||||
git status --porcelain | grep -E "^ D" | sed 's/^ D //' | while read -r f; do git checkout -- "$f"; done
|
||||
```
|
||||
|
||||
Tips: jangan pipe output build ke `tail`/`head` — outputnya jadi ter-buffer sampai proses selesai,
|
||||
sehingga terlihat seperti *hang* padahal sedang jalan.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Gejala | Penyebab | Solusi |
|
||||
| --- | --- | --- |
|
||||
| `Missing implementation of visitDotShorthandPropertyAccess` | codegen dijalankan dengan Flutter global 3.47 | pakai SDK FVM 3.41.9 |
|
||||
| `Failed to update packages` + dump stack VM | `flutter pub run build_runner` / `fvm spawn ... pub run` | pakai `dart.bat run build_runner`, jangan `flutter pub run` |
|
||||
| Ratusan error “isn't defined” di IDE, file `*.freezed.dart` hilang | build dibatalkan setelah `--delete-conflicting-outputs` | restore lewat `git checkout` (lihat di atas) |
|
||||
| `pub get` gagal / versi paket bergeser | `pubspec.lock` di-resolve oleh SDK berbeda | jalankan `flutter pub get` dengan SDK global setelah codegen selesai |
|
||||
| Build terasa lama di run pertama | asset graph baru dibuat dari nol | wajar, ±90–120 detik; run berikutnya inkremental |
|
||||
|
||||
---
|
||||
|
||||
## Rencana jangka panjang
|
||||
|
||||
Perbaikan permanen = upgrade toolchain codegen supaya jalan native di Dart 3.13:
|
||||
|
||||
- `freezed` 2.5.8 → 3.x, `freezed_annotation` 2.4.x → 3.x
|
||||
- `injectable` 2.x → 3.x, `injectable_generator` 2.7 → 3.x
|
||||
- `auto_route` 9.x → 11.x, `auto_route_generator` 9.x → 10.x
|
||||
- `build_runner` → 2.16.x, `json_serializable` → 6.14.x (analyzer ikut naik ke 13.x)
|
||||
|
||||
Konsekuensinya breaking dan cukup besar, jadi perlu dijadwalkan sendiri:
|
||||
|
||||
- freezed 3 **menghapus** `.map()` / `.when()` / `.maybeWhen()` / `.maybeMap()` → ±49 pemakaian (mayoritas di handler event BLoC) harus diganti pattern matching Dart 3 (`switch`).
|
||||
- ±98 file dengan `@freezed` harus dideklarasikan `abstract class` atau `sealed class`.
|
||||
- Migrasi konfigurasi router (auto_route 11) dan DI (injectable 3).
|
||||
|
||||
Selama migrasi itu belum dikerjakan, gunakan alur FVM di dokumen ini.
|
||||
@@ -21,6 +21,6 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
<string>15.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
platform :ios, '14.0'
|
||||
platform :ios, '15.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
@@ -40,7 +40,7 @@ post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0'
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
|
||||
config.build_settings['CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES'] = 'YES'
|
||||
# Disable code signing for all Pod targets — only Runner needs signing
|
||||
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
|
||||
|
||||
@@ -476,7 +476,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -495,7 +495,7 @@
|
||||
DEVELOPMENT_TEAM = 5TRC3M8UZG;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -607,7 +607,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -658,7 +658,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -679,7 +679,7 @@
|
||||
DEVELOPMENT_TEAM = 5TRC3M8UZG;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -703,7 +703,7 @@
|
||||
DEVELOPMENT_TEAM = 5TRC3M8UZG;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
||||
+5
-2
@@ -25,6 +25,9 @@ class CategoryAnalyticLoaderBloc
|
||||
Emitter<CategoryAnalyticLoaderState> emit,
|
||||
) {
|
||||
return event.map(
|
||||
rangeDateChanged: (e) async {
|
||||
emit(state.copyWith(dateFrom: e.dateFrom, dateTo: e.dateTo));
|
||||
},
|
||||
fetched: (e) async {
|
||||
emit(
|
||||
state.copyWith(
|
||||
@@ -34,8 +37,8 @@ class CategoryAnalyticLoaderBloc
|
||||
);
|
||||
|
||||
final result = await _repository.getCategory(
|
||||
dateFrom: DateTime.now().subtract(const Duration(days: 30)),
|
||||
dateTo: DateTime.now(),
|
||||
dateFrom: state.dateFrom,
|
||||
dateTo: state.dateTo,
|
||||
);
|
||||
|
||||
var data = result.fold(
|
||||
|
||||
+224
-4
@@ -19,27 +19,34 @@ final _privateConstructorUsedError = UnsupportedError(
|
||||
mixin _$CategoryAnalyticLoaderEvent {
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||
rangeDateChanged,
|
||||
required TResult Function() fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult? Function()? fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult Function()? fetched,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_RangeDateChanged value) rangeDateChanged,
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@@ -74,6 +81,165 @@ class _$CategoryAnalyticLoaderEventCopyWithImpl<
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$RangeDateChangedImplCopyWith<$Res> {
|
||||
factory _$$RangeDateChangedImplCopyWith(
|
||||
_$RangeDateChangedImpl value,
|
||||
$Res Function(_$RangeDateChangedImpl) then,
|
||||
) = __$$RangeDateChangedImplCopyWithImpl<$Res>;
|
||||
@useResult
|
||||
$Res call({DateTime dateFrom, DateTime dateTo});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$RangeDateChangedImplCopyWithImpl<$Res>
|
||||
extends
|
||||
_$CategoryAnalyticLoaderEventCopyWithImpl<$Res, _$RangeDateChangedImpl>
|
||||
implements _$$RangeDateChangedImplCopyWith<$Res> {
|
||||
__$$RangeDateChangedImplCopyWithImpl(
|
||||
_$RangeDateChangedImpl _value,
|
||||
$Res Function(_$RangeDateChangedImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of CategoryAnalyticLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({Object? dateFrom = null, Object? dateTo = null}) {
|
||||
return _then(
|
||||
_$RangeDateChangedImpl(
|
||||
null == dateFrom
|
||||
? _value.dateFrom
|
||||
: dateFrom // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
null == dateTo
|
||||
? _value.dateTo
|
||||
: dateTo // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$RangeDateChangedImpl implements _RangeDateChanged {
|
||||
const _$RangeDateChangedImpl(this.dateFrom, this.dateTo);
|
||||
|
||||
@override
|
||||
final DateTime dateFrom;
|
||||
@override
|
||||
final DateTime dateTo;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CategoryAnalyticLoaderEvent.rangeDateChanged(dateFrom: $dateFrom, dateTo: $dateTo)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$RangeDateChangedImpl &&
|
||||
(identical(other.dateFrom, dateFrom) ||
|
||||
other.dateFrom == dateFrom) &&
|
||||
(identical(other.dateTo, dateTo) || other.dateTo == dateTo));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, dateFrom, dateTo);
|
||||
|
||||
/// Create a copy of CategoryAnalyticLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$RangeDateChangedImplCopyWith<_$RangeDateChangedImpl> get copyWith =>
|
||||
__$$RangeDateChangedImplCopyWithImpl<_$RangeDateChangedImpl>(
|
||||
this,
|
||||
_$identity,
|
||||
);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||
rangeDateChanged,
|
||||
required TResult Function() fetched,
|
||||
}) {
|
||||
return rangeDateChanged(dateFrom, dateTo);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult? Function()? fetched,
|
||||
}) {
|
||||
return rangeDateChanged?.call(dateFrom, dateTo);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult Function()? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (rangeDateChanged != null) {
|
||||
return rangeDateChanged(dateFrom, dateTo);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_RangeDateChanged value) rangeDateChanged,
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
}) {
|
||||
return rangeDateChanged(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
}) {
|
||||
return rangeDateChanged?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (rangeDateChanged != null) {
|
||||
return rangeDateChanged(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _RangeDateChanged implements CategoryAnalyticLoaderEvent {
|
||||
const factory _RangeDateChanged(
|
||||
final DateTime dateFrom,
|
||||
final DateTime dateTo,
|
||||
) = _$RangeDateChangedImpl;
|
||||
|
||||
DateTime get dateFrom;
|
||||
DateTime get dateTo;
|
||||
|
||||
/// Create a copy of CategoryAnalyticLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$RangeDateChangedImplCopyWith<_$RangeDateChangedImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$FetchedImplCopyWith<$Res> {
|
||||
factory _$$FetchedImplCopyWith(
|
||||
@@ -116,19 +282,27 @@ class _$FetchedImpl implements _Fetched {
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({required TResult Function() fetched}) {
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||
rangeDateChanged,
|
||||
required TResult Function() fetched,
|
||||
}) {
|
||||
return fetched();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({TResult? Function()? fetched}) {
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult? Function()? fetched,
|
||||
}) {
|
||||
return fetched?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult Function()? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
@@ -141,6 +315,7 @@ class _$FetchedImpl implements _Fetched {
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_RangeDateChanged value) rangeDateChanged,
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
}) {
|
||||
return fetched(this);
|
||||
@@ -149,6 +324,7 @@ class _$FetchedImpl implements _Fetched {
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
}) {
|
||||
return fetched?.call(this);
|
||||
@@ -157,6 +333,7 @@ class _$FetchedImpl implements _Fetched {
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
@@ -177,6 +354,8 @@ mixin _$CategoryAnalyticLoaderState {
|
||||
Option<AnalyticFailure> get failureOptionCategoryAnalytic =>
|
||||
throw _privateConstructorUsedError;
|
||||
bool get isFetching => throw _privateConstructorUsedError;
|
||||
DateTime get dateFrom => throw _privateConstructorUsedError;
|
||||
DateTime get dateTo => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of CategoryAnalyticLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -200,6 +379,8 @@ abstract class $CategoryAnalyticLoaderStateCopyWith<$Res> {
|
||||
CategoryAnalytic categoryAnalytic,
|
||||
Option<AnalyticFailure> failureOptionCategoryAnalytic,
|
||||
bool isFetching,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
});
|
||||
|
||||
$CategoryAnalyticCopyWith<$Res> get categoryAnalytic;
|
||||
@@ -226,6 +407,8 @@ class _$CategoryAnalyticLoaderStateCopyWithImpl<
|
||||
Object? categoryAnalytic = null,
|
||||
Object? failureOptionCategoryAnalytic = null,
|
||||
Object? isFetching = null,
|
||||
Object? dateFrom = null,
|
||||
Object? dateTo = null,
|
||||
}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
@@ -241,6 +424,14 @@ class _$CategoryAnalyticLoaderStateCopyWithImpl<
|
||||
? _value.isFetching
|
||||
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
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,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
@@ -270,6 +461,8 @@ abstract class _$$CategoryAnalyticLoaderStateImplCopyWith<$Res>
|
||||
CategoryAnalytic categoryAnalytic,
|
||||
Option<AnalyticFailure> failureOptionCategoryAnalytic,
|
||||
bool isFetching,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -297,6 +490,8 @@ class __$$CategoryAnalyticLoaderStateImplCopyWithImpl<$Res>
|
||||
Object? categoryAnalytic = null,
|
||||
Object? failureOptionCategoryAnalytic = null,
|
||||
Object? isFetching = null,
|
||||
Object? dateFrom = null,
|
||||
Object? dateTo = null,
|
||||
}) {
|
||||
return _then(
|
||||
_$CategoryAnalyticLoaderStateImpl(
|
||||
@@ -312,6 +507,14 @@ class __$$CategoryAnalyticLoaderStateImplCopyWithImpl<$Res>
|
||||
? _value.isFetching
|
||||
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -325,6 +528,8 @@ class _$CategoryAnalyticLoaderStateImpl
|
||||
required this.categoryAnalytic,
|
||||
required this.failureOptionCategoryAnalytic,
|
||||
this.isFetching = false,
|
||||
required this.dateFrom,
|
||||
required this.dateTo,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -334,10 +539,14 @@ class _$CategoryAnalyticLoaderStateImpl
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isFetching;
|
||||
@override
|
||||
final DateTime dateFrom;
|
||||
@override
|
||||
final DateTime dateTo;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'CategoryAnalyticLoaderState(categoryAnalytic: $categoryAnalytic, failureOptionCategoryAnalytic: $failureOptionCategoryAnalytic, isFetching: $isFetching)';
|
||||
return 'CategoryAnalyticLoaderState(categoryAnalytic: $categoryAnalytic, failureOptionCategoryAnalytic: $failureOptionCategoryAnalytic, isFetching: $isFetching, dateFrom: $dateFrom, dateTo: $dateTo)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -354,7 +563,10 @@ class _$CategoryAnalyticLoaderStateImpl
|
||||
other.failureOptionCategoryAnalytic ==
|
||||
failureOptionCategoryAnalytic) &&
|
||||
(identical(other.isFetching, isFetching) ||
|
||||
other.isFetching == isFetching));
|
||||
other.isFetching == isFetching) &&
|
||||
(identical(other.dateFrom, dateFrom) ||
|
||||
other.dateFrom == dateFrom) &&
|
||||
(identical(other.dateTo, dateTo) || other.dateTo == dateTo));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -363,6 +575,8 @@ class _$CategoryAnalyticLoaderStateImpl
|
||||
categoryAnalytic,
|
||||
failureOptionCategoryAnalytic,
|
||||
isFetching,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
);
|
||||
|
||||
/// Create a copy of CategoryAnalyticLoaderState
|
||||
@@ -383,6 +597,8 @@ abstract class _CategoryAnalyticLoaderState
|
||||
required final CategoryAnalytic categoryAnalytic,
|
||||
required final Option<AnalyticFailure> failureOptionCategoryAnalytic,
|
||||
final bool isFetching,
|
||||
required final DateTime dateFrom,
|
||||
required final DateTime dateTo,
|
||||
}) = _$CategoryAnalyticLoaderStateImpl;
|
||||
|
||||
@override
|
||||
@@ -391,6 +607,10 @@ abstract class _CategoryAnalyticLoaderState
|
||||
Option<AnalyticFailure> get failureOptionCategoryAnalytic;
|
||||
@override
|
||||
bool get isFetching;
|
||||
@override
|
||||
DateTime get dateFrom;
|
||||
@override
|
||||
DateTime get dateTo;
|
||||
|
||||
/// Create a copy of CategoryAnalyticLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
||||
@@ -2,5 +2,9 @@ part of 'category_analytic_loader_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class CategoryAnalyticLoaderEvent with _$CategoryAnalyticLoaderEvent {
|
||||
const factory CategoryAnalyticLoaderEvent.rangeDateChanged(
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
) = _RangeDateChanged;
|
||||
const factory CategoryAnalyticLoaderEvent.fetched() = _Fetched;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,14 @@ class CategoryAnalyticLoaderState with _$CategoryAnalyticLoaderState {
|
||||
required CategoryAnalytic categoryAnalytic,
|
||||
required Option<AnalyticFailure> failureOptionCategoryAnalytic,
|
||||
@Default(false) bool isFetching,
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
}) = _CategoryAnalyticLoaderState;
|
||||
|
||||
factory CategoryAnalyticLoaderState.initial() => CategoryAnalyticLoaderState(
|
||||
categoryAnalytic: CategoryAnalytic.empty(),
|
||||
failureOptionCategoryAnalytic: none(),
|
||||
dateFrom: DateTime.now(),
|
||||
dateTo: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
+5
-2
@@ -28,6 +28,9 @@ class PaymentMethodAnalyticLoaderBloc
|
||||
Emitter<PaymentMethodAnalyticLoaderState> emit,
|
||||
) {
|
||||
return event.map(
|
||||
rangeDateChanged: (e) async {
|
||||
emit(state.copyWith(dateFrom: e.dateFrom, dateTo: e.dateTo));
|
||||
},
|
||||
fetched: (e) async {
|
||||
emit(
|
||||
state.copyWith(
|
||||
@@ -37,8 +40,8 @@ class PaymentMethodAnalyticLoaderBloc
|
||||
);
|
||||
|
||||
final result = await _repository.getPaymentMethod(
|
||||
dateFrom: DateTime.now().subtract(const Duration(days: 30)),
|
||||
dateTo: DateTime.now(),
|
||||
dateFrom: state.dateFrom,
|
||||
dateTo: state.dateTo,
|
||||
);
|
||||
|
||||
var data = result.fold(
|
||||
|
||||
+227
-4
@@ -19,27 +19,34 @@ final _privateConstructorUsedError = UnsupportedError(
|
||||
mixin _$PaymentMethodAnalyticLoaderEvent {
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||
rangeDateChanged,
|
||||
required TResult Function() fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult? Function()? fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult Function()? fetched,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_RangeDateChanged value) rangeDateChanged,
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@@ -74,6 +81,168 @@ class _$PaymentMethodAnalyticLoaderEventCopyWithImpl<
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$RangeDateChangedImplCopyWith<$Res> {
|
||||
factory _$$RangeDateChangedImplCopyWith(
|
||||
_$RangeDateChangedImpl value,
|
||||
$Res Function(_$RangeDateChangedImpl) then,
|
||||
) = __$$RangeDateChangedImplCopyWithImpl<$Res>;
|
||||
@useResult
|
||||
$Res call({DateTime dateFrom, DateTime dateTo});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$RangeDateChangedImplCopyWithImpl<$Res>
|
||||
extends
|
||||
_$PaymentMethodAnalyticLoaderEventCopyWithImpl<
|
||||
$Res,
|
||||
_$RangeDateChangedImpl
|
||||
>
|
||||
implements _$$RangeDateChangedImplCopyWith<$Res> {
|
||||
__$$RangeDateChangedImplCopyWithImpl(
|
||||
_$RangeDateChangedImpl _value,
|
||||
$Res Function(_$RangeDateChangedImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of PaymentMethodAnalyticLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({Object? dateFrom = null, Object? dateTo = null}) {
|
||||
return _then(
|
||||
_$RangeDateChangedImpl(
|
||||
null == dateFrom
|
||||
? _value.dateFrom
|
||||
: dateFrom // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
null == dateTo
|
||||
? _value.dateTo
|
||||
: dateTo // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$RangeDateChangedImpl implements _RangeDateChanged {
|
||||
const _$RangeDateChangedImpl(this.dateFrom, this.dateTo);
|
||||
|
||||
@override
|
||||
final DateTime dateFrom;
|
||||
@override
|
||||
final DateTime dateTo;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PaymentMethodAnalyticLoaderEvent.rangeDateChanged(dateFrom: $dateFrom, dateTo: $dateTo)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$RangeDateChangedImpl &&
|
||||
(identical(other.dateFrom, dateFrom) ||
|
||||
other.dateFrom == dateFrom) &&
|
||||
(identical(other.dateTo, dateTo) || other.dateTo == dateTo));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, dateFrom, dateTo);
|
||||
|
||||
/// Create a copy of PaymentMethodAnalyticLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$RangeDateChangedImplCopyWith<_$RangeDateChangedImpl> get copyWith =>
|
||||
__$$RangeDateChangedImplCopyWithImpl<_$RangeDateChangedImpl>(
|
||||
this,
|
||||
_$identity,
|
||||
);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||
rangeDateChanged,
|
||||
required TResult Function() fetched,
|
||||
}) {
|
||||
return rangeDateChanged(dateFrom, dateTo);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult? Function()? fetched,
|
||||
}) {
|
||||
return rangeDateChanged?.call(dateFrom, dateTo);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult Function()? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (rangeDateChanged != null) {
|
||||
return rangeDateChanged(dateFrom, dateTo);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_RangeDateChanged value) rangeDateChanged,
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
}) {
|
||||
return rangeDateChanged(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
}) {
|
||||
return rangeDateChanged?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (rangeDateChanged != null) {
|
||||
return rangeDateChanged(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _RangeDateChanged implements PaymentMethodAnalyticLoaderEvent {
|
||||
const factory _RangeDateChanged(
|
||||
final DateTime dateFrom,
|
||||
final DateTime dateTo,
|
||||
) = _$RangeDateChangedImpl;
|
||||
|
||||
DateTime get dateFrom;
|
||||
DateTime get dateTo;
|
||||
|
||||
/// Create a copy of PaymentMethodAnalyticLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$RangeDateChangedImplCopyWith<_$RangeDateChangedImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$FetchedImplCopyWith<$Res> {
|
||||
factory _$$FetchedImplCopyWith(
|
||||
@@ -116,19 +285,27 @@ class _$FetchedImpl implements _Fetched {
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({required TResult Function() fetched}) {
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||
rangeDateChanged,
|
||||
required TResult Function() fetched,
|
||||
}) {
|
||||
return fetched();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({TResult? Function()? fetched}) {
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult? Function()? fetched,
|
||||
}) {
|
||||
return fetched?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult Function()? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
@@ -141,6 +318,7 @@ class _$FetchedImpl implements _Fetched {
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_RangeDateChanged value) rangeDateChanged,
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
}) {
|
||||
return fetched(this);
|
||||
@@ -149,6 +327,7 @@ class _$FetchedImpl implements _Fetched {
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
}) {
|
||||
return fetched?.call(this);
|
||||
@@ -157,6 +336,7 @@ class _$FetchedImpl implements _Fetched {
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
@@ -178,6 +358,8 @@ mixin _$PaymentMethodAnalyticLoaderState {
|
||||
Option<AnalyticFailure> get failureOptionPaymentMethodAnalytic =>
|
||||
throw _privateConstructorUsedError;
|
||||
bool get isFetching => throw _privateConstructorUsedError;
|
||||
DateTime get dateFrom => throw _privateConstructorUsedError;
|
||||
DateTime get dateTo => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of PaymentMethodAnalyticLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -201,6 +383,8 @@ abstract class $PaymentMethodAnalyticLoaderStateCopyWith<$Res> {
|
||||
PaymentMethodAnalytic paymentMethodAnalytic,
|
||||
Option<AnalyticFailure> failureOptionPaymentMethodAnalytic,
|
||||
bool isFetching,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
});
|
||||
|
||||
$PaymentMethodAnalyticCopyWith<$Res> get paymentMethodAnalytic;
|
||||
@@ -227,6 +411,8 @@ class _$PaymentMethodAnalyticLoaderStateCopyWithImpl<
|
||||
Object? paymentMethodAnalytic = null,
|
||||
Object? failureOptionPaymentMethodAnalytic = null,
|
||||
Object? isFetching = null,
|
||||
Object? dateFrom = null,
|
||||
Object? dateTo = null,
|
||||
}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
@@ -243,6 +429,14 @@ class _$PaymentMethodAnalyticLoaderStateCopyWithImpl<
|
||||
? _value.isFetching
|
||||
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
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,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
@@ -274,6 +468,8 @@ abstract class _$$PaymentMethodAnalyticLoaderStateImplCopyWith<$Res>
|
||||
PaymentMethodAnalytic paymentMethodAnalytic,
|
||||
Option<AnalyticFailure> failureOptionPaymentMethodAnalytic,
|
||||
bool isFetching,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -301,6 +497,8 @@ class __$$PaymentMethodAnalyticLoaderStateImplCopyWithImpl<$Res>
|
||||
Object? paymentMethodAnalytic = null,
|
||||
Object? failureOptionPaymentMethodAnalytic = null,
|
||||
Object? isFetching = null,
|
||||
Object? dateFrom = null,
|
||||
Object? dateTo = null,
|
||||
}) {
|
||||
return _then(
|
||||
_$PaymentMethodAnalyticLoaderStateImpl(
|
||||
@@ -317,6 +515,14 @@ class __$$PaymentMethodAnalyticLoaderStateImplCopyWithImpl<$Res>
|
||||
? _value.isFetching
|
||||
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -330,6 +536,8 @@ class _$PaymentMethodAnalyticLoaderStateImpl
|
||||
required this.paymentMethodAnalytic,
|
||||
required this.failureOptionPaymentMethodAnalytic,
|
||||
this.isFetching = false,
|
||||
required this.dateFrom,
|
||||
required this.dateTo,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -339,10 +547,14 @@ class _$PaymentMethodAnalyticLoaderStateImpl
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isFetching;
|
||||
@override
|
||||
final DateTime dateFrom;
|
||||
@override
|
||||
final DateTime dateTo;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PaymentMethodAnalyticLoaderState(paymentMethodAnalytic: $paymentMethodAnalytic, failureOptionPaymentMethodAnalytic: $failureOptionPaymentMethodAnalytic, isFetching: $isFetching)';
|
||||
return 'PaymentMethodAnalyticLoaderState(paymentMethodAnalytic: $paymentMethodAnalytic, failureOptionPaymentMethodAnalytic: $failureOptionPaymentMethodAnalytic, isFetching: $isFetching, dateFrom: $dateFrom, dateTo: $dateTo)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -359,7 +571,10 @@ class _$PaymentMethodAnalyticLoaderStateImpl
|
||||
other.failureOptionPaymentMethodAnalytic ==
|
||||
failureOptionPaymentMethodAnalytic) &&
|
||||
(identical(other.isFetching, isFetching) ||
|
||||
other.isFetching == isFetching));
|
||||
other.isFetching == isFetching) &&
|
||||
(identical(other.dateFrom, dateFrom) ||
|
||||
other.dateFrom == dateFrom) &&
|
||||
(identical(other.dateTo, dateTo) || other.dateTo == dateTo));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -368,6 +583,8 @@ class _$PaymentMethodAnalyticLoaderStateImpl
|
||||
paymentMethodAnalytic,
|
||||
failureOptionPaymentMethodAnalytic,
|
||||
isFetching,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
);
|
||||
|
||||
/// Create a copy of PaymentMethodAnalyticLoaderState
|
||||
@@ -390,6 +607,8 @@ abstract class _PaymentMethodAnalyticLoaderState
|
||||
required final PaymentMethodAnalytic paymentMethodAnalytic,
|
||||
required final Option<AnalyticFailure> failureOptionPaymentMethodAnalytic,
|
||||
final bool isFetching,
|
||||
required final DateTime dateFrom,
|
||||
required final DateTime dateTo,
|
||||
}) = _$PaymentMethodAnalyticLoaderStateImpl;
|
||||
|
||||
@override
|
||||
@@ -398,6 +617,10 @@ abstract class _PaymentMethodAnalyticLoaderState
|
||||
Option<AnalyticFailure> get failureOptionPaymentMethodAnalytic;
|
||||
@override
|
||||
bool get isFetching;
|
||||
@override
|
||||
DateTime get dateFrom;
|
||||
@override
|
||||
DateTime get dateTo;
|
||||
|
||||
/// Create a copy of PaymentMethodAnalyticLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
||||
+4
@@ -2,5 +2,9 @@ part of 'payment_method_analytic_loader_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class PaymentMethodAnalyticLoaderEvent with _$PaymentMethodAnalyticLoaderEvent {
|
||||
const factory PaymentMethodAnalyticLoaderEvent.rangeDateChanged(
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
) = _RangeDateChanged;
|
||||
const factory PaymentMethodAnalyticLoaderEvent.fetched() = _Fetched;
|
||||
}
|
||||
|
||||
+4
@@ -6,11 +6,15 @@ class PaymentMethodAnalyticLoaderState with _$PaymentMethodAnalyticLoaderState {
|
||||
required PaymentMethodAnalytic paymentMethodAnalytic,
|
||||
required Option<AnalyticFailure> failureOptionPaymentMethodAnalytic,
|
||||
@Default(false) bool isFetching,
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
}) = _PaymentMethodAnalyticLoaderState;
|
||||
|
||||
factory PaymentMethodAnalyticLoaderState.initial() =>
|
||||
PaymentMethodAnalyticLoaderState(
|
||||
paymentMethodAnalytic: PaymentMethodAnalytic.empty(),
|
||||
failureOptionPaymentMethodAnalytic: none(),
|
||||
dateFrom: DateTime.now(),
|
||||
dateTo: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class ProductAnalyticLoaderState with _$ProductAnalyticLoaderState {
|
||||
factory ProductAnalyticLoaderState.initial() => ProductAnalyticLoaderState(
|
||||
productAnalytic: ProductAnalytic.empty(),
|
||||
failureOptionProductAnalytic: none(),
|
||||
dateFrom: DateTime.now().subtract(const Duration(days: 30)),
|
||||
dateFrom: DateTime.now(),
|
||||
dateTo: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
import '../../../domain/analytic/analytic.dart';
|
||||
import '../../../domain/analytic/repositories/i_analytic_repository.dart';
|
||||
|
||||
part 'profit_sharing_detail_loader_event.dart';
|
||||
part 'profit_sharing_detail_loader_state.dart';
|
||||
part 'profit_sharing_detail_loader_bloc.freezed.dart';
|
||||
|
||||
@injectable
|
||||
class ProfitSharingDetailLoaderBloc
|
||||
extends
|
||||
Bloc<ProfitSharingDetailLoaderEvent, ProfitSharingDetailLoaderState> {
|
||||
final IAnalyticRepository _repository;
|
||||
|
||||
ProfitSharingDetailLoaderBloc(this._repository)
|
||||
: super(ProfitSharingDetailLoaderState.initial()) {
|
||||
on<ProfitSharingDetailLoaderEvent>(_onProfitSharingDetailLoaderEvent);
|
||||
}
|
||||
|
||||
Future<void> _onProfitSharingDetailLoaderEvent(
|
||||
ProfitSharingDetailLoaderEvent event,
|
||||
Emitter<ProfitSharingDetailLoaderState> emit,
|
||||
) {
|
||||
return event.map(
|
||||
expandedCategoryChanged: (e) async {
|
||||
final isSame = state.expandedCategoryId == e.categoryId;
|
||||
emit(state.copyWith(expandedCategoryId: isSame ? '' : e.categoryId));
|
||||
},
|
||||
fetched: (e) async {
|
||||
emit(
|
||||
state.copyWith(
|
||||
isFetching: true,
|
||||
failureOption: none(),
|
||||
parentCategoryId: e.parentCategoryId,
|
||||
dateFrom: e.dateFrom,
|
||||
dateTo: e.dateTo,
|
||||
),
|
||||
);
|
||||
|
||||
final result = await _repository.getProfitSharingDetail(
|
||||
parentCategoryId: e.parentCategoryId,
|
||||
dateFrom: e.dateFrom,
|
||||
dateTo: e.dateTo,
|
||||
);
|
||||
|
||||
final newState = result.fold(
|
||||
(f) => state.copyWith(failureOption: optionOf(f)),
|
||||
(detail) => state.copyWith(
|
||||
detail: detail,
|
||||
// Sub kategori pertama langsung terbuka supaya produknya kelihatan.
|
||||
expandedCategoryId: detail.categories.isEmpty
|
||||
? ''
|
||||
: detail.categories.first.categoryId,
|
||||
),
|
||||
);
|
||||
|
||||
emit(newState.copyWith(isFetching: false));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
+772
@@ -0,0 +1,772 @@
|
||||
// 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_sharing_detail_loader_bloc.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
|
||||
);
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ProfitSharingDetailLoaderEvent {
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(
|
||||
String parentCategoryId,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
)
|
||||
fetched,
|
||||
required TResult Function(String categoryId) expandedCategoryChanged,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function(
|
||||
String parentCategoryId,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
)?
|
||||
fetched,
|
||||
TResult? Function(String categoryId)? expandedCategoryChanged,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function(
|
||||
String parentCategoryId,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
)?
|
||||
fetched,
|
||||
TResult Function(String categoryId)? expandedCategoryChanged,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
required TResult Function(_ExpandedCategoryChanged value)
|
||||
expandedCategoryChanged,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
TResult? Function(_ExpandedCategoryChanged value)? expandedCategoryChanged,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
TResult Function(_ExpandedCategoryChanged value)? expandedCategoryChanged,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ProfitSharingDetailLoaderEventCopyWith<$Res> {
|
||||
factory $ProfitSharingDetailLoaderEventCopyWith(
|
||||
ProfitSharingDetailLoaderEvent value,
|
||||
$Res Function(ProfitSharingDetailLoaderEvent) then,
|
||||
) =
|
||||
_$ProfitSharingDetailLoaderEventCopyWithImpl<
|
||||
$Res,
|
||||
ProfitSharingDetailLoaderEvent
|
||||
>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ProfitSharingDetailLoaderEventCopyWithImpl<
|
||||
$Res,
|
||||
$Val extends ProfitSharingDetailLoaderEvent
|
||||
>
|
||||
implements $ProfitSharingDetailLoaderEventCopyWith<$Res> {
|
||||
_$ProfitSharingDetailLoaderEventCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ProfitSharingDetailLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$FetchedImplCopyWith<$Res> {
|
||||
factory _$$FetchedImplCopyWith(
|
||||
_$FetchedImpl value,
|
||||
$Res Function(_$FetchedImpl) then,
|
||||
) = __$$FetchedImplCopyWithImpl<$Res>;
|
||||
@useResult
|
||||
$Res call({String parentCategoryId, DateTime dateFrom, DateTime dateTo});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$FetchedImplCopyWithImpl<$Res>
|
||||
extends _$ProfitSharingDetailLoaderEventCopyWithImpl<$Res, _$FetchedImpl>
|
||||
implements _$$FetchedImplCopyWith<$Res> {
|
||||
__$$FetchedImplCopyWithImpl(
|
||||
_$FetchedImpl _value,
|
||||
$Res Function(_$FetchedImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of ProfitSharingDetailLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? parentCategoryId = null,
|
||||
Object? dateFrom = null,
|
||||
Object? dateTo = null,
|
||||
}) {
|
||||
return _then(
|
||||
_$FetchedImpl(
|
||||
parentCategoryId: null == parentCategoryId
|
||||
? _value.parentCategoryId
|
||||
: parentCategoryId // 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$FetchedImpl implements _Fetched {
|
||||
const _$FetchedImpl({
|
||||
required this.parentCategoryId,
|
||||
required this.dateFrom,
|
||||
required this.dateTo,
|
||||
});
|
||||
|
||||
@override
|
||||
final String parentCategoryId;
|
||||
@override
|
||||
final DateTime dateFrom;
|
||||
@override
|
||||
final DateTime dateTo;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfitSharingDetailLoaderEvent.fetched(parentCategoryId: $parentCategoryId, dateFrom: $dateFrom, dateTo: $dateTo)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$FetchedImpl &&
|
||||
(identical(other.parentCategoryId, parentCategoryId) ||
|
||||
other.parentCategoryId == parentCategoryId) &&
|
||||
(identical(other.dateFrom, dateFrom) ||
|
||||
other.dateFrom == dateFrom) &&
|
||||
(identical(other.dateTo, dateTo) || other.dateTo == dateTo));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, parentCategoryId, dateFrom, dateTo);
|
||||
|
||||
/// Create a copy of ProfitSharingDetailLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$FetchedImplCopyWith<_$FetchedImpl> get copyWith =>
|
||||
__$$FetchedImplCopyWithImpl<_$FetchedImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(
|
||||
String parentCategoryId,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
)
|
||||
fetched,
|
||||
required TResult Function(String categoryId) expandedCategoryChanged,
|
||||
}) {
|
||||
return fetched(parentCategoryId, dateFrom, dateTo);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function(
|
||||
String parentCategoryId,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
)?
|
||||
fetched,
|
||||
TResult? Function(String categoryId)? expandedCategoryChanged,
|
||||
}) {
|
||||
return fetched?.call(parentCategoryId, dateFrom, dateTo);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function(
|
||||
String parentCategoryId,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
)?
|
||||
fetched,
|
||||
TResult Function(String categoryId)? expandedCategoryChanged,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (fetched != null) {
|
||||
return fetched(parentCategoryId, dateFrom, dateTo);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
required TResult Function(_ExpandedCategoryChanged value)
|
||||
expandedCategoryChanged,
|
||||
}) {
|
||||
return fetched(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
TResult? Function(_ExpandedCategoryChanged value)? expandedCategoryChanged,
|
||||
}) {
|
||||
return fetched?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
TResult Function(_ExpandedCategoryChanged value)? expandedCategoryChanged,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (fetched != null) {
|
||||
return fetched(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Fetched implements ProfitSharingDetailLoaderEvent {
|
||||
const factory _Fetched({
|
||||
required final String parentCategoryId,
|
||||
required final DateTime dateFrom,
|
||||
required final DateTime dateTo,
|
||||
}) = _$FetchedImpl;
|
||||
|
||||
String get parentCategoryId;
|
||||
DateTime get dateFrom;
|
||||
DateTime get dateTo;
|
||||
|
||||
/// Create a copy of ProfitSharingDetailLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$FetchedImplCopyWith<_$FetchedImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ExpandedCategoryChangedImplCopyWith<$Res> {
|
||||
factory _$$ExpandedCategoryChangedImplCopyWith(
|
||||
_$ExpandedCategoryChangedImpl value,
|
||||
$Res Function(_$ExpandedCategoryChangedImpl) then,
|
||||
) = __$$ExpandedCategoryChangedImplCopyWithImpl<$Res>;
|
||||
@useResult
|
||||
$Res call({String categoryId});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ExpandedCategoryChangedImplCopyWithImpl<$Res>
|
||||
extends
|
||||
_$ProfitSharingDetailLoaderEventCopyWithImpl<
|
||||
$Res,
|
||||
_$ExpandedCategoryChangedImpl
|
||||
>
|
||||
implements _$$ExpandedCategoryChangedImplCopyWith<$Res> {
|
||||
__$$ExpandedCategoryChangedImplCopyWithImpl(
|
||||
_$ExpandedCategoryChangedImpl _value,
|
||||
$Res Function(_$ExpandedCategoryChangedImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of ProfitSharingDetailLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({Object? categoryId = null}) {
|
||||
return _then(
|
||||
_$ExpandedCategoryChangedImpl(
|
||||
null == categoryId
|
||||
? _value.categoryId
|
||||
: categoryId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ExpandedCategoryChangedImpl implements _ExpandedCategoryChanged {
|
||||
const _$ExpandedCategoryChangedImpl(this.categoryId);
|
||||
|
||||
@override
|
||||
final String categoryId;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfitSharingDetailLoaderEvent.expandedCategoryChanged(categoryId: $categoryId)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ExpandedCategoryChangedImpl &&
|
||||
(identical(other.categoryId, categoryId) ||
|
||||
other.categoryId == categoryId));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, categoryId);
|
||||
|
||||
/// Create a copy of ProfitSharingDetailLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ExpandedCategoryChangedImplCopyWith<_$ExpandedCategoryChangedImpl>
|
||||
get copyWith =>
|
||||
__$$ExpandedCategoryChangedImplCopyWithImpl<
|
||||
_$ExpandedCategoryChangedImpl
|
||||
>(this, _$identity);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(
|
||||
String parentCategoryId,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
)
|
||||
fetched,
|
||||
required TResult Function(String categoryId) expandedCategoryChanged,
|
||||
}) {
|
||||
return expandedCategoryChanged(categoryId);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function(
|
||||
String parentCategoryId,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
)?
|
||||
fetched,
|
||||
TResult? Function(String categoryId)? expandedCategoryChanged,
|
||||
}) {
|
||||
return expandedCategoryChanged?.call(categoryId);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function(
|
||||
String parentCategoryId,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
)?
|
||||
fetched,
|
||||
TResult Function(String categoryId)? expandedCategoryChanged,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (expandedCategoryChanged != null) {
|
||||
return expandedCategoryChanged(categoryId);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
required TResult Function(_ExpandedCategoryChanged value)
|
||||
expandedCategoryChanged,
|
||||
}) {
|
||||
return expandedCategoryChanged(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
TResult? Function(_ExpandedCategoryChanged value)? expandedCategoryChanged,
|
||||
}) {
|
||||
return expandedCategoryChanged?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
TResult Function(_ExpandedCategoryChanged value)? expandedCategoryChanged,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (expandedCategoryChanged != null) {
|
||||
return expandedCategoryChanged(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ExpandedCategoryChanged
|
||||
implements ProfitSharingDetailLoaderEvent {
|
||||
const factory _ExpandedCategoryChanged(final String categoryId) =
|
||||
_$ExpandedCategoryChangedImpl;
|
||||
|
||||
String get categoryId;
|
||||
|
||||
/// Create a copy of ProfitSharingDetailLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ExpandedCategoryChangedImplCopyWith<_$ExpandedCategoryChangedImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ProfitSharingDetailLoaderState {
|
||||
ProfitSharingDetail get detail => throw _privateConstructorUsedError;
|
||||
Option<AnalyticFailure> get failureOption =>
|
||||
throw _privateConstructorUsedError;
|
||||
bool get isFetching => throw _privateConstructorUsedError;
|
||||
String get parentCategoryId => throw _privateConstructorUsedError;
|
||||
String get expandedCategoryId => throw _privateConstructorUsedError;
|
||||
DateTime get dateFrom => throw _privateConstructorUsedError;
|
||||
DateTime get dateTo => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of ProfitSharingDetailLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ProfitSharingDetailLoaderStateCopyWith<ProfitSharingDetailLoaderState>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ProfitSharingDetailLoaderStateCopyWith<$Res> {
|
||||
factory $ProfitSharingDetailLoaderStateCopyWith(
|
||||
ProfitSharingDetailLoaderState value,
|
||||
$Res Function(ProfitSharingDetailLoaderState) then,
|
||||
) =
|
||||
_$ProfitSharingDetailLoaderStateCopyWithImpl<
|
||||
$Res,
|
||||
ProfitSharingDetailLoaderState
|
||||
>;
|
||||
@useResult
|
||||
$Res call({
|
||||
ProfitSharingDetail detail,
|
||||
Option<AnalyticFailure> failureOption,
|
||||
bool isFetching,
|
||||
String parentCategoryId,
|
||||
String expandedCategoryId,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
});
|
||||
|
||||
$ProfitSharingDetailCopyWith<$Res> get detail;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ProfitSharingDetailLoaderStateCopyWithImpl<
|
||||
$Res,
|
||||
$Val extends ProfitSharingDetailLoaderState
|
||||
>
|
||||
implements $ProfitSharingDetailLoaderStateCopyWith<$Res> {
|
||||
_$ProfitSharingDetailLoaderStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ProfitSharingDetailLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? detail = null,
|
||||
Object? failureOption = null,
|
||||
Object? isFetching = null,
|
||||
Object? parentCategoryId = null,
|
||||
Object? expandedCategoryId = null,
|
||||
Object? dateFrom = null,
|
||||
Object? dateTo = null,
|
||||
}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
detail: null == detail
|
||||
? _value.detail
|
||||
: detail // ignore: cast_nullable_to_non_nullable
|
||||
as ProfitSharingDetail,
|
||||
failureOption: null == failureOption
|
||||
? _value.failureOption
|
||||
: failureOption // ignore: cast_nullable_to_non_nullable
|
||||
as Option<AnalyticFailure>,
|
||||
isFetching: null == isFetching
|
||||
? _value.isFetching
|
||||
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
parentCategoryId: null == parentCategoryId
|
||||
? _value.parentCategoryId
|
||||
: parentCategoryId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
expandedCategoryId: null == expandedCategoryId
|
||||
? _value.expandedCategoryId
|
||||
: expandedCategoryId // 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,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a copy of ProfitSharingDetailLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ProfitSharingDetailCopyWith<$Res> get detail {
|
||||
return $ProfitSharingDetailCopyWith<$Res>(_value.detail, (value) {
|
||||
return _then(_value.copyWith(detail: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ProfitSharingDetailLoaderStateImplCopyWith<$Res>
|
||||
implements $ProfitSharingDetailLoaderStateCopyWith<$Res> {
|
||||
factory _$$ProfitSharingDetailLoaderStateImplCopyWith(
|
||||
_$ProfitSharingDetailLoaderStateImpl value,
|
||||
$Res Function(_$ProfitSharingDetailLoaderStateImpl) then,
|
||||
) = __$$ProfitSharingDetailLoaderStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({
|
||||
ProfitSharingDetail detail,
|
||||
Option<AnalyticFailure> failureOption,
|
||||
bool isFetching,
|
||||
String parentCategoryId,
|
||||
String expandedCategoryId,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
});
|
||||
|
||||
@override
|
||||
$ProfitSharingDetailCopyWith<$Res> get detail;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ProfitSharingDetailLoaderStateImplCopyWithImpl<$Res>
|
||||
extends
|
||||
_$ProfitSharingDetailLoaderStateCopyWithImpl<
|
||||
$Res,
|
||||
_$ProfitSharingDetailLoaderStateImpl
|
||||
>
|
||||
implements _$$ProfitSharingDetailLoaderStateImplCopyWith<$Res> {
|
||||
__$$ProfitSharingDetailLoaderStateImplCopyWithImpl(
|
||||
_$ProfitSharingDetailLoaderStateImpl _value,
|
||||
$Res Function(_$ProfitSharingDetailLoaderStateImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of ProfitSharingDetailLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? detail = null,
|
||||
Object? failureOption = null,
|
||||
Object? isFetching = null,
|
||||
Object? parentCategoryId = null,
|
||||
Object? expandedCategoryId = null,
|
||||
Object? dateFrom = null,
|
||||
Object? dateTo = null,
|
||||
}) {
|
||||
return _then(
|
||||
_$ProfitSharingDetailLoaderStateImpl(
|
||||
detail: null == detail
|
||||
? _value.detail
|
||||
: detail // ignore: cast_nullable_to_non_nullable
|
||||
as ProfitSharingDetail,
|
||||
failureOption: null == failureOption
|
||||
? _value.failureOption
|
||||
: failureOption // ignore: cast_nullable_to_non_nullable
|
||||
as Option<AnalyticFailure>,
|
||||
isFetching: null == isFetching
|
||||
? _value.isFetching
|
||||
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
parentCategoryId: null == parentCategoryId
|
||||
? _value.parentCategoryId
|
||||
: parentCategoryId // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
expandedCategoryId: null == expandedCategoryId
|
||||
? _value.expandedCategoryId
|
||||
: expandedCategoryId // 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ProfitSharingDetailLoaderStateImpl
|
||||
extends _ProfitSharingDetailLoaderState {
|
||||
const _$ProfitSharingDetailLoaderStateImpl({
|
||||
required this.detail,
|
||||
required this.failureOption,
|
||||
this.isFetching = false,
|
||||
this.parentCategoryId = '',
|
||||
this.expandedCategoryId = '',
|
||||
required this.dateFrom,
|
||||
required this.dateTo,
|
||||
}) : super._();
|
||||
|
||||
@override
|
||||
final ProfitSharingDetail detail;
|
||||
@override
|
||||
final Option<AnalyticFailure> failureOption;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isFetching;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String parentCategoryId;
|
||||
@override
|
||||
@JsonKey()
|
||||
final String expandedCategoryId;
|
||||
@override
|
||||
final DateTime dateFrom;
|
||||
@override
|
||||
final DateTime dateTo;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfitSharingDetailLoaderState(detail: $detail, failureOption: $failureOption, isFetching: $isFetching, parentCategoryId: $parentCategoryId, expandedCategoryId: $expandedCategoryId, dateFrom: $dateFrom, dateTo: $dateTo)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ProfitSharingDetailLoaderStateImpl &&
|
||||
(identical(other.detail, detail) || other.detail == detail) &&
|
||||
(identical(other.failureOption, failureOption) ||
|
||||
other.failureOption == failureOption) &&
|
||||
(identical(other.isFetching, isFetching) ||
|
||||
other.isFetching == isFetching) &&
|
||||
(identical(other.parentCategoryId, parentCategoryId) ||
|
||||
other.parentCategoryId == parentCategoryId) &&
|
||||
(identical(other.expandedCategoryId, expandedCategoryId) ||
|
||||
other.expandedCategoryId == expandedCategoryId) &&
|
||||
(identical(other.dateFrom, dateFrom) ||
|
||||
other.dateFrom == dateFrom) &&
|
||||
(identical(other.dateTo, dateTo) || other.dateTo == dateTo));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
detail,
|
||||
failureOption,
|
||||
isFetching,
|
||||
parentCategoryId,
|
||||
expandedCategoryId,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
);
|
||||
|
||||
/// Create a copy of ProfitSharingDetailLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ProfitSharingDetailLoaderStateImplCopyWith<
|
||||
_$ProfitSharingDetailLoaderStateImpl
|
||||
>
|
||||
get copyWith =>
|
||||
__$$ProfitSharingDetailLoaderStateImplCopyWithImpl<
|
||||
_$ProfitSharingDetailLoaderStateImpl
|
||||
>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _ProfitSharingDetailLoaderState
|
||||
extends ProfitSharingDetailLoaderState {
|
||||
const factory _ProfitSharingDetailLoaderState({
|
||||
required final ProfitSharingDetail detail,
|
||||
required final Option<AnalyticFailure> failureOption,
|
||||
final bool isFetching,
|
||||
final String parentCategoryId,
|
||||
final String expandedCategoryId,
|
||||
required final DateTime dateFrom,
|
||||
required final DateTime dateTo,
|
||||
}) = _$ProfitSharingDetailLoaderStateImpl;
|
||||
const _ProfitSharingDetailLoaderState._() : super._();
|
||||
|
||||
@override
|
||||
ProfitSharingDetail get detail;
|
||||
@override
|
||||
Option<AnalyticFailure> get failureOption;
|
||||
@override
|
||||
bool get isFetching;
|
||||
@override
|
||||
String get parentCategoryId;
|
||||
@override
|
||||
String get expandedCategoryId;
|
||||
@override
|
||||
DateTime get dateFrom;
|
||||
@override
|
||||
DateTime get dateTo;
|
||||
|
||||
/// Create a copy of ProfitSharingDetailLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ProfitSharingDetailLoaderStateImplCopyWith<
|
||||
_$ProfitSharingDetailLoaderStateImpl
|
||||
>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
part of 'profit_sharing_detail_loader_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class ProfitSharingDetailLoaderEvent with _$ProfitSharingDetailLoaderEvent {
|
||||
const factory ProfitSharingDetailLoaderEvent.fetched({
|
||||
required String parentCategoryId,
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
}) = _Fetched;
|
||||
|
||||
/// Buka/tutup daftar produk pada satu sub kategori.
|
||||
const factory ProfitSharingDetailLoaderEvent.expandedCategoryChanged(
|
||||
String categoryId,
|
||||
) = _ExpandedCategoryChanged;
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
part of 'profit_sharing_detail_loader_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class ProfitSharingDetailLoaderState with _$ProfitSharingDetailLoaderState {
|
||||
const ProfitSharingDetailLoaderState._();
|
||||
|
||||
const factory ProfitSharingDetailLoaderState({
|
||||
required ProfitSharingDetail detail,
|
||||
required Option<AnalyticFailure> failureOption,
|
||||
@Default(false) bool isFetching,
|
||||
@Default('') String parentCategoryId,
|
||||
@Default('') String expandedCategoryId,
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
}) = _ProfitSharingDetailLoaderState;
|
||||
|
||||
factory ProfitSharingDetailLoaderState.initial() {
|
||||
final now = DateTime.now();
|
||||
return ProfitSharingDetailLoaderState(
|
||||
detail: ProfitSharingDetail.empty(),
|
||||
failureOption: none(),
|
||||
dateFrom: DateTime(now.year, now.month, 1),
|
||||
dateTo: DateTime(now.year, now.month + 1, 0),
|
||||
);
|
||||
}
|
||||
|
||||
bool isExpanded(String categoryId) => expandedCategoryId == categoryId;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
import '../../../domain/analytic/analytic.dart';
|
||||
import '../../../domain/analytic/repositories/i_analytic_repository.dart';
|
||||
|
||||
part 'profit_sharing_loader_event.dart';
|
||||
part 'profit_sharing_loader_state.dart';
|
||||
part 'profit_sharing_loader_bloc.freezed.dart';
|
||||
|
||||
@injectable
|
||||
class ProfitSharingLoaderBloc
|
||||
extends Bloc<ProfitSharingLoaderEvent, ProfitSharingLoaderState> {
|
||||
final IAnalyticRepository _repository;
|
||||
|
||||
ProfitSharingLoaderBloc(this._repository)
|
||||
: super(ProfitSharingLoaderState.initial()) {
|
||||
on<ProfitSharingLoaderEvent>(_onProfitSharingLoaderEvent);
|
||||
}
|
||||
|
||||
Future<void> _onProfitSharingLoaderEvent(
|
||||
ProfitSharingLoaderEvent event,
|
||||
Emitter<ProfitSharingLoaderState> emit,
|
||||
) {
|
||||
return event.map(
|
||||
rangeDateChanged: (e) async {
|
||||
emit(state.copyWith(dateFrom: e.dateFrom, dateTo: e.dateTo));
|
||||
},
|
||||
periodTypeChanged: (e) async {
|
||||
emit(state.copyWith(periodType: e.periodType));
|
||||
},
|
||||
fetched: (e) async {
|
||||
emit(state.copyWith(isFetching: true, failureOption: none()));
|
||||
|
||||
final result = await _repository.getProfitSharing(
|
||||
dateFrom: state.dateFrom,
|
||||
dateTo: state.dateTo,
|
||||
);
|
||||
|
||||
final newState = result.fold(
|
||||
(f) => state.copyWith(failureOption: optionOf(f)),
|
||||
(profitSharing) => state.copyWith(profitSharing: profitSharing),
|
||||
);
|
||||
|
||||
emit(newState.copyWith(isFetching: false));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
+807
@@ -0,0 +1,807 @@
|
||||
// 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_sharing_loader_bloc.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
|
||||
);
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ProfitSharingLoaderEvent {
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||
rangeDateChanged,
|
||||
required TResult Function(ProfitSharingPeriodType periodType)
|
||||
periodTypeChanged,
|
||||
required TResult Function() fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult? Function(ProfitSharingPeriodType periodType)? periodTypeChanged,
|
||||
TResult? Function()? fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult Function(ProfitSharingPeriodType periodType)? periodTypeChanged,
|
||||
TResult Function()? fetched,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_RangeDateChanged value) rangeDateChanged,
|
||||
required TResult Function(_PeriodTypeChanged value) periodTypeChanged,
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult? Function(_PeriodTypeChanged value)? periodTypeChanged,
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult Function(_PeriodTypeChanged value)? periodTypeChanged,
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ProfitSharingLoaderEventCopyWith<$Res> {
|
||||
factory $ProfitSharingLoaderEventCopyWith(
|
||||
ProfitSharingLoaderEvent value,
|
||||
$Res Function(ProfitSharingLoaderEvent) then,
|
||||
) = _$ProfitSharingLoaderEventCopyWithImpl<$Res, ProfitSharingLoaderEvent>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ProfitSharingLoaderEventCopyWithImpl<
|
||||
$Res,
|
||||
$Val extends ProfitSharingLoaderEvent
|
||||
>
|
||||
implements $ProfitSharingLoaderEventCopyWith<$Res> {
|
||||
_$ProfitSharingLoaderEventCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ProfitSharingLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$RangeDateChangedImplCopyWith<$Res> {
|
||||
factory _$$RangeDateChangedImplCopyWith(
|
||||
_$RangeDateChangedImpl value,
|
||||
$Res Function(_$RangeDateChangedImpl) then,
|
||||
) = __$$RangeDateChangedImplCopyWithImpl<$Res>;
|
||||
@useResult
|
||||
$Res call({DateTime dateFrom, DateTime dateTo});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$RangeDateChangedImplCopyWithImpl<$Res>
|
||||
extends _$ProfitSharingLoaderEventCopyWithImpl<$Res, _$RangeDateChangedImpl>
|
||||
implements _$$RangeDateChangedImplCopyWith<$Res> {
|
||||
__$$RangeDateChangedImplCopyWithImpl(
|
||||
_$RangeDateChangedImpl _value,
|
||||
$Res Function(_$RangeDateChangedImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of ProfitSharingLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({Object? dateFrom = null, Object? dateTo = null}) {
|
||||
return _then(
|
||||
_$RangeDateChangedImpl(
|
||||
null == dateFrom
|
||||
? _value.dateFrom
|
||||
: dateFrom // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
null == dateTo
|
||||
? _value.dateTo
|
||||
: dateTo // ignore: cast_nullable_to_non_nullable
|
||||
as DateTime,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$RangeDateChangedImpl implements _RangeDateChanged {
|
||||
const _$RangeDateChangedImpl(this.dateFrom, this.dateTo);
|
||||
|
||||
@override
|
||||
final DateTime dateFrom;
|
||||
@override
|
||||
final DateTime dateTo;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfitSharingLoaderEvent.rangeDateChanged(dateFrom: $dateFrom, dateTo: $dateTo)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$RangeDateChangedImpl &&
|
||||
(identical(other.dateFrom, dateFrom) ||
|
||||
other.dateFrom == dateFrom) &&
|
||||
(identical(other.dateTo, dateTo) || other.dateTo == dateTo));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, dateFrom, dateTo);
|
||||
|
||||
/// Create a copy of ProfitSharingLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$RangeDateChangedImplCopyWith<_$RangeDateChangedImpl> get copyWith =>
|
||||
__$$RangeDateChangedImplCopyWithImpl<_$RangeDateChangedImpl>(
|
||||
this,
|
||||
_$identity,
|
||||
);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||
rangeDateChanged,
|
||||
required TResult Function(ProfitSharingPeriodType periodType)
|
||||
periodTypeChanged,
|
||||
required TResult Function() fetched,
|
||||
}) {
|
||||
return rangeDateChanged(dateFrom, dateTo);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult? Function(ProfitSharingPeriodType periodType)? periodTypeChanged,
|
||||
TResult? Function()? fetched,
|
||||
}) {
|
||||
return rangeDateChanged?.call(dateFrom, dateTo);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult Function(ProfitSharingPeriodType periodType)? periodTypeChanged,
|
||||
TResult Function()? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (rangeDateChanged != null) {
|
||||
return rangeDateChanged(dateFrom, dateTo);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_RangeDateChanged value) rangeDateChanged,
|
||||
required TResult Function(_PeriodTypeChanged value) periodTypeChanged,
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
}) {
|
||||
return rangeDateChanged(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult? Function(_PeriodTypeChanged value)? periodTypeChanged,
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
}) {
|
||||
return rangeDateChanged?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult Function(_PeriodTypeChanged value)? periodTypeChanged,
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (rangeDateChanged != null) {
|
||||
return rangeDateChanged(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _RangeDateChanged implements ProfitSharingLoaderEvent {
|
||||
const factory _RangeDateChanged(
|
||||
final DateTime dateFrom,
|
||||
final DateTime dateTo,
|
||||
) = _$RangeDateChangedImpl;
|
||||
|
||||
DateTime get dateFrom;
|
||||
DateTime get dateTo;
|
||||
|
||||
/// Create a copy of ProfitSharingLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$RangeDateChangedImplCopyWith<_$RangeDateChangedImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$PeriodTypeChangedImplCopyWith<$Res> {
|
||||
factory _$$PeriodTypeChangedImplCopyWith(
|
||||
_$PeriodTypeChangedImpl value,
|
||||
$Res Function(_$PeriodTypeChangedImpl) then,
|
||||
) = __$$PeriodTypeChangedImplCopyWithImpl<$Res>;
|
||||
@useResult
|
||||
$Res call({ProfitSharingPeriodType periodType});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$PeriodTypeChangedImplCopyWithImpl<$Res>
|
||||
extends
|
||||
_$ProfitSharingLoaderEventCopyWithImpl<$Res, _$PeriodTypeChangedImpl>
|
||||
implements _$$PeriodTypeChangedImplCopyWith<$Res> {
|
||||
__$$PeriodTypeChangedImplCopyWithImpl(
|
||||
_$PeriodTypeChangedImpl _value,
|
||||
$Res Function(_$PeriodTypeChangedImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of ProfitSharingLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({Object? periodType = null}) {
|
||||
return _then(
|
||||
_$PeriodTypeChangedImpl(
|
||||
null == periodType
|
||||
? _value.periodType
|
||||
: periodType // ignore: cast_nullable_to_non_nullable
|
||||
as ProfitSharingPeriodType,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$PeriodTypeChangedImpl implements _PeriodTypeChanged {
|
||||
const _$PeriodTypeChangedImpl(this.periodType);
|
||||
|
||||
@override
|
||||
final ProfitSharingPeriodType periodType;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfitSharingLoaderEvent.periodTypeChanged(periodType: $periodType)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$PeriodTypeChangedImpl &&
|
||||
(identical(other.periodType, periodType) ||
|
||||
other.periodType == periodType));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, periodType);
|
||||
|
||||
/// Create a copy of ProfitSharingLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$PeriodTypeChangedImplCopyWith<_$PeriodTypeChangedImpl> get copyWith =>
|
||||
__$$PeriodTypeChangedImplCopyWithImpl<_$PeriodTypeChangedImpl>(
|
||||
this,
|
||||
_$identity,
|
||||
);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||
rangeDateChanged,
|
||||
required TResult Function(ProfitSharingPeriodType periodType)
|
||||
periodTypeChanged,
|
||||
required TResult Function() fetched,
|
||||
}) {
|
||||
return periodTypeChanged(periodType);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult? Function(ProfitSharingPeriodType periodType)? periodTypeChanged,
|
||||
TResult? Function()? fetched,
|
||||
}) {
|
||||
return periodTypeChanged?.call(periodType);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult Function(ProfitSharingPeriodType periodType)? periodTypeChanged,
|
||||
TResult Function()? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (periodTypeChanged != null) {
|
||||
return periodTypeChanged(periodType);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_RangeDateChanged value) rangeDateChanged,
|
||||
required TResult Function(_PeriodTypeChanged value) periodTypeChanged,
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
}) {
|
||||
return periodTypeChanged(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult? Function(_PeriodTypeChanged value)? periodTypeChanged,
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
}) {
|
||||
return periodTypeChanged?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult Function(_PeriodTypeChanged value)? periodTypeChanged,
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (periodTypeChanged != null) {
|
||||
return periodTypeChanged(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _PeriodTypeChanged implements ProfitSharingLoaderEvent {
|
||||
const factory _PeriodTypeChanged(final ProfitSharingPeriodType periodType) =
|
||||
_$PeriodTypeChangedImpl;
|
||||
|
||||
ProfitSharingPeriodType get periodType;
|
||||
|
||||
/// Create a copy of ProfitSharingLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$PeriodTypeChangedImplCopyWith<_$PeriodTypeChangedImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$FetchedImplCopyWith<$Res> {
|
||||
factory _$$FetchedImplCopyWith(
|
||||
_$FetchedImpl value,
|
||||
$Res Function(_$FetchedImpl) then,
|
||||
) = __$$FetchedImplCopyWithImpl<$Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$FetchedImplCopyWithImpl<$Res>
|
||||
extends _$ProfitSharingLoaderEventCopyWithImpl<$Res, _$FetchedImpl>
|
||||
implements _$$FetchedImplCopyWith<$Res> {
|
||||
__$$FetchedImplCopyWithImpl(
|
||||
_$FetchedImpl _value,
|
||||
$Res Function(_$FetchedImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of ProfitSharingLoaderEvent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$FetchedImpl implements _Fetched {
|
||||
const _$FetchedImpl();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfitSharingLoaderEvent.fetched()';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType && other is _$FetchedImpl);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(DateTime dateFrom, DateTime dateTo)
|
||||
rangeDateChanged,
|
||||
required TResult Function(ProfitSharingPeriodType periodType)
|
||||
periodTypeChanged,
|
||||
required TResult Function() fetched,
|
||||
}) {
|
||||
return fetched();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult? Function(ProfitSharingPeriodType periodType)? periodTypeChanged,
|
||||
TResult? Function()? fetched,
|
||||
}) {
|
||||
return fetched?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
|
||||
TResult Function(ProfitSharingPeriodType periodType)? periodTypeChanged,
|
||||
TResult Function()? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (fetched != null) {
|
||||
return fetched();
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_RangeDateChanged value) rangeDateChanged,
|
||||
required TResult Function(_PeriodTypeChanged value) periodTypeChanged,
|
||||
required TResult Function(_Fetched value) fetched,
|
||||
}) {
|
||||
return fetched(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult? Function(_PeriodTypeChanged value)? periodTypeChanged,
|
||||
TResult? Function(_Fetched value)? fetched,
|
||||
}) {
|
||||
return fetched?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_RangeDateChanged value)? rangeDateChanged,
|
||||
TResult Function(_PeriodTypeChanged value)? periodTypeChanged,
|
||||
TResult Function(_Fetched value)? fetched,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (fetched != null) {
|
||||
return fetched(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Fetched implements ProfitSharingLoaderEvent {
|
||||
const factory _Fetched() = _$FetchedImpl;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ProfitSharingLoaderState {
|
||||
ProfitSharing get profitSharing => throw _privateConstructorUsedError;
|
||||
Option<AnalyticFailure> get failureOption =>
|
||||
throw _privateConstructorUsedError;
|
||||
bool get isFetching => throw _privateConstructorUsedError;
|
||||
ProfitSharingPeriodType get periodType => throw _privateConstructorUsedError;
|
||||
DateTime get dateFrom => throw _privateConstructorUsedError;
|
||||
DateTime get dateTo => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of ProfitSharingLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ProfitSharingLoaderStateCopyWith<ProfitSharingLoaderState> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ProfitSharingLoaderStateCopyWith<$Res> {
|
||||
factory $ProfitSharingLoaderStateCopyWith(
|
||||
ProfitSharingLoaderState value,
|
||||
$Res Function(ProfitSharingLoaderState) then,
|
||||
) = _$ProfitSharingLoaderStateCopyWithImpl<$Res, ProfitSharingLoaderState>;
|
||||
@useResult
|
||||
$Res call({
|
||||
ProfitSharing profitSharing,
|
||||
Option<AnalyticFailure> failureOption,
|
||||
bool isFetching,
|
||||
ProfitSharingPeriodType periodType,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
});
|
||||
|
||||
$ProfitSharingCopyWith<$Res> get profitSharing;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ProfitSharingLoaderStateCopyWithImpl<
|
||||
$Res,
|
||||
$Val extends ProfitSharingLoaderState
|
||||
>
|
||||
implements $ProfitSharingLoaderStateCopyWith<$Res> {
|
||||
_$ProfitSharingLoaderStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of ProfitSharingLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? profitSharing = null,
|
||||
Object? failureOption = null,
|
||||
Object? isFetching = null,
|
||||
Object? periodType = null,
|
||||
Object? dateFrom = null,
|
||||
Object? dateTo = null,
|
||||
}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
profitSharing: null == profitSharing
|
||||
? _value.profitSharing
|
||||
: profitSharing // ignore: cast_nullable_to_non_nullable
|
||||
as ProfitSharing,
|
||||
failureOption: null == failureOption
|
||||
? _value.failureOption
|
||||
: failureOption // ignore: cast_nullable_to_non_nullable
|
||||
as Option<AnalyticFailure>,
|
||||
isFetching: null == isFetching
|
||||
? _value.isFetching
|
||||
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
periodType: null == periodType
|
||||
? _value.periodType
|
||||
: periodType // ignore: cast_nullable_to_non_nullable
|
||||
as ProfitSharingPeriodType,
|
||||
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,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a copy of ProfitSharingLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$ProfitSharingCopyWith<$Res> get profitSharing {
|
||||
return $ProfitSharingCopyWith<$Res>(_value.profitSharing, (value) {
|
||||
return _then(_value.copyWith(profitSharing: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ProfitSharingLoaderStateImplCopyWith<$Res>
|
||||
implements $ProfitSharingLoaderStateCopyWith<$Res> {
|
||||
factory _$$ProfitSharingLoaderStateImplCopyWith(
|
||||
_$ProfitSharingLoaderStateImpl value,
|
||||
$Res Function(_$ProfitSharingLoaderStateImpl) then,
|
||||
) = __$$ProfitSharingLoaderStateImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({
|
||||
ProfitSharing profitSharing,
|
||||
Option<AnalyticFailure> failureOption,
|
||||
bool isFetching,
|
||||
ProfitSharingPeriodType periodType,
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
});
|
||||
|
||||
@override
|
||||
$ProfitSharingCopyWith<$Res> get profitSharing;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ProfitSharingLoaderStateImplCopyWithImpl<$Res>
|
||||
extends
|
||||
_$ProfitSharingLoaderStateCopyWithImpl<
|
||||
$Res,
|
||||
_$ProfitSharingLoaderStateImpl
|
||||
>
|
||||
implements _$$ProfitSharingLoaderStateImplCopyWith<$Res> {
|
||||
__$$ProfitSharingLoaderStateImplCopyWithImpl(
|
||||
_$ProfitSharingLoaderStateImpl _value,
|
||||
$Res Function(_$ProfitSharingLoaderStateImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of ProfitSharingLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? profitSharing = null,
|
||||
Object? failureOption = null,
|
||||
Object? isFetching = null,
|
||||
Object? periodType = null,
|
||||
Object? dateFrom = null,
|
||||
Object? dateTo = null,
|
||||
}) {
|
||||
return _then(
|
||||
_$ProfitSharingLoaderStateImpl(
|
||||
profitSharing: null == profitSharing
|
||||
? _value.profitSharing
|
||||
: profitSharing // ignore: cast_nullable_to_non_nullable
|
||||
as ProfitSharing,
|
||||
failureOption: null == failureOption
|
||||
? _value.failureOption
|
||||
: failureOption // ignore: cast_nullable_to_non_nullable
|
||||
as Option<AnalyticFailure>,
|
||||
isFetching: null == isFetching
|
||||
? _value.isFetching
|
||||
: isFetching // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
periodType: null == periodType
|
||||
? _value.periodType
|
||||
: periodType // ignore: cast_nullable_to_non_nullable
|
||||
as ProfitSharingPeriodType,
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$ProfitSharingLoaderStateImpl extends _ProfitSharingLoaderState {
|
||||
const _$ProfitSharingLoaderStateImpl({
|
||||
required this.profitSharing,
|
||||
required this.failureOption,
|
||||
this.isFetching = false,
|
||||
this.periodType = ProfitSharingPeriodType.weekly,
|
||||
required this.dateFrom,
|
||||
required this.dateTo,
|
||||
}) : super._();
|
||||
|
||||
@override
|
||||
final ProfitSharing profitSharing;
|
||||
@override
|
||||
final Option<AnalyticFailure> failureOption;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool isFetching;
|
||||
@override
|
||||
@JsonKey()
|
||||
final ProfitSharingPeriodType periodType;
|
||||
@override
|
||||
final DateTime dateFrom;
|
||||
@override
|
||||
final DateTime dateTo;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfitSharingLoaderState(profitSharing: $profitSharing, failureOption: $failureOption, isFetching: $isFetching, periodType: $periodType, dateFrom: $dateFrom, dateTo: $dateTo)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ProfitSharingLoaderStateImpl &&
|
||||
(identical(other.profitSharing, profitSharing) ||
|
||||
other.profitSharing == profitSharing) &&
|
||||
(identical(other.failureOption, failureOption) ||
|
||||
other.failureOption == failureOption) &&
|
||||
(identical(other.isFetching, isFetching) ||
|
||||
other.isFetching == isFetching) &&
|
||||
(identical(other.periodType, periodType) ||
|
||||
other.periodType == periodType) &&
|
||||
(identical(other.dateFrom, dateFrom) ||
|
||||
other.dateFrom == dateFrom) &&
|
||||
(identical(other.dateTo, dateTo) || other.dateTo == dateTo));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
profitSharing,
|
||||
failureOption,
|
||||
isFetching,
|
||||
periodType,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
);
|
||||
|
||||
/// Create a copy of ProfitSharingLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ProfitSharingLoaderStateImplCopyWith<_$ProfitSharingLoaderStateImpl>
|
||||
get copyWith =>
|
||||
__$$ProfitSharingLoaderStateImplCopyWithImpl<
|
||||
_$ProfitSharingLoaderStateImpl
|
||||
>(this, _$identity);
|
||||
}
|
||||
|
||||
abstract class _ProfitSharingLoaderState extends ProfitSharingLoaderState {
|
||||
const factory _ProfitSharingLoaderState({
|
||||
required final ProfitSharing profitSharing,
|
||||
required final Option<AnalyticFailure> failureOption,
|
||||
final bool isFetching,
|
||||
final ProfitSharingPeriodType periodType,
|
||||
required final DateTime dateFrom,
|
||||
required final DateTime dateTo,
|
||||
}) = _$ProfitSharingLoaderStateImpl;
|
||||
const _ProfitSharingLoaderState._() : super._();
|
||||
|
||||
@override
|
||||
ProfitSharing get profitSharing;
|
||||
@override
|
||||
Option<AnalyticFailure> get failureOption;
|
||||
@override
|
||||
bool get isFetching;
|
||||
@override
|
||||
ProfitSharingPeriodType get periodType;
|
||||
@override
|
||||
DateTime get dateFrom;
|
||||
@override
|
||||
DateTime get dateTo;
|
||||
|
||||
/// Create a copy of ProfitSharingLoaderState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ProfitSharingLoaderStateImplCopyWith<_$ProfitSharingLoaderStateImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
part of 'profit_sharing_loader_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class ProfitSharingLoaderEvent with _$ProfitSharingLoaderEvent {
|
||||
const factory ProfitSharingLoaderEvent.rangeDateChanged(
|
||||
DateTime dateFrom,
|
||||
DateTime dateTo,
|
||||
) = _RangeDateChanged;
|
||||
const factory ProfitSharingLoaderEvent.periodTypeChanged(
|
||||
ProfitSharingPeriodType periodType,
|
||||
) = _PeriodTypeChanged;
|
||||
const factory ProfitSharingLoaderEvent.fetched() = _Fetched;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
part of 'profit_sharing_loader_bloc.dart';
|
||||
|
||||
/// Tampilan rincian bagi hasil: per minggu atau per bulan.
|
||||
enum ProfitSharingPeriodType { weekly, monthly }
|
||||
|
||||
@freezed
|
||||
class ProfitSharingLoaderState with _$ProfitSharingLoaderState {
|
||||
const ProfitSharingLoaderState._();
|
||||
|
||||
const factory ProfitSharingLoaderState({
|
||||
required ProfitSharing profitSharing,
|
||||
required Option<AnalyticFailure> failureOption,
|
||||
@Default(false) bool isFetching,
|
||||
@Default(ProfitSharingPeriodType.weekly) ProfitSharingPeriodType periodType,
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
}) = _ProfitSharingLoaderState;
|
||||
|
||||
factory ProfitSharingLoaderState.initial() {
|
||||
final now = DateTime.now();
|
||||
return ProfitSharingLoaderState(
|
||||
profitSharing: ProfitSharing.empty(),
|
||||
failureOption: none(),
|
||||
dateFrom: DateTime(now.year, now.month, 1),
|
||||
dateTo: DateTime(now.year, now.month + 1, 0),
|
||||
);
|
||||
}
|
||||
|
||||
/// Baris periode sesuai tab yang dipilih, periode kosong disembunyikan.
|
||||
List<ProfitSharingPeriod> get periods {
|
||||
final budget = profitSharing.budget;
|
||||
final list = periodType == ProfitSharingPeriodType.weekly
|
||||
? budget.weekly
|
||||
: budget.monthly;
|
||||
return list.where((e) => e.hasRevenue).toList();
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ class SalesLoaderState with _$SalesLoaderState {
|
||||
factory SalesLoaderState.initial() => SalesLoaderState(
|
||||
sales: SalesAnalytic.empty(),
|
||||
failureOptionSales: none(),
|
||||
dateFrom: DateTime.now().subtract(const Duration(days: 30)),
|
||||
dateFrom: DateTime.now(),
|
||||
dateTo: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,6 @@ class OrderLoaderState with _$OrderLoaderState {
|
||||
failureOptionOrder: none(),
|
||||
dateFrom: DateTime.now(),
|
||||
dateTo: DateTime.now(),
|
||||
status: 'all',
|
||||
status: 'pending',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,14 +22,29 @@ class SelectedOutletBloc
|
||||
Future<void> _onSelectedOutletEvent(
|
||||
SelectedOutletEvent event,
|
||||
Emitter<SelectedOutletState> emit,
|
||||
) {
|
||||
) {
|
||||
return event.map(
|
||||
loaded: (e) async {
|
||||
final savedId = _localDataProvider.getSelectedOutletId();
|
||||
emit(state.copyWith(selectedOutletId: savedId));
|
||||
final savedName = _localDataProvider.getSelectedOutletName();
|
||||
|
||||
if (savedId != null && savedName != null) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
selectedOutletId: savedId,
|
||||
selectedOutlet: Outlet.empty().copyWith(
|
||||
id: savedId,
|
||||
name: savedName,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(state.copyWith(selectedOutletId: savedId));
|
||||
}
|
||||
},
|
||||
selected: (e) async {
|
||||
await _localDataProvider.saveSelectedOutletId(e.outlet.id);
|
||||
await _localDataProvider.saveSelectedOutletName(e.outlet.name);
|
||||
emit(
|
||||
state.copyWith(
|
||||
selectedOutlet: e.outlet,
|
||||
|
||||
@@ -3,4 +3,5 @@ class LocalStorageKey {
|
||||
static const String token = 'token';
|
||||
static const String user = 'user';
|
||||
static const String selectedOutletId = 'selected_outlet_id';
|
||||
static const String selectedOutletName = 'selected_outlet_name';
|
||||
}
|
||||
|
||||
@@ -6,4 +6,7 @@ extension IntegerExt on int {
|
||||
symbol: 'Rp. ',
|
||||
decimalDigits: 0,
|
||||
).format(this);
|
||||
|
||||
/// Format ribuan tanpa simbol mata uang, contoh: 1.639
|
||||
String get thousandFormat => NumberFormat.decimalPattern('id').format(this);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@ part 'app_value.dart';
|
||||
class ThemeApp {
|
||||
static ThemeData get theme => ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: AppColor.primary,
|
||||
primary: AppColor.primary,
|
||||
brightness: Brightness.light,
|
||||
),
|
||||
scaffoldBackgroundColor: AppColor.background,
|
||||
fontFamily: FontFamily.quicksand,
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
@@ -67,6 +72,7 @@ class ThemeApp {
|
||||
),
|
||||
bottomSheetTheme: BottomSheetThemeData(
|
||||
backgroundColor: AppColor.white,
|
||||
)
|
||||
surfaceTintColor: Colors.transparent,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,10 +10,12 @@ class ApiPath {
|
||||
static const String dashboardAnalytic = '/api/v1/analytics/dashboard';
|
||||
static const String productAnalytic = '/api/v1/analytics/products';
|
||||
static const String paymentMethodAnalytic =
|
||||
'/api/v1/analytics/paymentMethods';
|
||||
'/api/v1/analytics/payment-methods';
|
||||
static const String purchasingAnalytic = '/api/v1/analytics/purchasing';
|
||||
static const String exclusiveSummaryAnalytic =
|
||||
'/api/v1/analytics/exclusive-summary/period';
|
||||
static const String parentCategoryAnalytic =
|
||||
'/api/v1/analytics/parent-categories';
|
||||
|
||||
// Inventory
|
||||
static const String inventoryReportDetail =
|
||||
|
||||
@@ -13,4 +13,5 @@ part 'entities/product_analytic_entity.dart';
|
||||
part 'entities/payment_method_analytic_entity.dart';
|
||||
part 'entities/purchasing_analytic_entity.dart';
|
||||
part 'entities/exclusive_summary_entity.dart';
|
||||
part 'entities/profit_sharing_entity.dart';
|
||||
part 'failures/analytic_failure.dart';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ class CategoryAnalytic with _$CategoryAnalytic {
|
||||
const factory CategoryAnalytic({
|
||||
required String organizationId,
|
||||
required String outletId,
|
||||
required String outletName,
|
||||
required String dateFrom,
|
||||
required String dateTo,
|
||||
required List<CategoryAnalyticItem> data,
|
||||
@@ -13,6 +14,7 @@ class CategoryAnalytic with _$CategoryAnalytic {
|
||||
factory CategoryAnalytic.empty() => const CategoryAnalytic(
|
||||
organizationId: "",
|
||||
outletId: "",
|
||||
outletName: "",
|
||||
dateFrom: "",
|
||||
dateTo: "",
|
||||
data: [],
|
||||
|
||||
@@ -5,6 +5,7 @@ class DashboardAnalytic with _$DashboardAnalytic {
|
||||
const factory DashboardAnalytic({
|
||||
required String organizationId,
|
||||
required String outletId,
|
||||
required String outletName,
|
||||
required String dateFrom,
|
||||
required String dateTo,
|
||||
required DashboardOverview overview,
|
||||
@@ -16,6 +17,7 @@ class DashboardAnalytic with _$DashboardAnalytic {
|
||||
factory DashboardAnalytic.empty() => DashboardAnalytic(
|
||||
organizationId: '',
|
||||
outletId: '',
|
||||
outletName: '',
|
||||
dateFrom: '',
|
||||
dateTo: '',
|
||||
overview: DashboardOverview.empty(),
|
||||
@@ -34,6 +36,9 @@ class DashboardOverview with _$DashboardOverview {
|
||||
required int totalCustomers,
|
||||
required int voidedOrders,
|
||||
required int refundedOrders,
|
||||
required int totalItemSold,
|
||||
required int totalLowStock,
|
||||
required int totalProductActive,
|
||||
}) = _DashboardOverview;
|
||||
|
||||
factory DashboardOverview.empty() => const DashboardOverview(
|
||||
@@ -43,6 +48,9 @@ class DashboardOverview with _$DashboardOverview {
|
||||
totalCustomers: 0,
|
||||
voidedOrders: 0,
|
||||
refundedOrders: 0,
|
||||
totalItemSold: 0,
|
||||
totalLowStock: 0,
|
||||
totalProductActive: 0,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ class ExclusiveSummary with _$ExclusiveSummary {
|
||||
const factory ExclusiveSummary({
|
||||
required String organizationId,
|
||||
required String outletId,
|
||||
required String outletName,
|
||||
required ExclusiveSummaryPeriod period,
|
||||
required ExclusiveSummarySummary summary,
|
||||
required ExclusiveSummaryReimburse reimburse,
|
||||
@@ -17,6 +18,7 @@ class ExclusiveSummary with _$ExclusiveSummary {
|
||||
factory ExclusiveSummary.empty() => ExclusiveSummary(
|
||||
organizationId: '',
|
||||
outletId: '',
|
||||
outletName: '',
|
||||
period: ExclusiveSummaryPeriod.empty(),
|
||||
summary: ExclusiveSummarySummary.empty(),
|
||||
reimburse: ExclusiveSummaryReimburse.empty(),
|
||||
@@ -79,12 +81,11 @@ class ExclusiveSummaryReimburse with _$ExclusiveSummaryReimburse {
|
||||
required int totalReimburse,
|
||||
}) = _ExclusiveSummaryReimburse;
|
||||
|
||||
factory ExclusiveSummaryReimburse.empty() =>
|
||||
const ExclusiveSummaryReimburse(
|
||||
totalCost: 0,
|
||||
excludedSalaryStaff: 0,
|
||||
totalReimburse: 0,
|
||||
);
|
||||
factory ExclusiveSummaryReimburse.empty() => const ExclusiveSummaryReimburse(
|
||||
totalCost: 0,
|
||||
excludedSalaryStaff: 0,
|
||||
totalReimburse: 0,
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
||||
@@ -5,6 +5,7 @@ class PaymentMethodAnalytic with _$PaymentMethodAnalytic {
|
||||
const factory PaymentMethodAnalytic({
|
||||
required String organizationId,
|
||||
required String outletId,
|
||||
required String outletName,
|
||||
required String dateFrom,
|
||||
required String dateTo,
|
||||
required String groupBy,
|
||||
@@ -15,6 +16,7 @@ class PaymentMethodAnalytic with _$PaymentMethodAnalytic {
|
||||
factory PaymentMethodAnalytic.empty() => PaymentMethodAnalytic(
|
||||
organizationId: '',
|
||||
outletId: '',
|
||||
outletName: '',
|
||||
dateFrom: '',
|
||||
dateTo: '',
|
||||
groupBy: '',
|
||||
|
||||
@@ -5,6 +5,7 @@ class ProductAnalytic with _$ProductAnalytic {
|
||||
const factory ProductAnalytic({
|
||||
required String organizationId,
|
||||
required String outletId,
|
||||
required String outletName,
|
||||
required String dateFrom,
|
||||
required String dateTo,
|
||||
required List<ProductAnalyticData> data,
|
||||
@@ -13,6 +14,7 @@ class ProductAnalytic with _$ProductAnalytic {
|
||||
factory ProductAnalytic.empty() => const ProductAnalytic(
|
||||
organizationId: '',
|
||||
outletId: '',
|
||||
outletName: '',
|
||||
dateFrom: '',
|
||||
dateTo: '',
|
||||
data: [],
|
||||
@@ -24,22 +26,40 @@ class ProductAnalyticData with _$ProductAnalyticData {
|
||||
const factory ProductAnalyticData({
|
||||
required String productId,
|
||||
required String productName,
|
||||
required String productSku,
|
||||
required int productPrice,
|
||||
required String categoryId,
|
||||
required String categoryName,
|
||||
required int categoryOrder,
|
||||
required int quantitySold,
|
||||
required int revenue,
|
||||
required double averagePrice,
|
||||
required int orderCount,
|
||||
required int standardHppPerUnit,
|
||||
required int standardHppTotal,
|
||||
required int fifoHppPerUnit,
|
||||
required int fifoHppTotal,
|
||||
required int movingAverageHppPerUnit,
|
||||
required int movingAverageHppTotal,
|
||||
}) = _ProductAnalyticData;
|
||||
|
||||
factory ProductAnalyticData.empty() => const ProductAnalyticData(
|
||||
productId: '',
|
||||
productName: '',
|
||||
productSku: '',
|
||||
productPrice: 0,
|
||||
categoryId: '',
|
||||
categoryName: '',
|
||||
categoryOrder: 0,
|
||||
quantitySold: 0,
|
||||
revenue: 0,
|
||||
averagePrice: 0.0,
|
||||
orderCount: 0,
|
||||
standardHppPerUnit: 0,
|
||||
standardHppTotal: 0,
|
||||
fifoHppPerUnit: 0,
|
||||
fifoHppTotal: 0,
|
||||
movingAverageHppPerUnit: 0,
|
||||
movingAverageHppTotal: 0,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,22 +4,34 @@ part of '../analytic.dart';
|
||||
class ProfitLossAnalytic with _$ProfitLossAnalytic {
|
||||
const factory ProfitLossAnalytic({
|
||||
required String organizationId,
|
||||
required String outletId,
|
||||
required String outletName,
|
||||
required String dateFrom,
|
||||
required String dateTo,
|
||||
required String groupBy,
|
||||
required ProfitLossSummary summary,
|
||||
required List<ProfitLossDailyData> data,
|
||||
required List<ProfitLossProductData> productData,
|
||||
required List<ProfitLossMainSummaryItem> mainSummary,
|
||||
required ProfitLossPurchasing purchasing,
|
||||
required List<ProfitLossOperationalExpense> operationalExpenses,
|
||||
required int operationalExpensesTotal,
|
||||
}) = _ProfitLossAnalytic;
|
||||
|
||||
factory ProfitLossAnalytic.empty() => ProfitLossAnalytic(
|
||||
organizationId: '',
|
||||
outletId: '',
|
||||
outletName: '',
|
||||
dateFrom: '',
|
||||
dateTo: '',
|
||||
groupBy: '',
|
||||
summary: ProfitLossSummary.empty(),
|
||||
data: [],
|
||||
productData: [],
|
||||
mainSummary: [],
|
||||
purchasing: ProfitLossPurchasing.empty(),
|
||||
operationalExpenses: [],
|
||||
operationalExpensesTotal: 0,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -115,3 +127,79 @@ class ProfitLossProductData with _$ProfitLossProductData {
|
||||
profitPerUnit: 0,
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitLossMainSummaryItem with _$ProfitLossMainSummaryItem {
|
||||
const factory ProfitLossMainSummaryItem({
|
||||
required String id,
|
||||
required String label,
|
||||
required bool isBold,
|
||||
required int todayNominal,
|
||||
required double todayPct,
|
||||
required int mtdNominal,
|
||||
required double mtdPct,
|
||||
@Default([]) List<ProfitLossMainSummaryItem> subItems,
|
||||
}) = _ProfitLossMainSummaryItem;
|
||||
|
||||
factory ProfitLossMainSummaryItem.empty() => const ProfitLossMainSummaryItem(
|
||||
id: '',
|
||||
label: '',
|
||||
isBold: false,
|
||||
todayNominal: 0,
|
||||
todayPct: 0,
|
||||
mtdNominal: 0,
|
||||
mtdPct: 0,
|
||||
subItems: [],
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitLossPurchasing with _$ProfitLossPurchasing {
|
||||
const factory ProfitLossPurchasing({
|
||||
required int todayTotal,
|
||||
required int mtdTotal,
|
||||
required int todayRawMaterial,
|
||||
required int mtdRawMaterial,
|
||||
required int todayExpense,
|
||||
required int mtdExpense,
|
||||
@Default([]) List<ProfitLossPurchasingItem> items,
|
||||
}) = _ProfitLossPurchasing;
|
||||
|
||||
factory ProfitLossPurchasing.empty() => const ProfitLossPurchasing(
|
||||
todayTotal: 0,
|
||||
mtdTotal: 0,
|
||||
todayRawMaterial: 0,
|
||||
mtdRawMaterial: 0,
|
||||
todayExpense: 0,
|
||||
mtdExpense: 0,
|
||||
items: [],
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitLossPurchasingItem with _$ProfitLossPurchasingItem {
|
||||
const factory ProfitLossPurchasingItem({
|
||||
required String date,
|
||||
required String item,
|
||||
required int quantity,
|
||||
required int nominal,
|
||||
}) = _ProfitLossPurchasingItem;
|
||||
|
||||
factory ProfitLossPurchasingItem.empty() => const ProfitLossPurchasingItem(
|
||||
date: '',
|
||||
item: '',
|
||||
quantity: 0,
|
||||
nominal: 0,
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitLossOperationalExpense with _$ProfitLossOperationalExpense {
|
||||
const factory ProfitLossOperationalExpense({
|
||||
required String item,
|
||||
required int nominal,
|
||||
}) = _ProfitLossOperationalExpense;
|
||||
|
||||
factory ProfitLossOperationalExpense.empty() =>
|
||||
const ProfitLossOperationalExpense(item: '', nominal: 0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
part of '../analytic.dart';
|
||||
|
||||
/// Bagi hasil (profit sharing) berdasarkan omzet per parent category.
|
||||
@freezed
|
||||
class ProfitSharing with _$ProfitSharing {
|
||||
const factory ProfitSharing({
|
||||
required String organizationId,
|
||||
required String outletId,
|
||||
required String outletName,
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
required List<ProfitSharingCategory> categories,
|
||||
required ProfitSharingBudget budget,
|
||||
}) = _ProfitSharing;
|
||||
|
||||
factory ProfitSharing.empty() => ProfitSharing(
|
||||
organizationId: '',
|
||||
outletId: '',
|
||||
outletName: '',
|
||||
dateFrom: DateTime.fromMillisecondsSinceEpoch(0),
|
||||
dateTo: DateTime.fromMillisecondsSinceEpoch(0),
|
||||
categories: [],
|
||||
budget: ProfitSharingBudget.empty(),
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitSharingCategory with _$ProfitSharingCategory {
|
||||
const ProfitSharingCategory._();
|
||||
|
||||
const factory ProfitSharingCategory({
|
||||
required String parentCategoryId,
|
||||
required String parentCategoryName,
|
||||
required int totalRevenue,
|
||||
required int totalQuantity,
|
||||
required int categoryCount,
|
||||
required int productCount,
|
||||
required int orderCount,
|
||||
required int totalStandardHpp,
|
||||
required int totalFifoHpp,
|
||||
required int totalMovingAverageHpp,
|
||||
}) = _ProfitSharingCategory;
|
||||
|
||||
factory ProfitSharingCategory.empty() => const ProfitSharingCategory(
|
||||
parentCategoryId: '',
|
||||
parentCategoryName: '',
|
||||
totalRevenue: 0,
|
||||
totalQuantity: 0,
|
||||
categoryCount: 0,
|
||||
productCount: 0,
|
||||
orderCount: 0,
|
||||
totalStandardHpp: 0,
|
||||
totalFifoHpp: 0,
|
||||
totalMovingAverageHpp: 0,
|
||||
);
|
||||
|
||||
/// Laba kotor memakai HPP standar (acuan yang dipakai di laporan laba rugi).
|
||||
int get grossProfit => totalRevenue - totalStandardHpp;
|
||||
|
||||
double get grossMargin =>
|
||||
totalRevenue == 0 ? 0 : (grossProfit / totalRevenue) * 100;
|
||||
|
||||
/// % HPP standar terhadap omzet.
|
||||
double get standardHppPercentage =>
|
||||
totalRevenue == 0 ? 0 : (totalStandardHpp / totalRevenue) * 100;
|
||||
|
||||
/// % HPP riil (FIFO) terhadap omzet — dipakai untuk status kesehatan margin.
|
||||
double get realHppPercentage =>
|
||||
totalRevenue == 0 ? 0 : (totalFifoHpp / totalRevenue) * 100;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitSharingBudget with _$ProfitSharingBudget {
|
||||
const factory ProfitSharingBudget({
|
||||
required ProfitSharingPercentage percentages,
|
||||
required DateTime cutOffFrom,
|
||||
required DateTime cutOffTo,
|
||||
required ProfitSharingPeriod total,
|
||||
required List<ProfitSharingPeriod> weekly,
|
||||
required List<ProfitSharingPeriod> monthly,
|
||||
}) = _ProfitSharingBudget;
|
||||
|
||||
factory ProfitSharingBudget.empty() => ProfitSharingBudget(
|
||||
percentages: ProfitSharingPercentage.empty(),
|
||||
cutOffFrom: DateTime.fromMillisecondsSinceEpoch(0),
|
||||
cutOffTo: DateTime.fromMillisecondsSinceEpoch(0),
|
||||
total: ProfitSharingPeriod.empty(),
|
||||
weekly: [],
|
||||
monthly: [],
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitSharingPercentage with _$ProfitSharingPercentage {
|
||||
const factory ProfitSharingPercentage({
|
||||
required double purchase,
|
||||
required double owner,
|
||||
required double team,
|
||||
}) = _ProfitSharingPercentage;
|
||||
|
||||
factory ProfitSharingPercentage.empty() =>
|
||||
const ProfitSharingPercentage(purchase: 0, owner: 0, team: 0);
|
||||
}
|
||||
|
||||
/// Satu baris periode bagi hasil. Dipakai untuk total, mingguan, dan bulanan.
|
||||
/// [month] dan [weekCount] hanya terisi untuk data bulanan.
|
||||
@freezed
|
||||
class ProfitSharingPeriod with _$ProfitSharingPeriod {
|
||||
const ProfitSharingPeriod._();
|
||||
|
||||
const factory ProfitSharingPeriod({
|
||||
required DateTime periodStart,
|
||||
required DateTime periodEnd,
|
||||
required int revenue,
|
||||
required int orderCount,
|
||||
required int limitPurchase,
|
||||
required int limitOwner,
|
||||
required int limitTeam,
|
||||
@Default('') String month,
|
||||
@Default(0) int weekCount,
|
||||
}) = _ProfitSharingPeriod;
|
||||
|
||||
factory ProfitSharingPeriod.empty() => ProfitSharingPeriod(
|
||||
periodStart: DateTime.fromMillisecondsSinceEpoch(0),
|
||||
periodEnd: DateTime.fromMillisecondsSinceEpoch(0),
|
||||
revenue: 0,
|
||||
orderCount: 0,
|
||||
limitPurchase: 0,
|
||||
limitOwner: 0,
|
||||
limitTeam: 0,
|
||||
);
|
||||
|
||||
bool get hasRevenue => revenue > 0;
|
||||
|
||||
int get averageOrderValue => orderCount == 0 ? 0 : revenue ~/ orderCount;
|
||||
}
|
||||
|
||||
/// Rincian satu kategori induk: ringkasan, sub kategori beserta produknya,
|
||||
/// dan bagi hasil khusus kategori tersebut.
|
||||
@freezed
|
||||
class ProfitSharingDetail with _$ProfitSharingDetail {
|
||||
const factory ProfitSharingDetail({
|
||||
required String organizationId,
|
||||
required String outletId,
|
||||
required String outletName,
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
required String parentCategoryId,
|
||||
required String parentCategoryName,
|
||||
required ProfitSharingCategory summary,
|
||||
required List<ProfitSharingSubCategory> categories,
|
||||
required ProfitSharingBudget budget,
|
||||
}) = _ProfitSharingDetail;
|
||||
|
||||
factory ProfitSharingDetail.empty() => ProfitSharingDetail(
|
||||
organizationId: '',
|
||||
outletId: '',
|
||||
outletName: '',
|
||||
dateFrom: DateTime.fromMillisecondsSinceEpoch(0),
|
||||
dateTo: DateTime.fromMillisecondsSinceEpoch(0),
|
||||
parentCategoryId: '',
|
||||
parentCategoryName: '',
|
||||
summary: ProfitSharingCategory.empty(),
|
||||
categories: [],
|
||||
budget: ProfitSharingBudget.empty(),
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitSharingSubCategory with _$ProfitSharingSubCategory {
|
||||
const ProfitSharingSubCategory._();
|
||||
|
||||
const factory ProfitSharingSubCategory({
|
||||
required String categoryId,
|
||||
required String categoryName,
|
||||
required int totalRevenue,
|
||||
required int totalQuantity,
|
||||
required int productCount,
|
||||
required int orderCount,
|
||||
required int totalStandardHpp,
|
||||
required int totalFifoHpp,
|
||||
required int totalMovingAverageHpp,
|
||||
required List<ProfitSharingProduct> products,
|
||||
}) = _ProfitSharingSubCategory;
|
||||
|
||||
double get standardHppPercentage =>
|
||||
totalRevenue == 0 ? 0 : (totalStandardHpp / totalRevenue) * 100;
|
||||
|
||||
double get realHppPercentage =>
|
||||
totalRevenue == 0 ? 0 : (totalFifoHpp / totalRevenue) * 100;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitSharingProduct with _$ProfitSharingProduct {
|
||||
const ProfitSharingProduct._();
|
||||
|
||||
const factory ProfitSharingProduct({
|
||||
required String productId,
|
||||
required String productName,
|
||||
required String productSku,
|
||||
required int productPrice,
|
||||
required int quantitySold,
|
||||
required int revenue,
|
||||
required double averagePrice,
|
||||
required int orderCount,
|
||||
required double standardHppPerUnit,
|
||||
required int standardHppTotal,
|
||||
required double fifoHppPerUnit,
|
||||
required int fifoHppTotal,
|
||||
required double movingAverageHppPerUnit,
|
||||
required int movingAverageHppTotal,
|
||||
}) = _ProfitSharingProduct;
|
||||
|
||||
double get standardHppPercentage =>
|
||||
revenue == 0 ? 0 : (standardHppTotal / revenue) * 100;
|
||||
|
||||
double get realHppPercentage =>
|
||||
revenue == 0 ? 0 : (fifoHppTotal / revenue) * 100;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ class SalesAnalytic with _$SalesAnalytic {
|
||||
const factory SalesAnalytic({
|
||||
required String organizationId,
|
||||
required String outletId,
|
||||
required String outletName,
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
required String groupBy,
|
||||
@@ -15,6 +16,7 @@ class SalesAnalytic with _$SalesAnalytic {
|
||||
factory SalesAnalytic.empty() => SalesAnalytic(
|
||||
organizationId: '',
|
||||
outletId: '',
|
||||
outletName: '',
|
||||
dateFrom: DateTime.fromMillisecondsSinceEpoch(0),
|
||||
dateTo: DateTime.fromMillisecondsSinceEpoch(0),
|
||||
groupBy: '',
|
||||
|
||||
@@ -57,4 +57,18 @@ abstract class IAnalyticRepository {
|
||||
required DateTime dateTo,
|
||||
String? outletId,
|
||||
});
|
||||
|
||||
Future<Either<AnalyticFailure, ProfitSharing>> getProfitSharing({
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
String? outletId,
|
||||
String groupBy = 'day',
|
||||
});
|
||||
|
||||
Future<Either<AnalyticFailure, ProfitSharingDetail>> getProfitSharingDetail({
|
||||
required String parentCategoryId,
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
String? outletId,
|
||||
});
|
||||
}
|
||||
|
||||
+2
-2
@@ -9,12 +9,12 @@ abstract class Env {
|
||||
@dev
|
||||
class DevEnv implements Env {
|
||||
@override
|
||||
String get baseUrl => 'https://api-pos.apskel.id'; // example value
|
||||
String get baseUrl => 'https://enaklo-pos-api.altru.id'; // example value
|
||||
}
|
||||
|
||||
@Injectable(as: Env)
|
||||
@prod
|
||||
class ProdEnv implements Env {
|
||||
@override
|
||||
String get baseUrl => 'https://api-pos.apskel.id';
|
||||
String get baseUrl => 'https://enaklo-pos-api.altru.id';
|
||||
}
|
||||
|
||||
@@ -14,3 +14,4 @@ part 'dto/product_analytic_dto.dart';
|
||||
part 'dto/payment_method_analytic_dto.dart';
|
||||
part 'dto/purchasing_analytic_dto.dart';
|
||||
part 'dto/exclusive_summary_dto.dart';
|
||||
part 'dto/profit_sharing_dto.dart';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ _$SalesAnalyticDtoImpl _$$SalesAnalyticDtoImplFromJson(
|
||||
) => _$SalesAnalyticDtoImpl(
|
||||
organizationId: json['organization_id'] as String?,
|
||||
outletId: json['outlet_id'] as String?,
|
||||
outletName: json['outlet_name'] as String?,
|
||||
dateFrom: json['date_from'] == null
|
||||
? null
|
||||
: DateTime.parse(json['date_from'] as String),
|
||||
@@ -33,6 +34,7 @@ Map<String, dynamic> _$$SalesAnalyticDtoImplToJson(
|
||||
) => <String, dynamic>{
|
||||
'organization_id': instance.organizationId,
|
||||
'outlet_id': instance.outletId,
|
||||
'outlet_name': instance.outletName,
|
||||
'date_from': instance.dateFrom?.toIso8601String(),
|
||||
'date_to': instance.dateTo?.toIso8601String(),
|
||||
'group_by': instance.groupBy,
|
||||
@@ -92,6 +94,8 @@ _$ProfitLossAnalyticDtoImpl _$$ProfitLossAnalyticDtoImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ProfitLossAnalyticDtoImpl(
|
||||
organizationId: json['organization_id'] as String?,
|
||||
outletId: json['outlet_id'] as String?,
|
||||
outletName: json['outlet_name'] as String?,
|
||||
dateFrom: json['date_from'] as String?,
|
||||
dateTo: json['date_to'] as String?,
|
||||
groupBy: json['group_by'] as String?,
|
||||
@@ -104,18 +108,42 @@ _$ProfitLossAnalyticDtoImpl _$$ProfitLossAnalyticDtoImplFromJson(
|
||||
productData: (json['product_data'] as List<dynamic>?)
|
||||
?.map((e) => ProfitLossProductDataDto.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
mainSummary: (json['main_summary'] as List<dynamic>?)
|
||||
?.map(
|
||||
(e) => ProfitLossMainSummaryItemDto.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList(),
|
||||
purchasing: json['purchasing'] == null
|
||||
? null
|
||||
: ProfitLossPurchasingDto.fromJson(
|
||||
json['purchasing'] as Map<String, dynamic>,
|
||||
),
|
||||
operationalExpenses: (json['operational_expenses'] as List<dynamic>?)
|
||||
?.map(
|
||||
(e) =>
|
||||
ProfitLossOperationalExpenseDto.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList(),
|
||||
operationalExpensesTotal: (json['operational_expenses_total'] as num?)
|
||||
?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfitLossAnalyticDtoImplToJson(
|
||||
_$ProfitLossAnalyticDtoImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'organization_id': instance.organizationId,
|
||||
'outlet_id': instance.outletId,
|
||||
'outlet_name': instance.outletName,
|
||||
'date_from': instance.dateFrom,
|
||||
'date_to': instance.dateTo,
|
||||
'group_by': instance.groupBy,
|
||||
'summary': instance.summary,
|
||||
'data': instance.data,
|
||||
'product_data': instance.productData,
|
||||
'main_summary': instance.mainSummary,
|
||||
'purchasing': instance.purchasing,
|
||||
'operational_expenses': instance.operationalExpenses,
|
||||
'operational_expenses_total': instance.operationalExpensesTotal,
|
||||
};
|
||||
|
||||
_$ProfitLossSummaryDtoImpl _$$ProfitLossSummaryDtoImplFromJson(
|
||||
@@ -214,11 +242,99 @@ Map<String, dynamic> _$$ProfitLossProductDataDtoImplToJson(
|
||||
'profit_per_unit': instance.profitPerUnit,
|
||||
};
|
||||
|
||||
_$ProfitLossMainSummaryItemDtoImpl _$$ProfitLossMainSummaryItemDtoImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ProfitLossMainSummaryItemDtoImpl(
|
||||
id: json['id'] as String?,
|
||||
label: json['label'] as String?,
|
||||
isBold: json['is_bold'] as bool?,
|
||||
todayNominal: (json['today_nominal'] as num?)?.toInt(),
|
||||
todayPct: (json['today_pct'] as num?)?.toDouble(),
|
||||
mtdNominal: (json['mtd_nominal'] as num?)?.toInt(),
|
||||
mtdPct: (json['mtd_pct'] as num?)?.toDouble(),
|
||||
subItems: (json['sub_items'] as List<dynamic>?)
|
||||
?.map(
|
||||
(e) => ProfitLossMainSummaryItemDto.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfitLossMainSummaryItemDtoImplToJson(
|
||||
_$ProfitLossMainSummaryItemDtoImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'label': instance.label,
|
||||
'is_bold': instance.isBold,
|
||||
'today_nominal': instance.todayNominal,
|
||||
'today_pct': instance.todayPct,
|
||||
'mtd_nominal': instance.mtdNominal,
|
||||
'mtd_pct': instance.mtdPct,
|
||||
'sub_items': instance.subItems,
|
||||
};
|
||||
|
||||
_$ProfitLossPurchasingDtoImpl _$$ProfitLossPurchasingDtoImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ProfitLossPurchasingDtoImpl(
|
||||
todayTotal: (json['today_total'] as num?)?.toInt(),
|
||||
mtdTotal: (json['mtd_total'] as num?)?.toInt(),
|
||||
todayRawMaterial: (json['today_raw_material'] as num?)?.toInt(),
|
||||
mtdRawMaterial: (json['mtd_raw_material'] as num?)?.toInt(),
|
||||
todayExpense: (json['today_expense'] as num?)?.toInt(),
|
||||
mtdExpense: (json['mtd_expense'] as num?)?.toInt(),
|
||||
items: (json['items'] as List<dynamic>?)
|
||||
?.map(
|
||||
(e) => ProfitLossPurchasingItemDto.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfitLossPurchasingDtoImplToJson(
|
||||
_$ProfitLossPurchasingDtoImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'today_total': instance.todayTotal,
|
||||
'mtd_total': instance.mtdTotal,
|
||||
'today_raw_material': instance.todayRawMaterial,
|
||||
'mtd_raw_material': instance.mtdRawMaterial,
|
||||
'today_expense': instance.todayExpense,
|
||||
'mtd_expense': instance.mtdExpense,
|
||||
'items': instance.items,
|
||||
};
|
||||
|
||||
_$ProfitLossPurchasingItemDtoImpl _$$ProfitLossPurchasingItemDtoImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ProfitLossPurchasingItemDtoImpl(
|
||||
date: json['date'] as String?,
|
||||
item: json['item'] as String?,
|
||||
quantity: (json['quantity'] as num?)?.toInt(),
|
||||
nominal: (json['nominal'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfitLossPurchasingItemDtoImplToJson(
|
||||
_$ProfitLossPurchasingItemDtoImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'date': instance.date,
|
||||
'item': instance.item,
|
||||
'quantity': instance.quantity,
|
||||
'nominal': instance.nominal,
|
||||
};
|
||||
|
||||
_$ProfitLossOperationalExpenseDtoImpl
|
||||
_$$ProfitLossOperationalExpenseDtoImplFromJson(Map<String, dynamic> json) =>
|
||||
_$ProfitLossOperationalExpenseDtoImpl(
|
||||
item: json['item'] as String?,
|
||||
nominal: (json['nominal'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfitLossOperationalExpenseDtoImplToJson(
|
||||
_$ProfitLossOperationalExpenseDtoImpl instance,
|
||||
) => <String, dynamic>{'item': instance.item, 'nominal': instance.nominal};
|
||||
|
||||
_$CategoryAnalyticDtoImpl _$$CategoryAnalyticDtoImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$CategoryAnalyticDtoImpl(
|
||||
organizationId: json['organization_id'] as String?,
|
||||
outletId: json['outlet_id'] as String?,
|
||||
outletName: json['outlet_name'] as String?,
|
||||
dateFrom: json['date_from'] as String?,
|
||||
dateTo: json['date_to'] as String?,
|
||||
data: (json['data'] as List<dynamic>?)
|
||||
@@ -231,6 +347,7 @@ Map<String, dynamic> _$$CategoryAnalyticDtoImplToJson(
|
||||
) => <String, dynamic>{
|
||||
'organization_id': instance.organizationId,
|
||||
'outlet_id': instance.outletId,
|
||||
'outlet_name': instance.outletName,
|
||||
'date_from': instance.dateFrom,
|
||||
'date_to': instance.dateTo,
|
||||
'data': instance.data,
|
||||
@@ -391,6 +508,7 @@ _$DashboardAnalyticDtoImpl _$$DashboardAnalyticDtoImplFromJson(
|
||||
) => _$DashboardAnalyticDtoImpl(
|
||||
organizationId: json['organization_id'] as String?,
|
||||
outletId: json['outlet_id'] as String?,
|
||||
outletName: json['outlet_name'] as String?,
|
||||
dateFrom: json['date_from'] as String?,
|
||||
dateTo: json['date_to'] as String?,
|
||||
overview: json['overview'] == null
|
||||
@@ -414,6 +532,7 @@ Map<String, dynamic> _$$DashboardAnalyticDtoImplToJson(
|
||||
) => <String, dynamic>{
|
||||
'organization_id': instance.organizationId,
|
||||
'outlet_id': instance.outletId,
|
||||
'outlet_name': instance.outletName,
|
||||
'date_from': instance.dateFrom,
|
||||
'date_to': instance.dateTo,
|
||||
'overview': instance.overview,
|
||||
@@ -431,6 +550,9 @@ _$DashboardOverviewDtoImpl _$$DashboardOverviewDtoImplFromJson(
|
||||
totalCustomers: (json['total_customers'] as num?)?.toInt(),
|
||||
voidedOrders: (json['voided_orders'] as num?)?.toInt(),
|
||||
refundedOrders: (json['refunded_orders'] as num?)?.toInt(),
|
||||
totalItemSold: (json['total_item_sold'] as num?)?.toInt(),
|
||||
totalLowStock: (json['total_low_stock'] as num?)?.toInt(),
|
||||
totalProductActive: (json['total_product_active'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$DashboardOverviewDtoImplToJson(
|
||||
@@ -442,6 +564,9 @@ Map<String, dynamic> _$$DashboardOverviewDtoImplToJson(
|
||||
'total_customers': instance.totalCustomers,
|
||||
'voided_orders': instance.voidedOrders,
|
||||
'refunded_orders': instance.refundedOrders,
|
||||
'total_item_sold': instance.totalItemSold,
|
||||
'total_low_stock': instance.totalLowStock,
|
||||
'total_product_active': instance.totalProductActive,
|
||||
};
|
||||
|
||||
_$DashboardTopProductDtoImpl _$$DashboardTopProductDtoImplFromJson(
|
||||
@@ -523,6 +648,7 @@ _$ProductAnalyticDtoImpl _$$ProductAnalyticDtoImplFromJson(
|
||||
) => _$ProductAnalyticDtoImpl(
|
||||
organizationId: json['organization_id'] as String?,
|
||||
outletId: json['outlet_id'] as String?,
|
||||
outletName: json['outlet_name'] as String?,
|
||||
dateFrom: json['date_from'] as String?,
|
||||
dateTo: json['date_to'] as String?,
|
||||
data: (json['data'] as List<dynamic>?)
|
||||
@@ -535,6 +661,7 @@ Map<String, dynamic> _$$ProductAnalyticDtoImplToJson(
|
||||
) => <String, dynamic>{
|
||||
'organization_id': instance.organizationId,
|
||||
'outlet_id': instance.outletId,
|
||||
'outlet_name': instance.outletName,
|
||||
'date_from': instance.dateFrom,
|
||||
'date_to': instance.dateTo,
|
||||
'data': instance.data,
|
||||
@@ -545,12 +672,22 @@ _$ProductAnalyticDataDtoImpl _$$ProductAnalyticDataDtoImplFromJson(
|
||||
) => _$ProductAnalyticDataDtoImpl(
|
||||
productId: json['product_id'] as String?,
|
||||
productName: json['product_name'] as String?,
|
||||
productSku: json['product_sku'] as String?,
|
||||
productPrice: (json['product_price'] as num?)?.toInt(),
|
||||
categoryId: json['category_id'] as String?,
|
||||
categoryName: json['category_name'] as String?,
|
||||
categoryOrder: (json['category_order'] as num?)?.toInt(),
|
||||
quantitySold: (json['quantity_sold'] as num?)?.toInt(),
|
||||
revenue: (json['revenue'] as num?)?.toInt(),
|
||||
averagePrice: (json['average_price'] as num?)?.toDouble(),
|
||||
orderCount: (json['order_count'] as num?)?.toInt(),
|
||||
standardHppPerUnit: (json['standard_hpp_per_unit'] as num?)?.toInt(),
|
||||
standardHppTotal: (json['standard_hpp_total'] as num?)?.toInt(),
|
||||
fifoHppPerUnit: (json['fifo_hpp_per_unit'] as num?)?.toInt(),
|
||||
fifoHppTotal: (json['fifo_hpp_total'] as num?)?.toInt(),
|
||||
movingAverageHppPerUnit: (json['moving_average_hpp_per_unit'] as num?)
|
||||
?.toInt(),
|
||||
movingAverageHppTotal: (json['moving_average_hpp_total'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProductAnalyticDataDtoImplToJson(
|
||||
@@ -558,12 +695,21 @@ Map<String, dynamic> _$$ProductAnalyticDataDtoImplToJson(
|
||||
) => <String, dynamic>{
|
||||
'product_id': instance.productId,
|
||||
'product_name': instance.productName,
|
||||
'product_sku': instance.productSku,
|
||||
'product_price': instance.productPrice,
|
||||
'category_id': instance.categoryId,
|
||||
'category_name': instance.categoryName,
|
||||
'category_order': instance.categoryOrder,
|
||||
'quantity_sold': instance.quantitySold,
|
||||
'revenue': instance.revenue,
|
||||
'average_price': instance.averagePrice,
|
||||
'order_count': instance.orderCount,
|
||||
'standard_hpp_per_unit': instance.standardHppPerUnit,
|
||||
'standard_hpp_total': instance.standardHppTotal,
|
||||
'fifo_hpp_per_unit': instance.fifoHppPerUnit,
|
||||
'fifo_hpp_total': instance.fifoHppTotal,
|
||||
'moving_average_hpp_per_unit': instance.movingAverageHppPerUnit,
|
||||
'moving_average_hpp_total': instance.movingAverageHppTotal,
|
||||
};
|
||||
|
||||
_$PaymentMethodAnalyticDtoImpl _$$PaymentMethodAnalyticDtoImplFromJson(
|
||||
@@ -571,6 +717,7 @@ _$PaymentMethodAnalyticDtoImpl _$$PaymentMethodAnalyticDtoImplFromJson(
|
||||
) => _$PaymentMethodAnalyticDtoImpl(
|
||||
organizationId: json['organization_id'] as String?,
|
||||
outletId: json['outlet_id'] as String?,
|
||||
outletName: json['outlet_name'] as String?,
|
||||
dateFrom: json['date_from'] as String?,
|
||||
dateTo: json['date_to'] as String?,
|
||||
groupBy: json['group_by'] as String?,
|
||||
@@ -589,6 +736,7 @@ Map<String, dynamic> _$$PaymentMethodAnalyticDtoImplToJson(
|
||||
) => <String, dynamic>{
|
||||
'organization_id': instance.organizationId,
|
||||
'outlet_id': instance.outletId,
|
||||
'outlet_name': instance.outletName,
|
||||
'date_from': instance.dateFrom,
|
||||
'date_to': instance.dateTo,
|
||||
'group_by': instance.groupBy,
|
||||
@@ -795,6 +943,7 @@ _$ExclusiveSummaryDtoImpl _$$ExclusiveSummaryDtoImplFromJson(
|
||||
) => _$ExclusiveSummaryDtoImpl(
|
||||
organizationId: json['organization_id'] as String?,
|
||||
outletId: json['outlet_id'] as String?,
|
||||
outletName: json['outlet_name'] as String?,
|
||||
period: json['period'] == null
|
||||
? null
|
||||
: ExclusiveSummaryPeriodDto.fromJson(
|
||||
@@ -839,6 +988,7 @@ Map<String, dynamic> _$$ExclusiveSummaryDtoImplToJson(
|
||||
) => <String, dynamic>{
|
||||
'organization_id': instance.organizationId,
|
||||
'outlet_id': instance.outletId,
|
||||
'outlet_name': instance.outletName,
|
||||
'period': instance.period,
|
||||
'summary': instance.summary,
|
||||
'reimburse': instance.reimburse,
|
||||
@@ -971,3 +1121,289 @@ Map<String, dynamic> _$$ExclusiveSummaryTransactionDtoImplToJson(
|
||||
'amount': instance.amount,
|
||||
'source': instance.source,
|
||||
};
|
||||
|
||||
_$ProfitSharingDtoImpl _$$ProfitSharingDtoImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ProfitSharingDtoImpl(
|
||||
organizationId: json['organization_id'] as String?,
|
||||
outletId: json['outlet_id'] as String?,
|
||||
outletName: json['outlet_name'] as String?,
|
||||
dateFrom: json['date_from'] == null
|
||||
? null
|
||||
: DateTime.parse(json['date_from'] as String),
|
||||
dateTo: json['date_to'] == null
|
||||
? null
|
||||
: DateTime.parse(json['date_to'] as String),
|
||||
data: (json['data'] as List<dynamic>?)
|
||||
?.map((e) => ProfitSharingCategoryDto.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
budget: json['budget'] == null
|
||||
? null
|
||||
: ProfitSharingBudgetDto.fromJson(json['budget'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfitSharingDtoImplToJson(
|
||||
_$ProfitSharingDtoImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'organization_id': instance.organizationId,
|
||||
'outlet_id': instance.outletId,
|
||||
'outlet_name': instance.outletName,
|
||||
'date_from': instance.dateFrom?.toIso8601String(),
|
||||
'date_to': instance.dateTo?.toIso8601String(),
|
||||
'data': instance.data,
|
||||
'budget': instance.budget,
|
||||
};
|
||||
|
||||
_$ProfitSharingCategoryDtoImpl _$$ProfitSharingCategoryDtoImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ProfitSharingCategoryDtoImpl(
|
||||
parentCategoryId: json['parent_category_id'] as String?,
|
||||
parentCategoryName: json['parent_category_name'] as String?,
|
||||
totalRevenue: json['total_revenue'] as num?,
|
||||
totalQuantity: json['total_quantity'] as num?,
|
||||
categoryCount: json['category_count'] as num?,
|
||||
productCount: json['product_count'] as num?,
|
||||
orderCount: json['order_count'] as num?,
|
||||
totalStandardHpp: json['total_standard_hpp'] as num?,
|
||||
totalFifoHpp: json['total_fifo_hpp'] as num?,
|
||||
totalMovingAverageHpp: json['total_moving_average_hpp'] as num?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfitSharingCategoryDtoImplToJson(
|
||||
_$ProfitSharingCategoryDtoImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'parent_category_id': instance.parentCategoryId,
|
||||
'parent_category_name': instance.parentCategoryName,
|
||||
'total_revenue': instance.totalRevenue,
|
||||
'total_quantity': instance.totalQuantity,
|
||||
'category_count': instance.categoryCount,
|
||||
'product_count': instance.productCount,
|
||||
'order_count': instance.orderCount,
|
||||
'total_standard_hpp': instance.totalStandardHpp,
|
||||
'total_fifo_hpp': instance.totalFifoHpp,
|
||||
'total_moving_average_hpp': instance.totalMovingAverageHpp,
|
||||
};
|
||||
|
||||
_$ProfitSharingBudgetDtoImpl _$$ProfitSharingBudgetDtoImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ProfitSharingBudgetDtoImpl(
|
||||
percentages: json['percentages'] == null
|
||||
? null
|
||||
: ProfitSharingPercentageDto.fromJson(
|
||||
json['percentages'] as Map<String, dynamic>,
|
||||
),
|
||||
cutOffFrom: json['cut_off_from'] == null
|
||||
? null
|
||||
: DateTime.parse(json['cut_off_from'] as String),
|
||||
cutOffTo: json['cut_off_to'] == null
|
||||
? null
|
||||
: DateTime.parse(json['cut_off_to'] as String),
|
||||
total: json['total'] == null
|
||||
? null
|
||||
: ProfitSharingPeriodDto.fromJson(json['total'] as Map<String, dynamic>),
|
||||
weekly: (json['weekly'] as List<dynamic>?)
|
||||
?.map((e) => ProfitSharingPeriodDto.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
monthly: (json['monthly'] as List<dynamic>?)
|
||||
?.map((e) => ProfitSharingPeriodDto.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfitSharingBudgetDtoImplToJson(
|
||||
_$ProfitSharingBudgetDtoImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'percentages': instance.percentages,
|
||||
'cut_off_from': instance.cutOffFrom?.toIso8601String(),
|
||||
'cut_off_to': instance.cutOffTo?.toIso8601String(),
|
||||
'total': instance.total,
|
||||
'weekly': instance.weekly,
|
||||
'monthly': instance.monthly,
|
||||
};
|
||||
|
||||
_$ProfitSharingPercentageDtoImpl _$$ProfitSharingPercentageDtoImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ProfitSharingPercentageDtoImpl(
|
||||
purchase: json['purchase'] as num?,
|
||||
owner: json['owner'] as num?,
|
||||
team: json['team'] as num?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfitSharingPercentageDtoImplToJson(
|
||||
_$ProfitSharingPercentageDtoImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'purchase': instance.purchase,
|
||||
'owner': instance.owner,
|
||||
'team': instance.team,
|
||||
};
|
||||
|
||||
_$ProfitSharingPeriodDtoImpl _$$ProfitSharingPeriodDtoImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ProfitSharingPeriodDtoImpl(
|
||||
periodStart: json['period_start'] == null
|
||||
? null
|
||||
: DateTime.parse(json['period_start'] as String),
|
||||
periodEnd: json['period_end'] == null
|
||||
? null
|
||||
: DateTime.parse(json['period_end'] as String),
|
||||
revenue: json['revenue'] as num?,
|
||||
orderCount: json['order_count'] as num?,
|
||||
limitPurchase: json['limit_purchase'] as num?,
|
||||
limitOwner: json['limit_owner'] as num?,
|
||||
limitTeam: json['limit_team'] as num?,
|
||||
month: json['month'] as String?,
|
||||
weekCount: json['week_count'] as num?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfitSharingPeriodDtoImplToJson(
|
||||
_$ProfitSharingPeriodDtoImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'period_start': instance.periodStart?.toIso8601String(),
|
||||
'period_end': instance.periodEnd?.toIso8601String(),
|
||||
'revenue': instance.revenue,
|
||||
'order_count': instance.orderCount,
|
||||
'limit_purchase': instance.limitPurchase,
|
||||
'limit_owner': instance.limitOwner,
|
||||
'limit_team': instance.limitTeam,
|
||||
'month': instance.month,
|
||||
'week_count': instance.weekCount,
|
||||
};
|
||||
|
||||
_$ProfitSharingDetailDtoImpl _$$ProfitSharingDetailDtoImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ProfitSharingDetailDtoImpl(
|
||||
organizationId: json['organization_id'] as String?,
|
||||
outletId: json['outlet_id'] as String?,
|
||||
outletName: json['outlet_name'] as String?,
|
||||
dateFrom: json['date_from'] == null
|
||||
? null
|
||||
: DateTime.parse(json['date_from'] as String),
|
||||
dateTo: json['date_to'] == null
|
||||
? null
|
||||
: DateTime.parse(json['date_to'] as String),
|
||||
parentCategoryId: json['parent_category_id'] as String?,
|
||||
parentCategoryName: json['parent_category_name'] as String?,
|
||||
summary: json['summary'] == null
|
||||
? null
|
||||
: ProfitSharingSummaryDto.fromJson(
|
||||
json['summary'] as Map<String, dynamic>,
|
||||
),
|
||||
categories: (json['categories'] as List<dynamic>?)
|
||||
?.map(
|
||||
(e) => ProfitSharingSubCategoryDto.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList(),
|
||||
budget: json['budget'] == null
|
||||
? null
|
||||
: ProfitSharingBudgetDto.fromJson(json['budget'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfitSharingDetailDtoImplToJson(
|
||||
_$ProfitSharingDetailDtoImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'organization_id': instance.organizationId,
|
||||
'outlet_id': instance.outletId,
|
||||
'outlet_name': instance.outletName,
|
||||
'date_from': instance.dateFrom?.toIso8601String(),
|
||||
'date_to': instance.dateTo?.toIso8601String(),
|
||||
'parent_category_id': instance.parentCategoryId,
|
||||
'parent_category_name': instance.parentCategoryName,
|
||||
'summary': instance.summary,
|
||||
'categories': instance.categories,
|
||||
'budget': instance.budget,
|
||||
};
|
||||
|
||||
_$ProfitSharingSummaryDtoImpl _$$ProfitSharingSummaryDtoImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ProfitSharingSummaryDtoImpl(
|
||||
totalRevenue: json['total_revenue'] as num?,
|
||||
totalQuantity: json['total_quantity'] as num?,
|
||||
categoryCount: json['category_count'] as num?,
|
||||
productCount: json['product_count'] as num?,
|
||||
orderCount: json['order_count'] as num?,
|
||||
totalStandardHpp: json['total_standard_hpp'] as num?,
|
||||
totalFifoHpp: json['total_fifo_hpp'] as num?,
|
||||
totalMovingAverageHpp: json['total_moving_average_hpp'] as num?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfitSharingSummaryDtoImplToJson(
|
||||
_$ProfitSharingSummaryDtoImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'total_revenue': instance.totalRevenue,
|
||||
'total_quantity': instance.totalQuantity,
|
||||
'category_count': instance.categoryCount,
|
||||
'product_count': instance.productCount,
|
||||
'order_count': instance.orderCount,
|
||||
'total_standard_hpp': instance.totalStandardHpp,
|
||||
'total_fifo_hpp': instance.totalFifoHpp,
|
||||
'total_moving_average_hpp': instance.totalMovingAverageHpp,
|
||||
};
|
||||
|
||||
_$ProfitSharingSubCategoryDtoImpl _$$ProfitSharingSubCategoryDtoImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ProfitSharingSubCategoryDtoImpl(
|
||||
categoryId: json['category_id'] as String?,
|
||||
categoryName: json['category_name'] as String?,
|
||||
totalRevenue: json['total_revenue'] as num?,
|
||||
totalQuantity: json['total_quantity'] as num?,
|
||||
productCount: json['product_count'] as num?,
|
||||
orderCount: json['order_count'] as num?,
|
||||
totalStandardHpp: json['total_standard_hpp'] as num?,
|
||||
totalFifoHpp: json['total_fifo_hpp'] as num?,
|
||||
totalMovingAverageHpp: json['total_moving_average_hpp'] as num?,
|
||||
products: (json['products'] as List<dynamic>?)
|
||||
?.map((e) => ProfitSharingProductDto.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfitSharingSubCategoryDtoImplToJson(
|
||||
_$ProfitSharingSubCategoryDtoImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'category_id': instance.categoryId,
|
||||
'category_name': instance.categoryName,
|
||||
'total_revenue': instance.totalRevenue,
|
||||
'total_quantity': instance.totalQuantity,
|
||||
'product_count': instance.productCount,
|
||||
'order_count': instance.orderCount,
|
||||
'total_standard_hpp': instance.totalStandardHpp,
|
||||
'total_fifo_hpp': instance.totalFifoHpp,
|
||||
'total_moving_average_hpp': instance.totalMovingAverageHpp,
|
||||
'products': instance.products,
|
||||
};
|
||||
|
||||
_$ProfitSharingProductDtoImpl _$$ProfitSharingProductDtoImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ProfitSharingProductDtoImpl(
|
||||
productId: json['product_id'] as String?,
|
||||
productName: json['product_name'] as String?,
|
||||
productSku: json['product_sku'] as String?,
|
||||
productPrice: json['product_price'] as num?,
|
||||
quantitySold: json['quantity_sold'] as num?,
|
||||
revenue: json['revenue'] as num?,
|
||||
averagePrice: json['average_price'] as num?,
|
||||
orderCount: json['order_count'] as num?,
|
||||
standardHppPerUnit: json['standard_hpp_per_unit'] as num?,
|
||||
standardHppTotal: json['standard_hpp_total'] as num?,
|
||||
fifoHppPerUnit: json['fifo_hpp_per_unit'] as num?,
|
||||
fifoHppTotal: json['fifo_hpp_total'] as num?,
|
||||
movingAverageHppPerUnit: json['moving_average_hpp_per_unit'] as num?,
|
||||
movingAverageHppTotal: json['moving_average_hpp_total'] as num?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfitSharingProductDtoImplToJson(
|
||||
_$ProfitSharingProductDtoImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'product_id': instance.productId,
|
||||
'product_name': instance.productName,
|
||||
'product_sku': instance.productSku,
|
||||
'product_price': instance.productPrice,
|
||||
'quantity_sold': instance.quantitySold,
|
||||
'revenue': instance.revenue,
|
||||
'average_price': instance.averagePrice,
|
||||
'order_count': instance.orderCount,
|
||||
'standard_hpp_per_unit': instance.standardHppPerUnit,
|
||||
'standard_hpp_total': instance.standardHppTotal,
|
||||
'fifo_hpp_per_unit': instance.fifoHppPerUnit,
|
||||
'fifo_hpp_total': instance.fifoHppTotal,
|
||||
'moving_average_hpp_per_unit': instance.movingAverageHppPerUnit,
|
||||
'moving_average_hpp_total': instance.movingAverageHppTotal,
|
||||
};
|
||||
|
||||
@@ -295,4 +295,74 @@ class AnalyticRemoteDataProvider {
|
||||
return DC.error(AnalyticFailure.serverError(e));
|
||||
}
|
||||
}
|
||||
|
||||
Future<DC<AnalyticFailure, ProfitSharingDto>> fetchProfitSharing({
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
String? outletId,
|
||||
String groupBy = 'day',
|
||||
}) async {
|
||||
try {
|
||||
final Map<String, dynamic> params = {
|
||||
'date_from': dateFrom.toServerDate,
|
||||
'date_to': dateTo.toServerDate,
|
||||
'group_by': groupBy,
|
||||
};
|
||||
if (outletId != null) params['outlet_id'] = outletId;
|
||||
|
||||
final response = await _apiClient.get(
|
||||
ApiPath.parentCategoryAnalytic,
|
||||
params: params,
|
||||
headers: getAuthorizationHeader(),
|
||||
);
|
||||
|
||||
if (response.data['data'] == null) {
|
||||
return DC.error(AnalyticFailure.empty());
|
||||
}
|
||||
|
||||
final dto = ProfitSharingDto.fromJson(response.data['data']);
|
||||
|
||||
return DC.data(dto);
|
||||
} on ApiFailure catch (e, s) {
|
||||
log('fetchProfitSharingError', name: _logName, error: e, stackTrace: s);
|
||||
return DC.error(AnalyticFailure.serverError(e));
|
||||
}
|
||||
}
|
||||
|
||||
Future<DC<AnalyticFailure, ProfitSharingDetailDto>> fetchProfitSharingDetail({
|
||||
required String parentCategoryId,
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
String? outletId,
|
||||
}) async {
|
||||
try {
|
||||
final Map<String, dynamic> params = {
|
||||
'date_from': dateFrom.toServerDate,
|
||||
'date_to': dateTo.toServerDate,
|
||||
};
|
||||
if (outletId != null) params['outlet_id'] = outletId;
|
||||
|
||||
final response = await _apiClient.get(
|
||||
'${ApiPath.parentCategoryAnalytic}/$parentCategoryId',
|
||||
params: params,
|
||||
headers: getAuthorizationHeader(),
|
||||
);
|
||||
|
||||
if (response.data['data'] == null) {
|
||||
return DC.error(AnalyticFailure.empty());
|
||||
}
|
||||
|
||||
final dto = ProfitSharingDetailDto.fromJson(response.data['data']);
|
||||
|
||||
return DC.data(dto);
|
||||
} on ApiFailure catch (e, s) {
|
||||
log(
|
||||
'fetchProfitSharingDetailError',
|
||||
name: _logName,
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
return DC.error(AnalyticFailure.serverError(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ class CategoryAnalyticDto with _$CategoryAnalyticDto {
|
||||
const factory CategoryAnalyticDto({
|
||||
@JsonKey(name: 'organization_id') String? organizationId,
|
||||
@JsonKey(name: 'outlet_id') String? outletId,
|
||||
@JsonKey(name: 'outlet_name') String? outletName,
|
||||
@JsonKey(name: 'date_from') String? dateFrom,
|
||||
@JsonKey(name: 'date_to') String? dateTo,
|
||||
@JsonKey(name: 'data') List<CategoryAnalyticItemDto>? data,
|
||||
@@ -18,6 +19,7 @@ class CategoryAnalyticDto with _$CategoryAnalyticDto {
|
||||
CategoryAnalytic toDomain() => CategoryAnalytic(
|
||||
organizationId: organizationId ?? "",
|
||||
outletId: outletId ?? "",
|
||||
outletName: outletName ?? "",
|
||||
dateFrom: dateFrom ?? "",
|
||||
dateTo: dateTo ?? "",
|
||||
data: data?.map((e) => e.toDomain()).toList() ?? [],
|
||||
|
||||
@@ -7,6 +7,7 @@ class DashboardAnalyticDto with _$DashboardAnalyticDto {
|
||||
const factory DashboardAnalyticDto({
|
||||
@JsonKey(name: 'organization_id') String? organizationId,
|
||||
@JsonKey(name: 'outlet_id') String? outletId,
|
||||
@JsonKey(name: 'outlet_name') String? outletName,
|
||||
@JsonKey(name: 'date_from') String? dateFrom,
|
||||
@JsonKey(name: 'date_to') String? dateTo,
|
||||
@JsonKey(name: 'overview') DashboardOverviewDto? overview,
|
||||
@@ -22,6 +23,7 @@ class DashboardAnalyticDto with _$DashboardAnalyticDto {
|
||||
DashboardAnalytic toDomain() => DashboardAnalytic(
|
||||
organizationId: organizationId ?? '',
|
||||
outletId: outletId ?? '',
|
||||
outletName: outletName ?? '',
|
||||
dateFrom: dateFrom ?? '',
|
||||
dateTo: dateTo ?? '',
|
||||
overview: overview?.toDomain() ?? DashboardOverview.empty(),
|
||||
@@ -42,6 +44,9 @@ class DashboardOverviewDto with _$DashboardOverviewDto {
|
||||
@JsonKey(name: 'total_customers') int? totalCustomers,
|
||||
@JsonKey(name: 'voided_orders') int? voidedOrders,
|
||||
@JsonKey(name: 'refunded_orders') int? refundedOrders,
|
||||
@JsonKey(name: 'total_item_sold') int? totalItemSold,
|
||||
@JsonKey(name: 'total_low_stock') int? totalLowStock,
|
||||
@JsonKey(name: 'total_product_active') int? totalProductActive,
|
||||
}) = _DashboardOverviewDto;
|
||||
|
||||
factory DashboardOverviewDto.fromJson(Map<String, dynamic> json) =>
|
||||
@@ -54,6 +59,9 @@ class DashboardOverviewDto with _$DashboardOverviewDto {
|
||||
totalCustomers: totalCustomers ?? 0,
|
||||
voidedOrders: voidedOrders ?? 0,
|
||||
refundedOrders: refundedOrders ?? 0,
|
||||
totalItemSold: totalItemSold ?? 0,
|
||||
totalLowStock: totalLowStock ?? 0,
|
||||
totalProductActive: totalProductActive ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ class ExclusiveSummaryDto with _$ExclusiveSummaryDto {
|
||||
const factory ExclusiveSummaryDto({
|
||||
@JsonKey(name: 'organization_id') String? organizationId,
|
||||
@JsonKey(name: 'outlet_id') String? outletId,
|
||||
@JsonKey(name: 'outlet_name') String? outletName,
|
||||
@JsonKey(name: 'period') ExclusiveSummaryPeriodDto? period,
|
||||
@JsonKey(name: 'summary') ExclusiveSummarySummaryDto? summary,
|
||||
@JsonKey(name: 'reimburse') ExclusiveSummaryReimburseDto? reimburse,
|
||||
@@ -26,6 +27,7 @@ class ExclusiveSummaryDto with _$ExclusiveSummaryDto {
|
||||
ExclusiveSummary toDomain() => ExclusiveSummary(
|
||||
organizationId: organizationId ?? '',
|
||||
outletId: outletId ?? '',
|
||||
outletName: outletName ?? '',
|
||||
period: period?.toDomain() ?? ExclusiveSummaryPeriod.empty(),
|
||||
summary: summary?.toDomain() ?? ExclusiveSummarySummary.empty(),
|
||||
reimburse: reimburse?.toDomain() ?? ExclusiveSummaryReimburse.empty(),
|
||||
|
||||
@@ -7,6 +7,7 @@ class PaymentMethodAnalyticDto with _$PaymentMethodAnalyticDto {
|
||||
const factory PaymentMethodAnalyticDto({
|
||||
@JsonKey(name: 'organization_id') String? organizationId,
|
||||
@JsonKey(name: 'outlet_id') String? outletId,
|
||||
@JsonKey(name: 'outlet_name') String? outletName,
|
||||
@JsonKey(name: 'date_from') String? dateFrom,
|
||||
@JsonKey(name: 'date_to') String? dateTo,
|
||||
@JsonKey(name: 'group_by') String? groupBy,
|
||||
@@ -21,6 +22,7 @@ class PaymentMethodAnalyticDto with _$PaymentMethodAnalyticDto {
|
||||
return PaymentMethodAnalytic(
|
||||
organizationId: organizationId ?? '',
|
||||
outletId: outletId ?? '',
|
||||
outletName: outletName ?? '',
|
||||
dateFrom: dateFrom ?? '',
|
||||
dateTo: dateTo ?? '',
|
||||
groupBy: groupBy ?? '',
|
||||
|
||||
@@ -7,6 +7,7 @@ class ProductAnalyticDto with _$ProductAnalyticDto {
|
||||
const factory ProductAnalyticDto({
|
||||
@JsonKey(name: 'organization_id') String? organizationId,
|
||||
@JsonKey(name: 'outlet_id') String? outletId,
|
||||
@JsonKey(name: 'outlet_name') String? outletName,
|
||||
@JsonKey(name: 'date_from') String? dateFrom,
|
||||
@JsonKey(name: 'date_to') String? dateTo,
|
||||
@JsonKey(name: 'data') List<ProductAnalyticDataDto>? data,
|
||||
@@ -18,6 +19,7 @@ class ProductAnalyticDto with _$ProductAnalyticDto {
|
||||
ProductAnalytic toDomain() => ProductAnalytic(
|
||||
organizationId: organizationId ?? "",
|
||||
outletId: outletId ?? "",
|
||||
outletName: outletName ?? "",
|
||||
dateFrom: dateFrom ?? "",
|
||||
dateTo: dateTo ?? "",
|
||||
data: data?.map((e) => e.toDomain()).toList() ?? [],
|
||||
@@ -31,12 +33,21 @@ class ProductAnalyticDataDto with _$ProductAnalyticDataDto {
|
||||
const factory ProductAnalyticDataDto({
|
||||
@JsonKey(name: 'product_id') String? productId,
|
||||
@JsonKey(name: 'product_name') String? productName,
|
||||
@JsonKey(name: 'product_sku') String? productSku,
|
||||
@JsonKey(name: 'product_price') int? productPrice,
|
||||
@JsonKey(name: 'category_id') String? categoryId,
|
||||
@JsonKey(name: 'category_name') String? categoryName,
|
||||
@JsonKey(name: 'category_order') int? categoryOrder,
|
||||
@JsonKey(name: 'quantity_sold') int? quantitySold,
|
||||
@JsonKey(name: 'revenue') int? revenue,
|
||||
@JsonKey(name: 'average_price') double? averagePrice,
|
||||
@JsonKey(name: 'order_count') int? orderCount,
|
||||
@JsonKey(name: 'standard_hpp_per_unit') int? standardHppPerUnit,
|
||||
@JsonKey(name: 'standard_hpp_total') int? standardHppTotal,
|
||||
@JsonKey(name: 'fifo_hpp_per_unit') int? fifoHppPerUnit,
|
||||
@JsonKey(name: 'fifo_hpp_total') int? fifoHppTotal,
|
||||
@JsonKey(name: 'moving_average_hpp_per_unit') int? movingAverageHppPerUnit,
|
||||
@JsonKey(name: 'moving_average_hpp_total') int? movingAverageHppTotal,
|
||||
}) = _ProductAnalyticDataDto;
|
||||
|
||||
factory ProductAnalyticDataDto.fromJson(Map<String, dynamic> json) =>
|
||||
@@ -45,11 +56,20 @@ class ProductAnalyticDataDto with _$ProductAnalyticDataDto {
|
||||
ProductAnalyticData toDomain() => ProductAnalyticData(
|
||||
productId: productId ?? "",
|
||||
productName: productName ?? "",
|
||||
productSku: productSku ?? "",
|
||||
productPrice: productPrice ?? 0,
|
||||
categoryId: categoryId ?? "",
|
||||
categoryName: categoryName ?? "",
|
||||
categoryOrder: categoryOrder ?? 0,
|
||||
quantitySold: quantitySold ?? 0,
|
||||
revenue: revenue ?? 0,
|
||||
averagePrice: averagePrice ?? 0,
|
||||
orderCount: orderCount ?? 0,
|
||||
standardHppPerUnit: standardHppPerUnit ?? 0,
|
||||
standardHppTotal: standardHppTotal ?? 0,
|
||||
fifoHppPerUnit: fifoHppPerUnit ?? 0,
|
||||
fifoHppTotal: fifoHppTotal ?? 0,
|
||||
movingAverageHppPerUnit: movingAverageHppPerUnit ?? 0,
|
||||
movingAverageHppTotal: movingAverageHppTotal ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,12 +6,20 @@ class ProfitLossAnalyticDto with _$ProfitLossAnalyticDto {
|
||||
|
||||
const factory ProfitLossAnalyticDto({
|
||||
@JsonKey(name: 'organization_id') String? organizationId,
|
||||
@JsonKey(name: 'outlet_id') String? outletId,
|
||||
@JsonKey(name: 'outlet_name') String? outletName,
|
||||
@JsonKey(name: 'date_from') String? dateFrom,
|
||||
@JsonKey(name: 'date_to') String? dateTo,
|
||||
@JsonKey(name: 'group_by') String? groupBy,
|
||||
@JsonKey(name: 'summary') ProfitLossSummaryDto? summary,
|
||||
@JsonKey(name: 'data') List<ProfitLossDailyDataDto>? data,
|
||||
@JsonKey(name: 'product_data') List<ProfitLossProductDataDto>? productData,
|
||||
@JsonKey(name: 'main_summary')
|
||||
List<ProfitLossMainSummaryItemDto>? mainSummary,
|
||||
@JsonKey(name: 'purchasing') ProfitLossPurchasingDto? purchasing,
|
||||
@JsonKey(name: 'operational_expenses')
|
||||
List<ProfitLossOperationalExpenseDto>? operationalExpenses,
|
||||
@JsonKey(name: 'operational_expenses_total') int? operationalExpensesTotal,
|
||||
}) = _ProfitLossAnalyticDto;
|
||||
|
||||
factory ProfitLossAnalyticDto.fromJson(Map<String, dynamic> json) =>
|
||||
@@ -19,12 +27,20 @@ class ProfitLossAnalyticDto with _$ProfitLossAnalyticDto {
|
||||
|
||||
ProfitLossAnalytic toDomain() => ProfitLossAnalytic(
|
||||
organizationId: organizationId ?? '',
|
||||
outletId: outletId ?? '',
|
||||
outletName: outletName ?? '',
|
||||
dateFrom: dateFrom ?? '',
|
||||
dateTo: dateTo ?? '',
|
||||
groupBy: groupBy ?? '',
|
||||
summary: summary?.toDomain() ?? ProfitLossSummary.empty(),
|
||||
data: (data ?? []).map((e) => e.toDomain()).toList(),
|
||||
productData: (productData ?? []).map((e) => e.toDomain()).toList(),
|
||||
mainSummary: (mainSummary ?? []).map((e) => e.toDomain()).toList(),
|
||||
purchasing: purchasing?.toDomain() ?? ProfitLossPurchasing.empty(),
|
||||
operationalExpenses: (operationalExpenses ?? [])
|
||||
.map((e) => e.toDomain())
|
||||
.toList(),
|
||||
operationalExpensesTotal: operationalExpensesTotal ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -135,3 +151,99 @@ class ProfitLossProductDataDto with _$ProfitLossProductDataDto {
|
||||
profitPerUnit: profitPerUnit ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitLossMainSummaryItemDto with _$ProfitLossMainSummaryItemDto {
|
||||
const ProfitLossMainSummaryItemDto._();
|
||||
|
||||
const factory ProfitLossMainSummaryItemDto({
|
||||
@JsonKey(name: 'id') String? id,
|
||||
@JsonKey(name: 'label') String? label,
|
||||
@JsonKey(name: 'is_bold') bool? isBold,
|
||||
@JsonKey(name: 'today_nominal') int? todayNominal,
|
||||
@JsonKey(name: 'today_pct') double? todayPct,
|
||||
@JsonKey(name: 'mtd_nominal') int? mtdNominal,
|
||||
@JsonKey(name: 'mtd_pct') double? mtdPct,
|
||||
@JsonKey(name: 'sub_items') List<ProfitLossMainSummaryItemDto>? subItems,
|
||||
}) = _ProfitLossMainSummaryItemDto;
|
||||
|
||||
factory ProfitLossMainSummaryItemDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfitLossMainSummaryItemDtoFromJson(json);
|
||||
|
||||
ProfitLossMainSummaryItem toDomain() => ProfitLossMainSummaryItem(
|
||||
id: id ?? '',
|
||||
label: label ?? '',
|
||||
isBold: isBold ?? false,
|
||||
todayNominal: todayNominal ?? 0,
|
||||
todayPct: todayPct ?? 0.0,
|
||||
mtdNominal: mtdNominal ?? 0,
|
||||
mtdPct: mtdPct ?? 0.0,
|
||||
subItems: (subItems ?? []).map((e) => e.toDomain()).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitLossPurchasingDto with _$ProfitLossPurchasingDto {
|
||||
const ProfitLossPurchasingDto._();
|
||||
|
||||
const factory ProfitLossPurchasingDto({
|
||||
@JsonKey(name: 'today_total') int? todayTotal,
|
||||
@JsonKey(name: 'mtd_total') int? mtdTotal,
|
||||
@JsonKey(name: 'today_raw_material') int? todayRawMaterial,
|
||||
@JsonKey(name: 'mtd_raw_material') int? mtdRawMaterial,
|
||||
@JsonKey(name: 'today_expense') int? todayExpense,
|
||||
@JsonKey(name: 'mtd_expense') int? mtdExpense,
|
||||
@JsonKey(name: 'items') List<ProfitLossPurchasingItemDto>? items,
|
||||
}) = _ProfitLossPurchasingDto;
|
||||
|
||||
factory ProfitLossPurchasingDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfitLossPurchasingDtoFromJson(json);
|
||||
|
||||
ProfitLossPurchasing toDomain() => ProfitLossPurchasing(
|
||||
todayTotal: todayTotal ?? 0,
|
||||
mtdTotal: mtdTotal ?? 0,
|
||||
todayRawMaterial: todayRawMaterial ?? 0,
|
||||
mtdRawMaterial: mtdRawMaterial ?? 0,
|
||||
todayExpense: todayExpense ?? 0,
|
||||
mtdExpense: mtdExpense ?? 0,
|
||||
items: (items ?? []).map((e) => e.toDomain()).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitLossPurchasingItemDto with _$ProfitLossPurchasingItemDto {
|
||||
const ProfitLossPurchasingItemDto._();
|
||||
|
||||
const factory ProfitLossPurchasingItemDto({
|
||||
@JsonKey(name: 'date') String? date,
|
||||
@JsonKey(name: 'item') String? item,
|
||||
@JsonKey(name: 'quantity') int? quantity,
|
||||
@JsonKey(name: 'nominal') int? nominal,
|
||||
}) = _ProfitLossPurchasingItemDto;
|
||||
|
||||
factory ProfitLossPurchasingItemDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfitLossPurchasingItemDtoFromJson(json);
|
||||
|
||||
ProfitLossPurchasingItem toDomain() => ProfitLossPurchasingItem(
|
||||
date: date ?? '',
|
||||
item: item ?? '',
|
||||
quantity: quantity ?? 0,
|
||||
nominal: nominal ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitLossOperationalExpenseDto with _$ProfitLossOperationalExpenseDto {
|
||||
const ProfitLossOperationalExpenseDto._();
|
||||
|
||||
const factory ProfitLossOperationalExpenseDto({
|
||||
@JsonKey(name: 'item') String? item,
|
||||
@JsonKey(name: 'nominal') int? nominal,
|
||||
}) = _ProfitLossOperationalExpenseDto;
|
||||
|
||||
factory ProfitLossOperationalExpenseDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfitLossOperationalExpenseDtoFromJson(json);
|
||||
|
||||
ProfitLossOperationalExpense toDomain() =>
|
||||
ProfitLossOperationalExpense(item: item ?? '', nominal: nominal ?? 0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
part of '../analytic_dtos.dart';
|
||||
|
||||
@freezed
|
||||
class ProfitSharingDto with _$ProfitSharingDto {
|
||||
const ProfitSharingDto._();
|
||||
|
||||
const factory ProfitSharingDto({
|
||||
@JsonKey(name: 'organization_id') String? organizationId,
|
||||
@JsonKey(name: 'outlet_id') String? outletId,
|
||||
@JsonKey(name: 'outlet_name') String? outletName,
|
||||
@JsonKey(name: 'date_from') DateTime? dateFrom,
|
||||
@JsonKey(name: 'date_to') DateTime? dateTo,
|
||||
@JsonKey(name: 'data') List<ProfitSharingCategoryDto>? data,
|
||||
@JsonKey(name: 'budget') ProfitSharingBudgetDto? budget,
|
||||
}) = _ProfitSharingDto;
|
||||
|
||||
factory ProfitSharingDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfitSharingDtoFromJson(json);
|
||||
|
||||
ProfitSharing toDomain() => ProfitSharing(
|
||||
organizationId: organizationId ?? '',
|
||||
outletId: outletId ?? '',
|
||||
outletName: outletName ?? '',
|
||||
dateFrom: dateFrom?.toLocal() ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||
dateTo: dateTo?.toLocal() ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||
categories: data?.map((e) => e.toDomain()).toList() ?? [],
|
||||
budget: budget?.toDomain() ?? ProfitSharingBudget.empty(),
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitSharingCategoryDto with _$ProfitSharingCategoryDto {
|
||||
const ProfitSharingCategoryDto._();
|
||||
|
||||
const factory ProfitSharingCategoryDto({
|
||||
@JsonKey(name: 'parent_category_id') String? parentCategoryId,
|
||||
@JsonKey(name: 'parent_category_name') String? parentCategoryName,
|
||||
@JsonKey(name: 'total_revenue') num? totalRevenue,
|
||||
@JsonKey(name: 'total_quantity') num? totalQuantity,
|
||||
@JsonKey(name: 'category_count') num? categoryCount,
|
||||
@JsonKey(name: 'product_count') num? productCount,
|
||||
@JsonKey(name: 'order_count') num? orderCount,
|
||||
@JsonKey(name: 'total_standard_hpp') num? totalStandardHpp,
|
||||
@JsonKey(name: 'total_fifo_hpp') num? totalFifoHpp,
|
||||
@JsonKey(name: 'total_moving_average_hpp') num? totalMovingAverageHpp,
|
||||
}) = _ProfitSharingCategoryDto;
|
||||
|
||||
factory ProfitSharingCategoryDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfitSharingCategoryDtoFromJson(json);
|
||||
|
||||
ProfitSharingCategory toDomain() => ProfitSharingCategory(
|
||||
parentCategoryId: parentCategoryId ?? '',
|
||||
parentCategoryName: parentCategoryName ?? '',
|
||||
totalRevenue: totalRevenue?.toInt() ?? 0,
|
||||
totalQuantity: totalQuantity?.toInt() ?? 0,
|
||||
categoryCount: categoryCount?.toInt() ?? 0,
|
||||
productCount: productCount?.toInt() ?? 0,
|
||||
orderCount: orderCount?.toInt() ?? 0,
|
||||
totalStandardHpp: totalStandardHpp?.toInt() ?? 0,
|
||||
totalFifoHpp: totalFifoHpp?.toInt() ?? 0,
|
||||
totalMovingAverageHpp: totalMovingAverageHpp?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitSharingBudgetDto with _$ProfitSharingBudgetDto {
|
||||
const ProfitSharingBudgetDto._();
|
||||
|
||||
const factory ProfitSharingBudgetDto({
|
||||
@JsonKey(name: 'percentages') ProfitSharingPercentageDto? percentages,
|
||||
@JsonKey(name: 'cut_off_from') DateTime? cutOffFrom,
|
||||
@JsonKey(name: 'cut_off_to') DateTime? cutOffTo,
|
||||
@JsonKey(name: 'total') ProfitSharingPeriodDto? total,
|
||||
@JsonKey(name: 'weekly') List<ProfitSharingPeriodDto>? weekly,
|
||||
@JsonKey(name: 'monthly') List<ProfitSharingPeriodDto>? monthly,
|
||||
}) = _ProfitSharingBudgetDto;
|
||||
|
||||
factory ProfitSharingBudgetDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfitSharingBudgetDtoFromJson(json);
|
||||
|
||||
ProfitSharingBudget toDomain() => ProfitSharingBudget(
|
||||
percentages: percentages?.toDomain() ?? ProfitSharingPercentage.empty(),
|
||||
cutOffFrom: cutOffFrom?.toLocal() ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||
cutOffTo: cutOffTo?.toLocal() ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||
total: total?.toDomain() ?? ProfitSharingPeriod.empty(),
|
||||
weekly: weekly?.map((e) => e.toDomain()).toList() ?? [],
|
||||
monthly: monthly?.map((e) => e.toDomain()).toList() ?? [],
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitSharingPercentageDto with _$ProfitSharingPercentageDto {
|
||||
const ProfitSharingPercentageDto._();
|
||||
|
||||
const factory ProfitSharingPercentageDto({
|
||||
@JsonKey(name: 'purchase') num? purchase,
|
||||
@JsonKey(name: 'owner') num? owner,
|
||||
@JsonKey(name: 'team') num? team,
|
||||
}) = _ProfitSharingPercentageDto;
|
||||
|
||||
factory ProfitSharingPercentageDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfitSharingPercentageDtoFromJson(json);
|
||||
|
||||
ProfitSharingPercentage toDomain() => ProfitSharingPercentage(
|
||||
purchase: purchase?.toDouble() ?? 0,
|
||||
owner: owner?.toDouble() ?? 0,
|
||||
team: team?.toDouble() ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitSharingPeriodDto with _$ProfitSharingPeriodDto {
|
||||
const ProfitSharingPeriodDto._();
|
||||
|
||||
const factory ProfitSharingPeriodDto({
|
||||
@JsonKey(name: 'period_start') DateTime? periodStart,
|
||||
@JsonKey(name: 'period_end') DateTime? periodEnd,
|
||||
@JsonKey(name: 'revenue') num? revenue,
|
||||
@JsonKey(name: 'order_count') num? orderCount,
|
||||
@JsonKey(name: 'limit_purchase') num? limitPurchase,
|
||||
@JsonKey(name: 'limit_owner') num? limitOwner,
|
||||
@JsonKey(name: 'limit_team') num? limitTeam,
|
||||
@JsonKey(name: 'month') String? month,
|
||||
@JsonKey(name: 'week_count') num? weekCount,
|
||||
}) = _ProfitSharingPeriodDto;
|
||||
|
||||
factory ProfitSharingPeriodDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfitSharingPeriodDtoFromJson(json);
|
||||
|
||||
ProfitSharingPeriod toDomain() => ProfitSharingPeriod(
|
||||
periodStart:
|
||||
periodStart?.toLocal() ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||
periodEnd: periodEnd?.toLocal() ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||
revenue: revenue?.toInt() ?? 0,
|
||||
orderCount: orderCount?.toInt() ?? 0,
|
||||
limitPurchase: limitPurchase?.toInt() ?? 0,
|
||||
limitOwner: limitOwner?.toInt() ?? 0,
|
||||
limitTeam: limitTeam?.toInt() ?? 0,
|
||||
month: month ?? '',
|
||||
weekCount: weekCount?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitSharingDetailDto with _$ProfitSharingDetailDto {
|
||||
const ProfitSharingDetailDto._();
|
||||
|
||||
const factory ProfitSharingDetailDto({
|
||||
@JsonKey(name: 'organization_id') String? organizationId,
|
||||
@JsonKey(name: 'outlet_id') String? outletId,
|
||||
@JsonKey(name: 'outlet_name') String? outletName,
|
||||
@JsonKey(name: 'date_from') DateTime? dateFrom,
|
||||
@JsonKey(name: 'date_to') DateTime? dateTo,
|
||||
@JsonKey(name: 'parent_category_id') String? parentCategoryId,
|
||||
@JsonKey(name: 'parent_category_name') String? parentCategoryName,
|
||||
@JsonKey(name: 'summary') ProfitSharingSummaryDto? summary,
|
||||
@JsonKey(name: 'categories') List<ProfitSharingSubCategoryDto>? categories,
|
||||
@JsonKey(name: 'budget') ProfitSharingBudgetDto? budget,
|
||||
}) = _ProfitSharingDetailDto;
|
||||
|
||||
factory ProfitSharingDetailDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfitSharingDetailDtoFromJson(json);
|
||||
|
||||
ProfitSharingDetail toDomain() => ProfitSharingDetail(
|
||||
organizationId: organizationId ?? '',
|
||||
outletId: outletId ?? '',
|
||||
outletName: outletName ?? '',
|
||||
dateFrom: dateFrom?.toLocal() ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||
dateTo: dateTo?.toLocal() ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||
parentCategoryId: parentCategoryId ?? '',
|
||||
parentCategoryName: parentCategoryName ?? '',
|
||||
summary:
|
||||
summary?.toDomain(
|
||||
parentCategoryId: parentCategoryId ?? '',
|
||||
parentCategoryName: parentCategoryName ?? '',
|
||||
) ??
|
||||
ProfitSharingCategory.empty(),
|
||||
categories: categories?.map((e) => e.toDomain()).toList() ?? [],
|
||||
budget: budget?.toDomain() ?? ProfitSharingBudget.empty(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Ringkasan kategori induk pada endpoint detail — bentuknya sama dengan item
|
||||
/// pada endpoint daftar, hanya saja id & nama kategori ada di level atas.
|
||||
@freezed
|
||||
class ProfitSharingSummaryDto with _$ProfitSharingSummaryDto {
|
||||
const ProfitSharingSummaryDto._();
|
||||
|
||||
const factory ProfitSharingSummaryDto({
|
||||
@JsonKey(name: 'total_revenue') num? totalRevenue,
|
||||
@JsonKey(name: 'total_quantity') num? totalQuantity,
|
||||
@JsonKey(name: 'category_count') num? categoryCount,
|
||||
@JsonKey(name: 'product_count') num? productCount,
|
||||
@JsonKey(name: 'order_count') num? orderCount,
|
||||
@JsonKey(name: 'total_standard_hpp') num? totalStandardHpp,
|
||||
@JsonKey(name: 'total_fifo_hpp') num? totalFifoHpp,
|
||||
@JsonKey(name: 'total_moving_average_hpp') num? totalMovingAverageHpp,
|
||||
}) = _ProfitSharingSummaryDto;
|
||||
|
||||
factory ProfitSharingSummaryDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfitSharingSummaryDtoFromJson(json);
|
||||
|
||||
ProfitSharingCategory toDomain({
|
||||
required String parentCategoryId,
|
||||
required String parentCategoryName,
|
||||
}) => ProfitSharingCategory(
|
||||
parentCategoryId: parentCategoryId,
|
||||
parentCategoryName: parentCategoryName,
|
||||
totalRevenue: totalRevenue?.toInt() ?? 0,
|
||||
totalQuantity: totalQuantity?.toInt() ?? 0,
|
||||
categoryCount: categoryCount?.toInt() ?? 0,
|
||||
productCount: productCount?.toInt() ?? 0,
|
||||
orderCount: orderCount?.toInt() ?? 0,
|
||||
totalStandardHpp: totalStandardHpp?.toInt() ?? 0,
|
||||
totalFifoHpp: totalFifoHpp?.toInt() ?? 0,
|
||||
totalMovingAverageHpp: totalMovingAverageHpp?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitSharingSubCategoryDto with _$ProfitSharingSubCategoryDto {
|
||||
const ProfitSharingSubCategoryDto._();
|
||||
|
||||
const factory ProfitSharingSubCategoryDto({
|
||||
@JsonKey(name: 'category_id') String? categoryId,
|
||||
@JsonKey(name: 'category_name') String? categoryName,
|
||||
@JsonKey(name: 'total_revenue') num? totalRevenue,
|
||||
@JsonKey(name: 'total_quantity') num? totalQuantity,
|
||||
@JsonKey(name: 'product_count') num? productCount,
|
||||
@JsonKey(name: 'order_count') num? orderCount,
|
||||
@JsonKey(name: 'total_standard_hpp') num? totalStandardHpp,
|
||||
@JsonKey(name: 'total_fifo_hpp') num? totalFifoHpp,
|
||||
@JsonKey(name: 'total_moving_average_hpp') num? totalMovingAverageHpp,
|
||||
@JsonKey(name: 'products') List<ProfitSharingProductDto>? products,
|
||||
}) = _ProfitSharingSubCategoryDto;
|
||||
|
||||
factory ProfitSharingSubCategoryDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfitSharingSubCategoryDtoFromJson(json);
|
||||
|
||||
ProfitSharingSubCategory toDomain() => ProfitSharingSubCategory(
|
||||
categoryId: categoryId ?? '',
|
||||
categoryName: categoryName ?? '',
|
||||
totalRevenue: totalRevenue?.toInt() ?? 0,
|
||||
totalQuantity: totalQuantity?.toInt() ?? 0,
|
||||
productCount: productCount?.toInt() ?? 0,
|
||||
orderCount: orderCount?.toInt() ?? 0,
|
||||
totalStandardHpp: totalStandardHpp?.toInt() ?? 0,
|
||||
totalFifoHpp: totalFifoHpp?.toInt() ?? 0,
|
||||
totalMovingAverageHpp: totalMovingAverageHpp?.toInt() ?? 0,
|
||||
products: products?.map((e) => e.toDomain()).toList() ?? [],
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ProfitSharingProductDto with _$ProfitSharingProductDto {
|
||||
const ProfitSharingProductDto._();
|
||||
|
||||
const factory ProfitSharingProductDto({
|
||||
@JsonKey(name: 'product_id') String? productId,
|
||||
@JsonKey(name: 'product_name') String? productName,
|
||||
@JsonKey(name: 'product_sku') String? productSku,
|
||||
@JsonKey(name: 'product_price') num? productPrice,
|
||||
@JsonKey(name: 'quantity_sold') num? quantitySold,
|
||||
@JsonKey(name: 'revenue') num? revenue,
|
||||
@JsonKey(name: 'average_price') num? averagePrice,
|
||||
@JsonKey(name: 'order_count') num? orderCount,
|
||||
@JsonKey(name: 'standard_hpp_per_unit') num? standardHppPerUnit,
|
||||
@JsonKey(name: 'standard_hpp_total') num? standardHppTotal,
|
||||
@JsonKey(name: 'fifo_hpp_per_unit') num? fifoHppPerUnit,
|
||||
@JsonKey(name: 'fifo_hpp_total') num? fifoHppTotal,
|
||||
@JsonKey(name: 'moving_average_hpp_per_unit') num? movingAverageHppPerUnit,
|
||||
@JsonKey(name: 'moving_average_hpp_total') num? movingAverageHppTotal,
|
||||
}) = _ProfitSharingProductDto;
|
||||
|
||||
factory ProfitSharingProductDto.fromJson(Map<String, dynamic> json) =>
|
||||
_$ProfitSharingProductDtoFromJson(json);
|
||||
|
||||
ProfitSharingProduct toDomain() => ProfitSharingProduct(
|
||||
productId: productId ?? '',
|
||||
productName: productName ?? '',
|
||||
productSku: productSku ?? '',
|
||||
productPrice: productPrice?.toInt() ?? 0,
|
||||
quantitySold: quantitySold?.toInt() ?? 0,
|
||||
revenue: revenue?.toInt() ?? 0,
|
||||
averagePrice: averagePrice?.toDouble() ?? 0,
|
||||
orderCount: orderCount?.toInt() ?? 0,
|
||||
standardHppPerUnit: standardHppPerUnit?.toDouble() ?? 0,
|
||||
standardHppTotal: standardHppTotal?.toInt() ?? 0,
|
||||
fifoHppPerUnit: fifoHppPerUnit?.toDouble() ?? 0,
|
||||
fifoHppTotal: fifoHppTotal?.toInt() ?? 0,
|
||||
movingAverageHppPerUnit: movingAverageHppPerUnit?.toDouble() ?? 0,
|
||||
movingAverageHppTotal: movingAverageHppTotal?.toInt() ?? 0,
|
||||
);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ class SalesAnalyticDto with _$SalesAnalyticDto {
|
||||
const factory SalesAnalyticDto({
|
||||
@JsonKey(name: 'organization_id') String? organizationId,
|
||||
@JsonKey(name: 'outlet_id') String? outletId,
|
||||
@JsonKey(name: 'outlet_name') String? outletName,
|
||||
@JsonKey(name: 'date_from') DateTime? dateFrom,
|
||||
@JsonKey(name: 'date_to') DateTime? dateTo,
|
||||
@JsonKey(name: 'group_by') String? groupBy,
|
||||
@@ -20,6 +21,7 @@ class SalesAnalyticDto with _$SalesAnalyticDto {
|
||||
SalesAnalytic toDomain() => SalesAnalytic(
|
||||
organizationId: organizationId ?? '',
|
||||
outletId: outletId ?? '',
|
||||
outletName: outletName ?? '',
|
||||
dateFrom: dateFrom ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||
dateTo: dateTo ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||
groupBy: groupBy ?? '',
|
||||
|
||||
@@ -220,4 +220,50 @@ class AnalyticRepository implements IAnalyticRepository {
|
||||
return left(const AnalyticFailure.unexpectedError());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<AnalyticFailure, ProfitSharing>> getProfitSharing({
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
String? outletId,
|
||||
String groupBy = 'day',
|
||||
}) async {
|
||||
try {
|
||||
final result = await _dataProvider.fetchProfitSharing(
|
||||
dateFrom: dateFrom,
|
||||
dateTo: dateTo,
|
||||
outletId: _resolveOutletId(outletId),
|
||||
groupBy: groupBy,
|
||||
);
|
||||
|
||||
if (result.hasError) return left(result.error!);
|
||||
return right(result.data!.toDomain());
|
||||
} catch (e, s) {
|
||||
log('getProfitSharingError', name: _logName, error: e, stackTrace: s);
|
||||
return left(const AnalyticFailure.unexpectedError());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<AnalyticFailure, ProfitSharingDetail>> getProfitSharingDetail({
|
||||
required String parentCategoryId,
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
String? outletId,
|
||||
}) async {
|
||||
try {
|
||||
final result = await _dataProvider.fetchProfitSharingDetail(
|
||||
parentCategoryId: parentCategoryId,
|
||||
dateFrom: dateFrom,
|
||||
dateTo: dateTo,
|
||||
outletId: _resolveOutletId(outletId),
|
||||
);
|
||||
|
||||
if (result.hasError) return left(result.error!);
|
||||
return right(result.data!.toDomain());
|
||||
} catch (e, s) {
|
||||
log('getProfitSharingDetailError', name: _logName, error: e, stackTrace: s);
|
||||
return left(const AnalyticFailure.unexpectedError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,23 @@ class OutletLocalDataProvider {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> saveSelectedOutletName(String outletName) async {
|
||||
await _sharedPreferences.setString(
|
||||
LocalStorageKey.selectedOutletName,
|
||||
outletName,
|
||||
);
|
||||
}
|
||||
|
||||
String? getSelectedOutletId() {
|
||||
return _sharedPreferences.getString(LocalStorageKey.selectedOutletId);
|
||||
}
|
||||
|
||||
String? getSelectedOutletName() {
|
||||
return _sharedPreferences.getString(LocalStorageKey.selectedOutletName);
|
||||
}
|
||||
|
||||
Future<void> deleteSelectedOutletId() async {
|
||||
await _sharedPreferences.remove(LocalStorageKey.selectedOutletId);
|
||||
await _sharedPreferences.remove(LocalStorageKey.selectedOutletName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ import 'package:apskel_owner_flutter/application/analytic/product_analytic_loade
|
||||
as _i221;
|
||||
import 'package:apskel_owner_flutter/application/analytic/profit_loss_loader/profit_loss_loader_bloc.dart'
|
||||
as _i11;
|
||||
import 'package:apskel_owner_flutter/application/analytic/profit_sharing_detail_loader/profit_sharing_detail_loader_bloc.dart'
|
||||
as _i400;
|
||||
import 'package:apskel_owner_flutter/application/analytic/profit_sharing_loader/profit_sharing_loader_bloc.dart'
|
||||
as _i631;
|
||||
import 'package:apskel_owner_flutter/application/analytic/purchasing_analytic_loader/purchasing_analytic_loader_bloc.dart'
|
||||
as _i755;
|
||||
import 'package:apskel_owner_flutter/application/analytic/sales_loader/sales_loader_bloc.dart'
|
||||
@@ -290,6 +294,13 @@ extension GetItInjectableX on _i174.GetIt {
|
||||
gh.factory<_i11.ProfitLossLoaderBloc>(
|
||||
() => _i11.ProfitLossLoaderBloc(gh<_i477.IAnalyticRepository>()),
|
||||
);
|
||||
gh.factory<_i631.ProfitSharingLoaderBloc>(
|
||||
() => _i631.ProfitSharingLoaderBloc(gh<_i477.IAnalyticRepository>()),
|
||||
);
|
||||
gh.factory<_i400.ProfitSharingDetailLoaderBloc>(
|
||||
() =>
|
||||
_i400.ProfitSharingDetailLoaderBloc(gh<_i477.IAnalyticRepository>()),
|
||||
);
|
||||
gh.factory<_i945.AuthBloc>(
|
||||
() => _i945.AuthBloc(gh<_i49.IAuthRepository>()),
|
||||
);
|
||||
|
||||
+105
-1
@@ -496,5 +496,109 @@
|
||||
"example": "48"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mtd_month": "MTD ({month})",
|
||||
"@mtd_month": {
|
||||
"placeholders": {
|
||||
"month": {
|
||||
"type": "String",
|
||||
"example": "June"
|
||||
}
|
||||
}
|
||||
},
|
||||
"profit_loss_date": "Profit / Loss · {date}",
|
||||
"@profit_loss_date": {
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"type": "String",
|
||||
"example": "22 Jun 2026"
|
||||
}
|
||||
}
|
||||
},
|
||||
"profit_loss_report": "Profit & Loss Report",
|
||||
"@profit_loss_report": {},
|
||||
"net_profit_loss": "Net Profit/Loss",
|
||||
"@net_profit_loss": {},
|
||||
"cost_breakdown": "Cost Breakdown",
|
||||
"@cost_breakdown": {},
|
||||
"all_outlets": "All Outlets",
|
||||
"@all_outlets": {},
|
||||
"salary_dw": "DW Salary",
|
||||
"@salary_dw": {},
|
||||
"salary_staff": "Staff Salary",
|
||||
"@salary_staff": {},
|
||||
"salary_other": "Other Salary",
|
||||
"@salary_other": {},
|
||||
"other_operational_expenses": "Other Operational Expenses",
|
||||
"@other_operational_expenses": {},
|
||||
"daily_revenue": "Daily Revenue",
|
||||
"@daily_revenue": {},
|
||||
"daily_revenue_desc": "Monday – Sunday · in thousands of Rupiah",
|
||||
"@daily_revenue_desc": {},
|
||||
"no_data_yet": "No data yet",
|
||||
"@no_data_yet": {},
|
||||
"total_inventory_value": "Total Inventory Value",
|
||||
"@total_inventory_value": {},
|
||||
"profit_sharing": "Profit Sharing",
|
||||
"@profit_sharing": {},
|
||||
"profit_sharing_desc": "Revenue allocation for purchasing, owner, and team",
|
||||
"@profit_sharing_desc": {},
|
||||
"profit_sharing_allocation": "Profit Sharing Allocation",
|
||||
"@profit_sharing_allocation": {},
|
||||
"share_purchase": "Purchasing",
|
||||
"@share_purchase": {},
|
||||
"share_owner": "Owner",
|
||||
"@share_owner": {},
|
||||
"share_team": "Team",
|
||||
"@share_team": {},
|
||||
"weekly": "Weekly",
|
||||
"@weekly": {},
|
||||
"monthly": "Monthly",
|
||||
"@monthly": {},
|
||||
"period_breakdown": "Period Breakdown",
|
||||
"@period_breakdown": {},
|
||||
"revenue_by_brand": "Revenue by Brand",
|
||||
"@revenue_by_brand": {},
|
||||
"week_count": "{count} weeks",
|
||||
"@week_count": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"order_count_label": "{count} orders",
|
||||
"@order_count_label": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "328"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gross_margin": "Gross Margin",
|
||||
"@gross_margin": {},
|
||||
"parent_category_summary": "Parent Category Summary",
|
||||
"@parent_category_summary": {},
|
||||
"sub_category": "Sub Category",
|
||||
"@sub_category": {},
|
||||
"qty": "Qty",
|
||||
"@qty": {},
|
||||
"std_hpp": "Std HPP",
|
||||
"@std_hpp": {},
|
||||
"real_hpp": "Real HPP",
|
||||
"@real_hpp": {},
|
||||
"status": "Status",
|
||||
"@status": {},
|
||||
"grand_total": "TOTAL",
|
||||
"@grand_total": {},
|
||||
"status_healthy": "Healthy",
|
||||
"@status_healthy": {},
|
||||
"status_watch": "Watch",
|
||||
"@status_watch": {},
|
||||
"status_critical": "Unhealthy",
|
||||
"@status_critical": {},
|
||||
"tap_row_for_detail": "Tap a card to see sub category and product details",
|
||||
"@tap_row_for_detail": {}
|
||||
}
|
||||
|
||||
+105
-1
@@ -496,5 +496,109 @@
|
||||
"example": "48"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mtd_month": "MTD ({month})",
|
||||
"@mtd_month": {
|
||||
"placeholders": {
|
||||
"month": {
|
||||
"type": "String",
|
||||
"example": "Juni"
|
||||
}
|
||||
}
|
||||
},
|
||||
"profit_loss_date": "Laba / Rugi · {date}",
|
||||
"@profit_loss_date": {
|
||||
"placeholders": {
|
||||
"date": {
|
||||
"type": "String",
|
||||
"example": "22 Jun 2026"
|
||||
}
|
||||
}
|
||||
},
|
||||
"profit_loss_report": "Laporan Laba Rugi",
|
||||
"@profit_loss_report": {},
|
||||
"net_profit_loss": "Laba/Rugi Bersih",
|
||||
"@net_profit_loss": {},
|
||||
"cost_breakdown": "Rincian Biaya",
|
||||
"@cost_breakdown": {},
|
||||
"all_outlets": "Semua Outlet",
|
||||
"@all_outlets": {},
|
||||
"salary_dw": "Gaji DW",
|
||||
"@salary_dw": {},
|
||||
"salary_staff": "Gaji Staff",
|
||||
"@salary_staff": {},
|
||||
"salary_other": "Gaji Lainnya",
|
||||
"@salary_other": {},
|
||||
"other_operational_expenses": "Biaya Ops Lainnya",
|
||||
"@other_operational_expenses": {},
|
||||
"daily_revenue": "Omzet Harian",
|
||||
"@daily_revenue": {},
|
||||
"daily_revenue_desc": "Senin – Minggu · dalam ribuan Rupiah",
|
||||
"@daily_revenue_desc": {},
|
||||
"no_data_yet": "Belum ada data",
|
||||
"@no_data_yet": {},
|
||||
"total_inventory_value": "Total Nilai Inventori",
|
||||
"@total_inventory_value": {},
|
||||
"profit_sharing": "Bagi Hasil",
|
||||
"@profit_sharing": {},
|
||||
"profit_sharing_desc": "Alokasi omzet untuk belanja, owner, dan tim",
|
||||
"@profit_sharing_desc": {},
|
||||
"profit_sharing_allocation": "Alokasi Bagi Hasil",
|
||||
"@profit_sharing_allocation": {},
|
||||
"share_purchase": "Belanja",
|
||||
"@share_purchase": {},
|
||||
"share_owner": "Owner",
|
||||
"@share_owner": {},
|
||||
"share_team": "Tim",
|
||||
"@share_team": {},
|
||||
"weekly": "Mingguan",
|
||||
"@weekly": {},
|
||||
"monthly": "Bulanan",
|
||||
"@monthly": {},
|
||||
"period_breakdown": "Rincian Periode",
|
||||
"@period_breakdown": {},
|
||||
"revenue_by_brand": "Omzet per Brand",
|
||||
"@revenue_by_brand": {},
|
||||
"week_count": "{count} minggu",
|
||||
"@week_count": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"order_count_label": "{count} pesanan",
|
||||
"@order_count_label": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "328"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gross_margin": "Margin Kotor",
|
||||
"@gross_margin": {},
|
||||
"parent_category_summary": "Ringkasan Kategori Induk",
|
||||
"@parent_category_summary": {},
|
||||
"sub_category": "Sub Kategori",
|
||||
"@sub_category": {},
|
||||
"qty": "Qty",
|
||||
"@qty": {},
|
||||
"std_hpp": "Std HPP",
|
||||
"@std_hpp": {},
|
||||
"real_hpp": "Real HPP",
|
||||
"@real_hpp": {},
|
||||
"status": "Status",
|
||||
"@status": {},
|
||||
"grand_total": "TOTAL",
|
||||
"@grand_total": {},
|
||||
"status_healthy": "Sehat",
|
||||
"@status_healthy": {},
|
||||
"status_watch": "Waspada",
|
||||
"@status_watch": {},
|
||||
"status_critical": "Tidak Sehat",
|
||||
"@status_critical": {},
|
||||
"tap_row_for_detail": "Ketuk kartu untuk melihat rincian sub kategori dan produk",
|
||||
"@tap_row_for_detail": {}
|
||||
}
|
||||
|
||||
@@ -1480,6 +1480,234 @@ abstract class AppLocalizations {
|
||||
/// In en, this message translates to:
|
||||
/// **'{count} portions sold'**
|
||||
String portion_sold(int count);
|
||||
|
||||
/// No description provided for @mtd_month.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'MTD ({month})'**
|
||||
String mtd_month(String month);
|
||||
|
||||
/// No description provided for @profit_loss_date.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Profit / Loss · {date}'**
|
||||
String profit_loss_date(String date);
|
||||
|
||||
/// No description provided for @profit_loss_report.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Profit & Loss Report'**
|
||||
String get profit_loss_report;
|
||||
|
||||
/// No description provided for @net_profit_loss.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Net Profit/Loss'**
|
||||
String get net_profit_loss;
|
||||
|
||||
/// No description provided for @cost_breakdown.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Cost Breakdown'**
|
||||
String get cost_breakdown;
|
||||
|
||||
/// No description provided for @all_outlets.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'All Outlets'**
|
||||
String get all_outlets;
|
||||
|
||||
/// No description provided for @salary_dw.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'DW Salary'**
|
||||
String get salary_dw;
|
||||
|
||||
/// No description provided for @salary_staff.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Staff Salary'**
|
||||
String get salary_staff;
|
||||
|
||||
/// No description provided for @salary_other.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Other Salary'**
|
||||
String get salary_other;
|
||||
|
||||
/// No description provided for @other_operational_expenses.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Other Operational Expenses'**
|
||||
String get other_operational_expenses;
|
||||
|
||||
/// No description provided for @daily_revenue.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Daily Revenue'**
|
||||
String get daily_revenue;
|
||||
|
||||
/// No description provided for @daily_revenue_desc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Monday – Sunday · in thousands of Rupiah'**
|
||||
String get daily_revenue_desc;
|
||||
|
||||
/// No description provided for @no_data_yet.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No data yet'**
|
||||
String get no_data_yet;
|
||||
|
||||
/// No description provided for @total_inventory_value.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Total Inventory Value'**
|
||||
String get total_inventory_value;
|
||||
|
||||
/// No description provided for @profit_sharing.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Profit Sharing'**
|
||||
String get profit_sharing;
|
||||
|
||||
/// No description provided for @profit_sharing_desc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Revenue allocation for purchasing, owner, and team'**
|
||||
String get profit_sharing_desc;
|
||||
|
||||
/// No description provided for @profit_sharing_allocation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Profit Sharing Allocation'**
|
||||
String get profit_sharing_allocation;
|
||||
|
||||
/// No description provided for @share_purchase.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Purchasing'**
|
||||
String get share_purchase;
|
||||
|
||||
/// No description provided for @share_owner.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Owner'**
|
||||
String get share_owner;
|
||||
|
||||
/// No description provided for @share_team.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Team'**
|
||||
String get share_team;
|
||||
|
||||
/// No description provided for @weekly.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Weekly'**
|
||||
String get weekly;
|
||||
|
||||
/// No description provided for @monthly.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Monthly'**
|
||||
String get monthly;
|
||||
|
||||
/// No description provided for @period_breakdown.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Period Breakdown'**
|
||||
String get period_breakdown;
|
||||
|
||||
/// No description provided for @revenue_by_brand.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Revenue by Brand'**
|
||||
String get revenue_by_brand;
|
||||
|
||||
/// No description provided for @week_count.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{count} weeks'**
|
||||
String week_count(int count);
|
||||
|
||||
/// No description provided for @order_count_label.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{count} orders'**
|
||||
String order_count_label(int count);
|
||||
|
||||
/// No description provided for @gross_margin.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Gross Margin'**
|
||||
String get gross_margin;
|
||||
|
||||
/// No description provided for @parent_category_summary.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Parent Category Summary'**
|
||||
String get parent_category_summary;
|
||||
|
||||
/// No description provided for @sub_category.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sub Category'**
|
||||
String get sub_category;
|
||||
|
||||
/// No description provided for @qty.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Qty'**
|
||||
String get qty;
|
||||
|
||||
/// No description provided for @std_hpp.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Std HPP'**
|
||||
String get std_hpp;
|
||||
|
||||
/// No description provided for @real_hpp.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Real HPP'**
|
||||
String get real_hpp;
|
||||
|
||||
/// No description provided for @status.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Status'**
|
||||
String get status;
|
||||
|
||||
/// No description provided for @grand_total.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'TOTAL'**
|
||||
String get grand_total;
|
||||
|
||||
/// No description provided for @status_healthy.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Healthy'**
|
||||
String get status_healthy;
|
||||
|
||||
/// No description provided for @status_watch.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Watch'**
|
||||
String get status_watch;
|
||||
|
||||
/// No description provided for @status_critical.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Unhealthy'**
|
||||
String get status_critical;
|
||||
|
||||
/// No description provided for @tap_row_for_detail.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Tap a card to see sub category and product details'**
|
||||
String get tap_row_for_detail;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
|
||||
@@ -710,4 +710,126 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String portion_sold(int count) {
|
||||
return '$count portions sold';
|
||||
}
|
||||
|
||||
@override
|
||||
String mtd_month(String month) {
|
||||
return 'MTD ($month)';
|
||||
}
|
||||
|
||||
@override
|
||||
String profit_loss_date(String date) {
|
||||
return 'Profit / Loss · $date';
|
||||
}
|
||||
|
||||
@override
|
||||
String get profit_loss_report => 'Profit & Loss Report';
|
||||
|
||||
@override
|
||||
String get net_profit_loss => 'Net Profit/Loss';
|
||||
|
||||
@override
|
||||
String get cost_breakdown => 'Cost Breakdown';
|
||||
|
||||
@override
|
||||
String get all_outlets => 'All Outlets';
|
||||
|
||||
@override
|
||||
String get salary_dw => 'DW Salary';
|
||||
|
||||
@override
|
||||
String get salary_staff => 'Staff Salary';
|
||||
|
||||
@override
|
||||
String get salary_other => 'Other Salary';
|
||||
|
||||
@override
|
||||
String get other_operational_expenses => 'Other Operational Expenses';
|
||||
|
||||
@override
|
||||
String get daily_revenue => 'Daily Revenue';
|
||||
|
||||
@override
|
||||
String get daily_revenue_desc => 'Monday – Sunday · in thousands of Rupiah';
|
||||
|
||||
@override
|
||||
String get no_data_yet => 'No data yet';
|
||||
|
||||
@override
|
||||
String get total_inventory_value => 'Total Inventory Value';
|
||||
|
||||
@override
|
||||
String get profit_sharing => 'Profit Sharing';
|
||||
|
||||
@override
|
||||
String get profit_sharing_desc => 'Revenue allocation for purchasing, owner, and team';
|
||||
|
||||
@override
|
||||
String get profit_sharing_allocation => 'Profit Sharing Allocation';
|
||||
|
||||
@override
|
||||
String get share_purchase => 'Purchasing';
|
||||
|
||||
@override
|
||||
String get share_owner => 'Owner';
|
||||
|
||||
@override
|
||||
String get share_team => 'Team';
|
||||
|
||||
@override
|
||||
String get weekly => 'Weekly';
|
||||
|
||||
@override
|
||||
String get monthly => 'Monthly';
|
||||
|
||||
@override
|
||||
String get period_breakdown => 'Period Breakdown';
|
||||
|
||||
@override
|
||||
String get revenue_by_brand => 'Revenue by Brand';
|
||||
|
||||
@override
|
||||
String week_count(int count) {
|
||||
return '$count weeks';
|
||||
}
|
||||
|
||||
@override
|
||||
String order_count_label(int count) {
|
||||
return '$count orders';
|
||||
}
|
||||
|
||||
@override
|
||||
String get gross_margin => 'Gross Margin';
|
||||
|
||||
@override
|
||||
String get parent_category_summary => 'Parent Category Summary';
|
||||
|
||||
@override
|
||||
String get sub_category => 'Sub Category';
|
||||
|
||||
@override
|
||||
String get qty => 'Qty';
|
||||
|
||||
@override
|
||||
String get std_hpp => 'Std HPP';
|
||||
|
||||
@override
|
||||
String get real_hpp => 'Real HPP';
|
||||
|
||||
@override
|
||||
String get status => 'Status';
|
||||
|
||||
@override
|
||||
String get grand_total => 'TOTAL';
|
||||
|
||||
@override
|
||||
String get status_healthy => 'Healthy';
|
||||
|
||||
@override
|
||||
String get status_watch => 'Watch';
|
||||
|
||||
@override
|
||||
String get status_critical => 'Unhealthy';
|
||||
|
||||
@override
|
||||
String get tap_row_for_detail => 'Tap a card to see sub category and product details';
|
||||
}
|
||||
|
||||
@@ -710,4 +710,126 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
String portion_sold(int count) {
|
||||
return '$count porsi terjual';
|
||||
}
|
||||
|
||||
@override
|
||||
String mtd_month(String month) {
|
||||
return 'MTD ($month)';
|
||||
}
|
||||
|
||||
@override
|
||||
String profit_loss_date(String date) {
|
||||
return 'Laba / Rugi · $date';
|
||||
}
|
||||
|
||||
@override
|
||||
String get profit_loss_report => 'Laporan Laba Rugi';
|
||||
|
||||
@override
|
||||
String get net_profit_loss => 'Laba/Rugi Bersih';
|
||||
|
||||
@override
|
||||
String get cost_breakdown => 'Rincian Biaya';
|
||||
|
||||
@override
|
||||
String get all_outlets => 'Semua Outlet';
|
||||
|
||||
@override
|
||||
String get salary_dw => 'Gaji DW';
|
||||
|
||||
@override
|
||||
String get salary_staff => 'Gaji Staff';
|
||||
|
||||
@override
|
||||
String get salary_other => 'Gaji Lainnya';
|
||||
|
||||
@override
|
||||
String get other_operational_expenses => 'Biaya Ops Lainnya';
|
||||
|
||||
@override
|
||||
String get daily_revenue => 'Omzet Harian';
|
||||
|
||||
@override
|
||||
String get daily_revenue_desc => 'Senin – Minggu · dalam ribuan Rupiah';
|
||||
|
||||
@override
|
||||
String get no_data_yet => 'Belum ada data';
|
||||
|
||||
@override
|
||||
String get total_inventory_value => 'Total Nilai Inventori';
|
||||
|
||||
@override
|
||||
String get profit_sharing => 'Bagi Hasil';
|
||||
|
||||
@override
|
||||
String get profit_sharing_desc => 'Alokasi omzet untuk belanja, owner, dan tim';
|
||||
|
||||
@override
|
||||
String get profit_sharing_allocation => 'Alokasi Bagi Hasil';
|
||||
|
||||
@override
|
||||
String get share_purchase => 'Belanja';
|
||||
|
||||
@override
|
||||
String get share_owner => 'Owner';
|
||||
|
||||
@override
|
||||
String get share_team => 'Tim';
|
||||
|
||||
@override
|
||||
String get weekly => 'Mingguan';
|
||||
|
||||
@override
|
||||
String get monthly => 'Bulanan';
|
||||
|
||||
@override
|
||||
String get period_breakdown => 'Rincian Periode';
|
||||
|
||||
@override
|
||||
String get revenue_by_brand => 'Omzet per Brand';
|
||||
|
||||
@override
|
||||
String week_count(int count) {
|
||||
return '$count minggu';
|
||||
}
|
||||
|
||||
@override
|
||||
String order_count_label(int count) {
|
||||
return '$count pesanan';
|
||||
}
|
||||
|
||||
@override
|
||||
String get gross_margin => 'Margin Kotor';
|
||||
|
||||
@override
|
||||
String get parent_category_summary => 'Ringkasan Kategori Induk';
|
||||
|
||||
@override
|
||||
String get sub_category => 'Sub Kategori';
|
||||
|
||||
@override
|
||||
String get qty => 'Qty';
|
||||
|
||||
@override
|
||||
String get std_hpp => 'Std HPP';
|
||||
|
||||
@override
|
||||
String get real_hpp => 'Real HPP';
|
||||
|
||||
@override
|
||||
String get status => 'Status';
|
||||
|
||||
@override
|
||||
String get grand_total => 'TOTAL';
|
||||
|
||||
@override
|
||||
String get status_healthy => 'Sehat';
|
||||
|
||||
@override
|
||||
String get status_watch => 'Waspada';
|
||||
|
||||
@override
|
||||
String get status_critical => 'Tidak Sehat';
|
||||
|
||||
@override
|
||||
String get tap_row_for_detail => 'Ketuk kartu untuk melihat rincian sub kategori dan produk';
|
||||
}
|
||||
|
||||
@@ -52,6 +52,10 @@ class $AssetsIconsGen {
|
||||
class $AssetsImagesGen {
|
||||
const $AssetsImagesGen();
|
||||
|
||||
/// File path: assets/images/ic_launcher.png
|
||||
AssetGenImage get icLauncher =>
|
||||
const AssetGenImage('assets/images/ic_launcher.png');
|
||||
|
||||
/// File path: assets/images/ic_notification.png
|
||||
AssetGenImage get icNotification =>
|
||||
const AssetGenImage('assets/images/ic_notification.png');
|
||||
@@ -60,7 +64,7 @@ class $AssetsImagesGen {
|
||||
AssetGenImage get logo => const AssetGenImage('assets/images/logo.png');
|
||||
|
||||
/// List of all assets
|
||||
List<AssetGenImage> get values => [icNotification, logo];
|
||||
List<AssetGenImage> get values => [icLauncher, icNotification, logo];
|
||||
}
|
||||
|
||||
class Assets {
|
||||
|
||||
@@ -13,13 +13,14 @@ class DateRangePickerBottomSheet {
|
||||
DateTime? maxDate,
|
||||
String? confirmText,
|
||||
String? cancelText,
|
||||
Color primaryColor = Colors.blue,
|
||||
Color primaryColor = const Color(0xFFD90000),
|
||||
Function(DateTime? startDate, DateTime? endDate)? onChanged,
|
||||
}) async {
|
||||
return await showModalBottomSheet<DateRangePickerSelectionChangedArgs?>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
barrierColor: Colors.transparent,
|
||||
isDismissible: false,
|
||||
enableDrag: false,
|
||||
builder: (BuildContext context) => _DateRangePickerBottomSheet(
|
||||
@@ -71,6 +72,8 @@ class _DateRangePickerBottomSheetState
|
||||
DateRangePickerSelectionChangedArgs? _selectionChangedArgs;
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _slideAnimation;
|
||||
final DateRangePickerController _pickerController =
|
||||
DateRangePickerController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -88,6 +91,7 @@ class _DateRangePickerBottomSheetState
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
_pickerController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -135,10 +139,75 @@ class _DateRangePickerBottomSheetState
|
||||
return false;
|
||||
}
|
||||
|
||||
void _applyQuickFilter(DateTime start, DateTime end) {
|
||||
final range = PickerDateRange(start, end);
|
||||
_pickerController.selectedRange = range;
|
||||
setState(() {
|
||||
_selectionChangedArgs = DateRangePickerSelectionChangedArgs(range);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildQuickFilters() {
|
||||
final now = DateTime.now();
|
||||
final todayStart = DateTime(now.year, now.month, now.day);
|
||||
|
||||
// This week (Monday to today)
|
||||
final weekday = now.weekday; // 1=Mon, 7=Sun
|
||||
final weekStart = todayStart.subtract(Duration(days: weekday - 1));
|
||||
|
||||
// This month (1st to today)
|
||||
final monthStart = DateTime(now.year, now.month, 1);
|
||||
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_buildFilterChip(
|
||||
context.lang.today,
|
||||
() => _applyQuickFilter(todayStart, todayStart),
|
||||
),
|
||||
_buildFilterChip(
|
||||
'Minggu ini',
|
||||
() => _applyQuickFilter(weekStart, todayStart),
|
||||
),
|
||||
_buildFilterChip(
|
||||
'Bulan ini',
|
||||
() => _applyQuickFilter(monthStart, todayStart),
|
||||
),
|
||||
_buildFilterChip(
|
||||
'MTD',
|
||||
() => _applyQuickFilter(monthStart, todayStart),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFilterChip(String label, VoidCallback onTap) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.primaryColor.withOpacity(0.08),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: widget.primaryColor.withOpacity(0.3)),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: widget.primaryColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenHeight = MediaQuery.of(context).size.height;
|
||||
final bottomSheetHeight = screenHeight * 0.75;
|
||||
final bottomSheetHeight = screenHeight * 0.85;
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: _animationController,
|
||||
@@ -209,7 +278,12 @@ class _DateRangePickerBottomSheetState
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Quick filter chips
|
||||
_buildQuickFilters(),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Date Picker
|
||||
Container(
|
||||
@@ -221,8 +295,10 @@ class _DateRangePickerBottomSheetState
|
||||
),
|
||||
),
|
||||
child: SfDateRangePicker(
|
||||
controller: _pickerController,
|
||||
onSelectionChanged: _onSelectionChanged,
|
||||
selectionMode: DateRangePickerSelectionMode.range,
|
||||
backgroundColor: Colors.white,
|
||||
initialSelectedRange:
|
||||
(widget.initialStartDate != null &&
|
||||
widget.initialEndDate != null)
|
||||
@@ -233,6 +309,7 @@ class _DateRangePickerBottomSheetState
|
||||
: null,
|
||||
minDate: widget.minDate,
|
||||
maxDate: widget.maxDate,
|
||||
selectionColor: widget.primaryColor,
|
||||
startRangeSelectionColor: widget.primaryColor,
|
||||
endRangeSelectionColor: widget.primaryColor,
|
||||
rangeSelectionColor: widget.primaryColor
|
||||
@@ -249,7 +326,7 @@ class _DateRangePickerBottomSheetState
|
||||
),
|
||||
monthViewSettings: DateRangePickerMonthViewSettings(
|
||||
viewHeaderStyle: DateRangePickerViewHeaderStyle(
|
||||
backgroundColor: Colors.grey.withOpacity(0.1),
|
||||
backgroundColor: Colors.white,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,186 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
|
||||
class DailyRevenueChart extends StatelessWidget {
|
||||
final List<DashboardRecentSale> salesData;
|
||||
|
||||
const DailyRevenueChart({super.key, required this.salesData});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.textLight.withOpacity(0.08),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Title
|
||||
Text(
|
||||
context.lang.daily_revenue,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
context.lang.daily_revenue_desc,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Bar Chart
|
||||
salesData.isEmpty
|
||||
? _buildEmptyState()
|
||||
: SizedBox(height: 200, child: _buildBarChart()),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState() {
|
||||
return SizedBox(
|
||||
height: 200,
|
||||
child: Center(
|
||||
child: Builder(
|
||||
builder: (context) => Text(
|
||||
context.lang.no_data_yet,
|
||||
style: AppStyle.md.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBarChart() {
|
||||
final maxValue = _getMaxValue();
|
||||
|
||||
return BarChart(
|
||||
BarChartData(
|
||||
alignment: BarChartAlignment.spaceAround,
|
||||
maxY: maxValue,
|
||||
minY: 0,
|
||||
barTouchData: BarTouchData(
|
||||
enabled: true,
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
tooltipPadding: const EdgeInsets.all(8),
|
||||
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
||||
if (groupIndex < salesData.length) {
|
||||
final sale = salesData[groupIndex];
|
||||
return BarTooltipItem(
|
||||
sale.sales.currencyFormatRp,
|
||||
const TextStyle(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 30,
|
||||
getTitlesWidget: (value, meta) {
|
||||
final index = value.toInt();
|
||||
if (index >= 0 && index < salesData.length) {
|
||||
final date = DateTime.tryParse(salesData[index].date);
|
||||
final dayName = date != null
|
||||
? _getShortDayName(date.weekday)
|
||||
: '';
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text(
|
||||
dayName,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
gridData: FlGridData(show: false),
|
||||
borderData: FlBorderData(show: false),
|
||||
barGroups: _buildBarGroups(maxValue),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<BarChartGroupData> _buildBarGroups(double maxValue) {
|
||||
return salesData.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final sale = entry.value;
|
||||
final value = sale.sales.toDouble();
|
||||
|
||||
// Gradient from green to lighter green for higher values
|
||||
final ratio = maxValue > 0 ? value / maxValue : 0.0;
|
||||
final color = Color.lerp(
|
||||
AppColor.success,
|
||||
AppColor.success.withGreen(230),
|
||||
ratio,
|
||||
)!;
|
||||
|
||||
return BarChartGroupData(
|
||||
x: index,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: value,
|
||||
width: 28,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
gradient: LinearGradient(
|
||||
colors: [color, color.withOpacity(0.8)],
|
||||
begin: Alignment.bottomCenter,
|
||||
end: Alignment.topCenter,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
double _getMaxValue() {
|
||||
if (salesData.isEmpty) return 1000000;
|
||||
final maxValue = salesData
|
||||
.map((e) => e.sales.toDouble())
|
||||
.reduce((a, b) => a > b ? a : b);
|
||||
return maxValue * 1.2;
|
||||
}
|
||||
|
||||
String _getShortDayName(int weekday) {
|
||||
const days = ['Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab', 'Min'];
|
||||
return days[weekday - 1];
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,13 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import '../../../application/analytic/category_analytic_loader/category_analytic_loader_bloc.dart';
|
||||
import '../../../application/analytic/profit_loss_loader/profit_loss_loader_bloc.dart';
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../../domain/analytic/analytic.dart';
|
||||
import '../../../injection.dart';
|
||||
import '../../components/appbar/appbar.dart';
|
||||
import '../../components/field/date_range_picker_field.dart';
|
||||
import 'widgets/cash_flow.dart';
|
||||
import 'widgets/category.dart';
|
||||
import 'widgets/product.dart';
|
||||
import 'widgets/profit_loss.dart';
|
||||
import 'widgets/summary_card.dart';
|
||||
import 'widgets/cost_breakdown.dart';
|
||||
import 'widgets/profit_loss_header.dart';
|
||||
import 'widgets/profit_loss_report.dart';
|
||||
|
||||
@RoutePage()
|
||||
class FinancePage extends StatefulWidget implements AutoRouteWrapper {
|
||||
@@ -25,80 +17,40 @@ class FinancePage extends StatefulWidget implements AutoRouteWrapper {
|
||||
State<FinancePage> createState() => _FinancePageState();
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(BuildContext context) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (_) =>
|
||||
getIt<ProfitLossLoaderBloc>()..add(ProfitLossLoaderEvent.fetched()),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) =>
|
||||
getIt<CategoryAnalyticLoaderBloc>()
|
||||
..add(CategoryAnalyticLoaderEvent.fetched()),
|
||||
),
|
||||
],
|
||||
Widget wrappedRoute(BuildContext context) => BlocProvider(
|
||||
create: (_) =>
|
||||
getIt<ProfitLossLoaderBloc>()..add(ProfitLossLoaderEvent.fetched()),
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
|
||||
class _FinancePageState extends State<FinancePage>
|
||||
with TickerProviderStateMixin {
|
||||
late AnimationController _slideController;
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _fadeController;
|
||||
late AnimationController _scaleController;
|
||||
|
||||
late Animation<Offset> _slideAnimation;
|
||||
late Animation<double> _fadeAnimation;
|
||||
late Animation<double> _scaleAnimation;
|
||||
|
||||
int _selectedTabIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_slideController = AnimationController(
|
||||
duration: const Duration(milliseconds: 800),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_fadeController = AnimationController(
|
||||
duration: const Duration(milliseconds: 1000),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_scaleController = AnimationController(
|
||||
duration: const Duration(milliseconds: 600),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_slideAnimation =
|
||||
Tween<Offset>(begin: const Offset(0, 0.3), end: Offset.zero).animate(
|
||||
CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic),
|
||||
);
|
||||
|
||||
_fadeAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeIn));
|
||||
|
||||
_scaleAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut),
|
||||
);
|
||||
|
||||
// Start animations
|
||||
_fadeController.forward();
|
||||
Future.delayed(const Duration(milliseconds: 200), () {
|
||||
_slideController.forward();
|
||||
});
|
||||
Future.delayed(const Duration(milliseconds: 400), () {
|
||||
_scaleController.forward();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_slideController.dispose();
|
||||
_fadeController.dispose();
|
||||
_scaleController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -119,89 +71,48 @@ class _FinancePageState extends State<FinancePage>
|
||||
builder: (context, state) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
// SliverAppBar with animated background
|
||||
SliverAppBar(
|
||||
expandedHeight: 120,
|
||||
floating: false,
|
||||
pinned: true,
|
||||
backgroundColor: AppColor.primary,
|
||||
elevation: 0,
|
||||
flexibleSpace: CustomAppBar(title: context.lang.profit_loss),
|
||||
),
|
||||
|
||||
// Header dengan filter periode
|
||||
// Header with gradient background, tabs, and summary
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: DateRangePickerField(
|
||||
maxDate: DateTime.now(),
|
||||
startDate: state.dateFrom,
|
||||
endDate: state.dateTo,
|
||||
onChanged: (startDate, endDate) {
|
||||
context.read<ProfitLossLoaderBloc>().add(
|
||||
ProfitLossLoaderEvent.rangeDateChanged(
|
||||
startDate!,
|
||||
endDate!,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
child: ProfitLossHeader(
|
||||
state: state,
|
||||
selectedTabIndex: _selectedTabIndex,
|
||||
onTabChanged: (index) {
|
||||
setState(() {
|
||||
_selectedTabIndex = index;
|
||||
});
|
||||
_onTabChanged(context, index);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Summary Cards
|
||||
SliverToBoxAdapter(
|
||||
child: SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: _buildSummaryCards(state.profitLoss.summary),
|
||||
),
|
||||
),
|
||||
|
||||
// Cash Flow Analysis
|
||||
SliverToBoxAdapter(
|
||||
child: ScaleTransition(
|
||||
scale: _scaleAnimation,
|
||||
child: FinanceCashFlow(dailyData: state.profitLoss.data),
|
||||
),
|
||||
),
|
||||
|
||||
// Profit Loss Detail
|
||||
// Profit Loss Report Table
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: FinanceProfitLoss(data: state.profitLoss.summary),
|
||||
child: ProfitLossReport(
|
||||
mainSummary: state.profitLoss.mainSummary,
|
||||
summary: state.profitLoss.summary,
|
||||
selectedTabIndex: _selectedTabIndex,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
BlocBuilder<
|
||||
CategoryAnalyticLoaderBloc,
|
||||
CategoryAnalyticLoaderState
|
||||
>(
|
||||
builder: (context, stateCategory) {
|
||||
return SliverToBoxAdapter(
|
||||
child: SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: FinanceCategory(
|
||||
categories: stateCategory.categoryAnalytic.data,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// Product Analysis Section
|
||||
// Cost Breakdown
|
||||
SliverToBoxAdapter(
|
||||
child: SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: _buildProductAnalysis(state.profitLoss.productData),
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: CostBreakdown(
|
||||
purchasing: state.profitLoss.purchasing,
|
||||
selectedTabIndex: _selectedTabIndex,
|
||||
dateFrom: state.dateFrom,
|
||||
dateTo: state.dateTo,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Transaction Categories
|
||||
|
||||
// Bottom spacing
|
||||
const SliverToBoxAdapter(child: SizedBox(height: 100)),
|
||||
],
|
||||
@@ -212,125 +123,23 @@ class _FinancePageState extends State<FinancePage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSummaryCards(ProfitLossSummary summary) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FinanceSummaryCard(
|
||||
title: context.lang.total_revenue,
|
||||
amount: summary.totalRevenue.currencyFormatRp,
|
||||
icon: LineIcons.arrowUp,
|
||||
color: AppColor.success,
|
||||
isPositive: true,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: FinanceSummaryCard(
|
||||
title: context.lang.total_expenditures,
|
||||
amount: summary.totalCost.currencyFormatRp,
|
||||
icon: LineIcons.arrowDown,
|
||||
color: AppColor.error,
|
||||
isPositive: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FinanceSummaryCard(
|
||||
title: context.lang.net_profit,
|
||||
amount: summary.netProfit.currencyFormatRp,
|
||||
icon: LineIcons.lineChart,
|
||||
color: AppColor.info,
|
||||
isPositive: true,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: FinanceSummaryCard(
|
||||
title: context.lang.margin_profit,
|
||||
amount: '${summary.profitabilityRatio.round()}%',
|
||||
icon: LineIcons.percent,
|
||||
color: AppColor.warning,
|
||||
isPositive: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
void _onTabChanged(BuildContext context, int index) {
|
||||
final now = DateTime.now();
|
||||
DateTime dateFrom;
|
||||
DateTime dateTo;
|
||||
|
||||
Widget _buildProductAnalysis(List<ProfitLossProductData> products) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.textLight.withOpacity(0.1),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.info.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(
|
||||
LineIcons.shoppingBag,
|
||||
color: AppColor.info,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
context.lang.product_analytic,
|
||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
child: Text(
|
||||
context.lang.view_all,
|
||||
style: AppStyle.sm.copyWith(color: AppColor.primary),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// Product list
|
||||
ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
itemCount: products.length,
|
||||
separatorBuilder: (context, index) => const SizedBox(height: 12),
|
||||
itemBuilder: (context, index) {
|
||||
final product = products[index];
|
||||
return ProfitLossProduct(product: product);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
if (index == 0) {
|
||||
// Today
|
||||
dateFrom = DateTime(now.year, now.month, now.day);
|
||||
dateTo = DateTime(now.year, now.month, now.day, 23, 59, 59);
|
||||
} else {
|
||||
// MTD (Month-to-Date)
|
||||
dateFrom = DateTime(now.year, now.month, 1);
|
||||
dateTo = now;
|
||||
}
|
||||
|
||||
context.read<ProfitLossLoaderBloc>().add(
|
||||
ProfitLossLoaderEvent.rangeDateChanged(dateFrom, dateTo),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,476 +0,0 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
|
||||
class FinanceCashFlow extends StatelessWidget {
|
||||
final List<ProfitLossDailyData> dailyData;
|
||||
|
||||
const FinanceCashFlow({super.key, required this.dailyData});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Calculate totals from daily data
|
||||
final totalCashIn = _calculateTotalCashIn();
|
||||
final totalCashOut = _calculateTotalCashOut();
|
||||
final netFlow = totalCashIn - totalCashOut;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.textLight.withOpacity(0.1),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: AppColor.primaryGradient,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(
|
||||
LineIcons.areaChart,
|
||||
color: AppColor.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
context.lang.cash_flow_analysis,
|
||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(
|
||||
LineIcons.alternateExternalLink,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Cash Flow Indicators
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildCashFlowIndicator(
|
||||
context.lang.cash_in,
|
||||
_formatCurrency(totalCashIn),
|
||||
LineIcons.arrowUp,
|
||||
AppColor.success,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: _buildCashFlowIndicator(
|
||||
context.lang.cash_out,
|
||||
_formatCurrency(totalCashOut),
|
||||
LineIcons.arrowDown,
|
||||
AppColor.error,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: _buildCashFlowIndicator(
|
||||
context.lang.net_flow,
|
||||
_formatCurrency(netFlow),
|
||||
LineIcons.equals,
|
||||
AppColor.info,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// FL Chart Implementation
|
||||
Container(
|
||||
height: 200,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.background,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColor.borderLight),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.cash_flow_chart(dailyData.length),
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: dailyData.isEmpty
|
||||
? _buildEmptyChart()
|
||||
: LineChart(_buildLineChartData()),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Legend
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildChartLegend(context.lang.cash_in, AppColor.success),
|
||||
const SizedBox(width: 20),
|
||||
_buildChartLegend(context.lang.cash_out, AppColor.error),
|
||||
const SizedBox(width: 20),
|
||||
_buildChartLegend(context.lang.net_flow, AppColor.info),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
LineChartData _buildLineChartData() {
|
||||
final maxValue = _getMaxChartValue();
|
||||
final minValue = _getMinChartValue();
|
||||
|
||||
return LineChartData(
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: false,
|
||||
horizontalInterval: (maxValue / 5).roundToDouble(),
|
||||
getDrawingHorizontalLine: (value) {
|
||||
return FlLine(color: AppColor.borderLight, strokeWidth: 1);
|
||||
},
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 30,
|
||||
interval: 1,
|
||||
getTitlesWidget: (double value, TitleMeta meta) {
|
||||
final index = value.toInt();
|
||||
if (index >= 0 && index < dailyData.length) {
|
||||
final date = DateTime.parse(dailyData[index].date);
|
||||
final dayName = _getDayName(date.weekday);
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Text(
|
||||
dayName,
|
||||
style: const TextStyle(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return SideTitleWidget(meta: meta, child: Text(''));
|
||||
},
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
interval: (maxValue / 3).roundToDouble(),
|
||||
reservedSize: 42,
|
||||
getTitlesWidget: (double value, TitleMeta meta) {
|
||||
return Text(
|
||||
_formatChartValue(value),
|
||||
style: const TextStyle(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 10,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: true,
|
||||
border: Border.all(color: AppColor.borderLight),
|
||||
),
|
||||
minX: 0,
|
||||
maxX: (dailyData.length - 1).toDouble(),
|
||||
minY: minValue,
|
||||
maxY: maxValue,
|
||||
lineBarsData: [
|
||||
// Cash In Line (Revenue)
|
||||
LineChartBarData(
|
||||
spots: _buildCashInSpots(),
|
||||
isCurved: true,
|
||||
gradient: LinearGradient(
|
||||
colors: [AppColor.success.withOpacity(0.8), AppColor.success],
|
||||
),
|
||||
barWidth: 3,
|
||||
isStrokeCapRound: true,
|
||||
dotData: FlDotData(
|
||||
show: true,
|
||||
getDotPainter: (spot, percent, barData, index) {
|
||||
return FlDotCirclePainter(
|
||||
radius: 4,
|
||||
color: AppColor.success,
|
||||
strokeWidth: 2,
|
||||
strokeColor: AppColor.white,
|
||||
);
|
||||
},
|
||||
),
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
AppColor.success.withOpacity(0.1),
|
||||
AppColor.success.withOpacity(0.0),
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
),
|
||||
// Cash Out Line (Total Cost)
|
||||
LineChartBarData(
|
||||
spots: _buildCashOutSpots(),
|
||||
isCurved: true,
|
||||
gradient: LinearGradient(
|
||||
colors: [AppColor.error.withOpacity(0.8), AppColor.error],
|
||||
),
|
||||
barWidth: 3,
|
||||
isStrokeCapRound: true,
|
||||
dotData: FlDotData(
|
||||
show: true,
|
||||
getDotPainter: (spot, percent, barData, index) {
|
||||
return FlDotCirclePainter(
|
||||
radius: 4,
|
||||
color: AppColor.error,
|
||||
strokeWidth: 2,
|
||||
strokeColor: AppColor.white,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
// Net Flow Line (Net Profit)
|
||||
LineChartBarData(
|
||||
spots: _buildNetFlowSpots(),
|
||||
isCurved: true,
|
||||
gradient: LinearGradient(
|
||||
colors: [AppColor.info.withOpacity(0.8), AppColor.info],
|
||||
),
|
||||
barWidth: 3,
|
||||
isStrokeCapRound: true,
|
||||
dotData: FlDotData(
|
||||
show: true,
|
||||
getDotPainter: (spot, percent, barData, index) {
|
||||
return FlDotCirclePainter(
|
||||
radius: 4,
|
||||
color: AppColor.info,
|
||||
strokeWidth: 2,
|
||||
strokeColor: AppColor.white,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyChart() {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
LineIcons.lineChart,
|
||||
size: 48,
|
||||
color: AppColor.textSecondary.withOpacity(0.3),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Tidak ada data untuk ditampilkan',
|
||||
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Helper methods for calculating data
|
||||
int _calculateTotalCashIn() {
|
||||
return dailyData.fold(0, (sum, data) => sum + data.revenue);
|
||||
}
|
||||
|
||||
int _calculateTotalCashOut() {
|
||||
return dailyData.fold(
|
||||
0,
|
||||
(sum, data) => sum + data.cost + data.tax + data.discount,
|
||||
);
|
||||
}
|
||||
|
||||
double _getMaxChartValue() {
|
||||
if (dailyData.isEmpty) return 30000000;
|
||||
|
||||
final maxRevenue = dailyData
|
||||
.map((e) => e.revenue)
|
||||
.reduce((a, b) => a > b ? a : b);
|
||||
final maxCost = dailyData
|
||||
.map((e) => e.cost + e.tax + e.discount)
|
||||
.reduce((a, b) => a > b ? a : b);
|
||||
final maxValue = maxRevenue > maxCost ? maxRevenue : maxCost;
|
||||
|
||||
return (maxValue * 1.2).toDouble(); // Add 20% padding
|
||||
}
|
||||
|
||||
double _getMinChartValue() {
|
||||
if (dailyData.isEmpty) return -5000000;
|
||||
|
||||
final minNetProfit = dailyData
|
||||
.map((e) => e.netProfit)
|
||||
.reduce((a, b) => a < b ? a : b);
|
||||
return minNetProfit < 0 ? (minNetProfit * 1.2).toDouble() : 0;
|
||||
}
|
||||
|
||||
List<FlSpot> _buildCashInSpots() {
|
||||
return dailyData.asMap().entries.map((entry) {
|
||||
return FlSpot(entry.key.toDouble(), entry.value.revenue.toDouble());
|
||||
}).toList();
|
||||
}
|
||||
|
||||
List<FlSpot> _buildCashOutSpots() {
|
||||
return dailyData.asMap().entries.map((entry) {
|
||||
final totalCost =
|
||||
entry.value.cost + entry.value.tax + entry.value.discount;
|
||||
return FlSpot(entry.key.toDouble(), totalCost.toDouble());
|
||||
}).toList();
|
||||
}
|
||||
|
||||
List<FlSpot> _buildNetFlowSpots() {
|
||||
return dailyData.asMap().entries.map((entry) {
|
||||
return FlSpot(entry.key.toDouble(), entry.value.netProfit.toDouble());
|
||||
}).toList();
|
||||
}
|
||||
|
||||
String _getDayName(int weekday) {
|
||||
switch (weekday) {
|
||||
case 1:
|
||||
return 'Sen';
|
||||
case 2:
|
||||
return 'Sel';
|
||||
case 3:
|
||||
return 'Rab';
|
||||
case 4:
|
||||
return 'Kam';
|
||||
case 5:
|
||||
return 'Jum';
|
||||
case 6:
|
||||
return 'Sab';
|
||||
case 7:
|
||||
return 'Min';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String _formatChartValue(double value) {
|
||||
if (value.abs() >= 1000000) {
|
||||
return '${(value / 1000000).toStringAsFixed(0)}M';
|
||||
} else if (value.abs() >= 1000) {
|
||||
return '${(value / 1000).toStringAsFixed(0)}K';
|
||||
} else {
|
||||
return value.toStringAsFixed(0);
|
||||
}
|
||||
}
|
||||
|
||||
String _formatCurrency(int amount) {
|
||||
if (amount.abs() >= 1000000000) {
|
||||
return 'Rp ${(amount / 1000000000).toStringAsFixed(1)}B';
|
||||
} else if (amount.abs() >= 1000000) {
|
||||
return 'Rp ${(amount / 1000000).toStringAsFixed(1)}M';
|
||||
} else if (amount.abs() >= 1000) {
|
||||
return 'Rp ${(amount / 1000).toStringAsFixed(1)}K';
|
||||
} else {
|
||||
return 'Rp ${NumberFormat('#,###', 'id_ID').format(amount)}';
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildChartLegend(String label, Color color) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 12,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
label,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCashFlowIndicator(
|
||||
String label,
|
||||
String amount,
|
||||
IconData icon,
|
||||
Color color,
|
||||
) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: color.withOpacity(0.2)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(icon, color: color, size: 20),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
label,
|
||||
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
amount,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
import '../../../components/widgets/empty_widget.dart';
|
||||
|
||||
class FinanceCategory extends StatelessWidget {
|
||||
final List<CategoryAnalyticItem> categories;
|
||||
|
||||
const FinanceCategory({super.key, required this.categories});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final totalRevenue = _calculateTotalRevenue();
|
||||
final sortedCategories = _sortCategoriesByRevenue();
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.textLight.withOpacity(0.1),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.secondary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(
|
||||
LineIcons.pieChart,
|
||||
color: AppColor.secondary,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
context.lang.sales_category,
|
||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Show empty state if no categories
|
||||
if (categories.isEmpty)
|
||||
_buildEmptyState(context)
|
||||
else
|
||||
...sortedCategories.asMap().entries.map(
|
||||
(entry) => _buildCategoryItem(
|
||||
context,
|
||||
entry.value,
|
||||
_calculatePercentage(entry.value.totalRevenue, totalRevenue),
|
||||
_getCategoryColor(entry.key),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCategoryItem(
|
||||
BuildContext context,
|
||||
CategoryAnalyticItem category,
|
||||
double percentage,
|
||||
Color color,
|
||||
) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 12,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
category.categoryName,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'${category.productCount} ${context.lang.product} • ${category.orderCount} ${context.lang.orders}',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
category.totalRevenue.currencyFormatRp,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${NumberFormat('#,###', 'id_ID').format(category.totalQuantity)} ${context.lang.unit}',
|
||||
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LinearProgressIndicator(
|
||||
value: percentage / 100,
|
||||
backgroundColor: AppColor.borderLight,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(color),
|
||||
minHeight: 6,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
'${percentage.toStringAsFixed(1)}%',
|
||||
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState(BuildContext context) {
|
||||
return EmptyWidget(
|
||||
title: context.lang.category_no_data,
|
||||
message: context.lang.category_no_data_desc,
|
||||
);
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
int _calculateTotalRevenue() {
|
||||
return categories.fold(0, (sum, category) => sum + category.totalRevenue);
|
||||
}
|
||||
|
||||
List<CategoryAnalyticItem> _sortCategoriesByRevenue() {
|
||||
final sorted = List<CategoryAnalyticItem>.from(categories);
|
||||
sorted.sort((a, b) => b.totalRevenue.compareTo(a.totalRevenue));
|
||||
return sorted;
|
||||
}
|
||||
|
||||
double _calculatePercentage(int categoryRevenue, int totalRevenue) {
|
||||
if (totalRevenue == 0) return 0;
|
||||
return (categoryRevenue / totalRevenue) * 100;
|
||||
}
|
||||
|
||||
Color _getCategoryColor(int index) {
|
||||
// Predefined color palette for categories
|
||||
const colors = [
|
||||
AppColor.primary,
|
||||
AppColor.secondary,
|
||||
AppColor.success,
|
||||
AppColor.warning,
|
||||
AppColor.error,
|
||||
AppColor.info,
|
||||
];
|
||||
|
||||
// Generate additional colors if needed
|
||||
if (index < colors.length) {
|
||||
return colors[index];
|
||||
} else {
|
||||
// Generate colors based on index for unlimited categories
|
||||
final hue = (index * 137.5) % 360; // Golden angle approximation
|
||||
return HSLColor.fromAHSL(1.0, hue, 0.7, 0.5).toColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
|
||||
class CostBreakdown extends StatelessWidget {
|
||||
final ProfitLossPurchasing purchasing;
|
||||
final int selectedTabIndex;
|
||||
final DateTime dateFrom;
|
||||
final DateTime dateTo;
|
||||
|
||||
const CostBreakdown({
|
||||
super.key,
|
||||
required this.purchasing,
|
||||
required this.selectedTabIndex,
|
||||
required this.dateFrom,
|
||||
required this.dateTo,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isToday = selectedTabIndex == 0;
|
||||
final total = isToday ? purchasing.todayTotal : purchasing.mtdTotal;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.textLight.withOpacity(0.08),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Title row
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.cost_breakdown,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_formatDateLabel(dateFrom, dateTo),
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Item list
|
||||
...purchasing.items.map((item) => _buildItemRow(item)),
|
||||
|
||||
// Total row
|
||||
const Divider(height: 24, color: AppColor.borderLight),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.total_cost,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
total.currencyFormatRp,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItemRow(ProfitLossPurchasingItem item) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.item,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
item.nominal.currencyFormatRp,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDateLabel(DateTime from, DateTime to) {
|
||||
const months = [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'Mei',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Agu',
|
||||
'Sep',
|
||||
'Okt',
|
||||
'Nov',
|
||||
'Des',
|
||||
];
|
||||
|
||||
if (from.year == to.year && from.month == to.month && from.day == to.day) {
|
||||
return '${from.day} ${months[from.month - 1]} ${from.year}';
|
||||
}
|
||||
return '${from.day} ${months[from.month - 1]} - ${to.day} ${months[to.month - 1]} ${to.year}';
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
|
||||
class ProfitLossProduct extends StatelessWidget {
|
||||
final ProfitLossProductData product;
|
||||
const ProfitLossProduct({super.key, required this.product});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.background,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColor.border.withOpacity(0.5)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Product header
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
product.productName,
|
||||
style: AppStyle.md.copyWith(fontWeight: FontWeight.bold),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
product.categoryName,
|
||||
style: AppStyle.xs.copyWith(color: AppColor.primary),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'${product.quantitySold} terjual',
|
||||
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
Text(
|
||||
'${product.grossProfitMargin.toStringAsFixed(1)}%',
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: product.grossProfitMargin > 25
|
||||
? AppColor.success
|
||||
: product.grossProfitMargin > 15
|
||||
? AppColor.warning
|
||||
: AppColor.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Financial metrics
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildMetricColumn(
|
||||
context.lang.revenue,
|
||||
product.revenue.currencyFormatRp,
|
||||
AppColor.success,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildMetricColumn(
|
||||
context.lang.cost,
|
||||
product.cost.currencyFormatRp,
|
||||
AppColor.error,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildMetricColumn(
|
||||
context.lang.gross_profit,
|
||||
product.grossProfit.currencyFormatRp,
|
||||
AppColor.info,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Average metrics
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildMetricColumn(
|
||||
context.lang.average_price,
|
||||
product.averagePrice.currencyFormatRp,
|
||||
AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildMetricColumn(
|
||||
context.lang.profit_per_unit,
|
||||
product.profitPerUnit.currencyFormatRp,
|
||||
AppColor.primary,
|
||||
),
|
||||
),
|
||||
const Expanded(child: SizedBox()),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMetricColumn(String label, String value, Color color) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label, style: AppStyle.xs.copyWith(color: AppColor.textSecondary)),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
value,
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
|
||||
class FinanceProfitLoss extends StatelessWidget {
|
||||
final ProfitLossSummary data;
|
||||
|
||||
const FinanceProfitLoss({super.key, required this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.textLight.withOpacity(0.1),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.info.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(
|
||||
LineIcons.calculator,
|
||||
color: AppColor.info,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
context.lang.profit_loss_detail,
|
||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Total Revenue (Penjualan Kotor)
|
||||
_buildPLItem(
|
||||
context.lang.gross_sales,
|
||||
data.totalRevenue.currencyFormatRp,
|
||||
AppColor.success,
|
||||
true,
|
||||
),
|
||||
|
||||
// Discount (Diskon & Retur)
|
||||
_buildPLItem(
|
||||
'${context.lang.discount} & ${context.lang.return_text}',
|
||||
'- ${data.totalDiscount.currencyFormatRp}',
|
||||
AppColor.error,
|
||||
false,
|
||||
),
|
||||
|
||||
const Divider(height: 24),
|
||||
|
||||
// Net Sales (Penjualan Bersih = Total Revenue - Discount)
|
||||
_buildPLItem(
|
||||
context.lang.net_sales,
|
||||
(data.totalRevenue - data.totalDiscount).currencyFormatRp,
|
||||
AppColor.textPrimary,
|
||||
true,
|
||||
isHeader: true,
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Cost of Goods Sold (HPP)
|
||||
_buildPLItem(
|
||||
'${context.lang.cogs} (${context.lang.cost_of_goods_sold})',
|
||||
'- ${data.totalCost.currencyFormatRp}',
|
||||
AppColor.error,
|
||||
false,
|
||||
),
|
||||
|
||||
const Divider(height: 24),
|
||||
|
||||
// Gross Profit (Laba Kotor)
|
||||
_buildPLItem(
|
||||
context.lang.gross_profit,
|
||||
data.grossProfit.currencyFormatRp,
|
||||
AppColor.success,
|
||||
true,
|
||||
isHeader: true,
|
||||
showPercentage: true,
|
||||
percentage: '${data.grossProfitMargin.toStringAsFixed(1)}%',
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Operational Cost (Biaya Operasional) - calculated as difference
|
||||
_buildPLItem(
|
||||
context.lang.operating_costs,
|
||||
'- ${_calculateOperationalCost().currencyFormatRp}',
|
||||
AppColor.error,
|
||||
false,
|
||||
),
|
||||
|
||||
const Divider(height: 24),
|
||||
|
||||
// Net Profit (Laba Bersih)
|
||||
_buildPLItem(
|
||||
context.lang.net_profit,
|
||||
data.netProfit.currencyFormatRp,
|
||||
AppColor.primary,
|
||||
true,
|
||||
isHeader: true,
|
||||
showPercentage: true,
|
||||
percentage: '${data.netProfitMargin.round()}%',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPLItem(
|
||||
String title,
|
||||
String amount,
|
||||
Color color,
|
||||
bool isPositive, {
|
||||
bool isHeader = false,
|
||||
bool showPercentage = false,
|
||||
String? percentage,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: isHeader
|
||||
? AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
)
|
||||
: AppStyle.md.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
amount,
|
||||
style: isHeader
|
||||
? AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
)
|
||||
: AppStyle.md.copyWith(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
if (showPercentage && percentage != null)
|
||||
Text(
|
||||
percentage,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate operational cost as the difference between gross profit and net profit
|
||||
int _calculateOperationalCost() {
|
||||
return data.grossProfit - data.netProfit - data.totalTax;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
import '../../../../application/analytic/profit_loss_loader/profit_loss_loader_bloc.dart';
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/painter/wave_painter.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
|
||||
class ProfitLossHeader extends StatelessWidget {
|
||||
final ProfitLossLoaderState state;
|
||||
final int selectedTabIndex;
|
||||
final ValueChanged<int> onTabChanged;
|
||||
|
||||
const ProfitLossHeader({
|
||||
super.key,
|
||||
required this.state,
|
||||
required this.selectedTabIndex,
|
||||
required this.onTabChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final outletLabel = state.profitLoss.outletName.isNotEmpty
|
||||
? state.profitLoss.outletName
|
||||
: 'Semua Outlet';
|
||||
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: AppColor.primaryGradient,
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24),
|
||||
bottomRight: Radius.circular(24),
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
// Decorative circles
|
||||
Positioned(
|
||||
top: -20,
|
||||
right: -30,
|
||||
child: Container(
|
||||
width: 120,
|
||||
height: 120,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColor.textWhite.withOpacity(0.08),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 30,
|
||||
right: 20,
|
||||
child: Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColor.textWhite.withOpacity(0.05),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 10,
|
||||
left: -20,
|
||||
child: Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColor.textWhite.withOpacity(0.04),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Wave pattern
|
||||
Positioned.fill(
|
||||
child: CustomPaint(
|
||||
painter: WavePainter(
|
||||
animation: 0.0,
|
||||
color: AppColor.textWhite.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Content
|
||||
SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Back button + Title row
|
||||
Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => context.router.maybePop(),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.chevron_left_rounded,
|
||||
color: AppColor.textWhite,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SpaceWidth(12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.profit_loss,
|
||||
style: AppStyle.xl.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
outletLabel,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.75),
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 12,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(20),
|
||||
|
||||
// Tab selector (Today / MTD)
|
||||
_buildTabSelector(context),
|
||||
|
||||
const SpaceHeight(24),
|
||||
|
||||
// Profit / Loss label with date
|
||||
Text(
|
||||
context.lang.profit_loss_date(
|
||||
_formatDateLabel(state.dateFrom, state.dateTo),
|
||||
),
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.75),
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(4),
|
||||
|
||||
// Big profit/loss value
|
||||
state.isFetching
|
||||
? _buildHeaderValueShimmer()
|
||||
: Text(
|
||||
state.profitLoss.summary.netProfit.currencyFormatRp,
|
||||
style: AppStyle.h1.copyWith(
|
||||
color: state.profitLoss.summary.netProfit >= 0
|
||||
? AppColor.textWhite
|
||||
: AppColor.textWhite.withOpacity(0.7),
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 32,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
// Chips row (Omset + Total Biaya)
|
||||
state.isFetching
|
||||
? _buildHeaderChipsShimmer()
|
||||
: _buildHeaderChips(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTabSelector(BuildContext context) {
|
||||
final todayLabel = _formatTodayTabLabel(DateTime.now());
|
||||
final mtdLabel = context.lang.mtd_month(
|
||||
_getMonthName(DateTime.now().month),
|
||||
);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
border: Border.all(color: AppColor.textWhite.withOpacity(0.2)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildTab(
|
||||
label: todayLabel,
|
||||
isSelected: selectedTabIndex == 0,
|
||||
onTap: () => onTabChanged(0),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildTab(
|
||||
label: mtdLabel,
|
||||
isSelected: selectedTabIndex == 1,
|
||||
onTap: () => onTabChanged(1),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTab({
|
||||
required String label,
|
||||
required bool isSelected,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppColor.white : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(26),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
label,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: isSelected ? AppColor.textPrimary : AppColor.textWhite,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeaderValueShimmer() {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: AppColor.textWhite.withOpacity(0.3),
|
||||
highlightColor: AppColor.textWhite.withOpacity(0.6),
|
||||
child: Container(
|
||||
width: 200,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.3),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeaderChipsShimmer() {
|
||||
return Row(
|
||||
children: List.generate(
|
||||
2,
|
||||
(index) => Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Shimmer.fromColors(
|
||||
baseColor: AppColor.textWhite.withOpacity(0.15),
|
||||
highlightColor: AppColor.textWhite.withOpacity(0.3),
|
||||
child: Container(
|
||||
width: 130,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeaderChips(BuildContext context) {
|
||||
final summary = state.profitLoss.summary;
|
||||
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_buildChip(
|
||||
'${context.lang.sales} ${summary.totalRevenue.currencyFormatRp}',
|
||||
),
|
||||
_buildChip(
|
||||
'${context.lang.total_cost} ${summary.totalCost.currencyFormatRp}',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildChip(String label) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: AppColor.textWhite.withOpacity(0.25)),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatTodayTabLabel(DateTime date) {
|
||||
const months = [
|
||||
'Januari',
|
||||
'Februari',
|
||||
'Maret',
|
||||
'April',
|
||||
'Mei',
|
||||
'Juni',
|
||||
'Juli',
|
||||
'Agustus',
|
||||
'September',
|
||||
'Oktober',
|
||||
'November',
|
||||
'Desember',
|
||||
];
|
||||
return '${date.day} ${months[date.month - 1]}';
|
||||
}
|
||||
|
||||
String _formatDateLabel(DateTime from, DateTime to) {
|
||||
const months = [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'Mei',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Agu',
|
||||
'Sep',
|
||||
'Okt',
|
||||
'Nov',
|
||||
'Des',
|
||||
];
|
||||
|
||||
if (from.year == to.year && from.month == to.month && from.day == to.day) {
|
||||
return '${from.day} ${months[from.month - 1]} ${from.year}';
|
||||
}
|
||||
return '${from.day} ${months[from.month - 1]} - ${to.day} ${months[to.month - 1]} ${to.year}';
|
||||
}
|
||||
|
||||
String _getMonthName(int month) {
|
||||
const months = [
|
||||
'Januari',
|
||||
'Februari',
|
||||
'Maret',
|
||||
'April',
|
||||
'Mei',
|
||||
'Juni',
|
||||
'Juli',
|
||||
'Agustus',
|
||||
'September',
|
||||
'Oktober',
|
||||
'November',
|
||||
'Desember',
|
||||
];
|
||||
return months[month - 1];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
|
||||
class ProfitLossReport extends StatelessWidget {
|
||||
final List<ProfitLossMainSummaryItem> mainSummary;
|
||||
final ProfitLossSummary summary;
|
||||
final int selectedTabIndex;
|
||||
|
||||
const ProfitLossReport({
|
||||
super.key,
|
||||
required this.mainSummary,
|
||||
required this.summary,
|
||||
required this.selectedTabIndex,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isToday = selectedTabIndex == 0;
|
||||
final marginPct = isToday
|
||||
? summary.netProfitMargin.round()
|
||||
: summary.netProfitMargin.round();
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.textLight.withOpacity(0.08),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Title row
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.profit_loss_report,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'margin $marginPct%',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Main summary items
|
||||
...mainSummary.map((item) => _buildSummarySection(item, isToday)),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Net Profit/Loss footer
|
||||
_buildNetProfitFooter(context, isToday),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSummarySection(ProfitLossMainSummaryItem item, bool isToday) {
|
||||
final nominal = isToday ? item.todayNominal : item.mtdNominal;
|
||||
final pct = isToday ? item.todayPct : item.mtdPct;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Main item row
|
||||
_buildItemRow(
|
||||
label: item.label,
|
||||
nominal: nominal,
|
||||
pct: pct,
|
||||
isBold: item.isBold,
|
||||
isSubItem: false,
|
||||
),
|
||||
|
||||
// Sub items
|
||||
...item.subItems.map((subItem) {
|
||||
final subNominal = isToday
|
||||
? subItem.todayNominal
|
||||
: subItem.mtdNominal;
|
||||
final subPct = isToday ? subItem.todayPct : subItem.mtdPct;
|
||||
return _buildItemRow(
|
||||
label: subItem.label,
|
||||
nominal: subNominal,
|
||||
pct: subPct,
|
||||
isBold: subItem.isBold,
|
||||
isSubItem: true,
|
||||
);
|
||||
}),
|
||||
|
||||
// Divider after section (except for sub-items only sections)
|
||||
if (item.isBold)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
child: Divider(height: 1, color: AppColor.borderLight),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItemRow({
|
||||
required String label,
|
||||
required int nominal,
|
||||
required double pct,
|
||||
required bool isBold,
|
||||
required bool isSubItem,
|
||||
}) {
|
||||
final isNegative = nominal < 0;
|
||||
final displayNominal = isNegative
|
||||
? '-${nominal.abs().currencyFormatRp}'
|
||||
: nominal.currencyFormatRp;
|
||||
final pctText = '${pct.round()}%';
|
||||
|
||||
// Determine color based on context
|
||||
Color nominalColor;
|
||||
if (isBold && isNegative) {
|
||||
nominalColor = AppColor.error;
|
||||
} else if (isBold) {
|
||||
nominalColor = AppColor.textPrimary;
|
||||
} else {
|
||||
nominalColor = AppColor.textPrimary;
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: isSubItem ? 16 : 0, top: 6, bottom: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
// Label
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: Text(
|
||||
label,
|
||||
style: isBold
|
||||
? AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
)
|
||||
: AppStyle.md.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Nominal
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Text(
|
||||
displayNominal,
|
||||
textAlign: TextAlign.right,
|
||||
style: isBold
|
||||
? AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: nominalColor,
|
||||
)
|
||||
: AppStyle.md.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Percentage
|
||||
SizedBox(
|
||||
width: 48,
|
||||
child: Text(
|
||||
pctText,
|
||||
textAlign: TextAlign.right,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNetProfitFooter(BuildContext context, bool isToday) {
|
||||
final netProfit = summary.netProfit;
|
||||
final isNegative = netProfit < 0;
|
||||
final displayValue = isNegative
|
||||
? '-${netProfit.abs().currencyFormatRp}'
|
||||
: netProfit.currencyFormatRp;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: isNegative
|
||||
? AppColor.error.withOpacity(0.08)
|
||||
: AppColor.success.withOpacity(0.08),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
context.lang.net_profit_loss,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: isNegative ? AppColor.error : AppColor.success,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
displayValue,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: isNegative ? AppColor.error : AppColor.success,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/theme/theme.dart';
|
||||
|
||||
class FinanceSummaryCard extends StatelessWidget {
|
||||
const FinanceSummaryCard({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.amount,
|
||||
required this.icon,
|
||||
required this.color,
|
||||
required this.isPositive,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String amount;
|
||||
final IconData icon;
|
||||
final Color color;
|
||||
final bool isPositive;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.textLight.withOpacity(0.1),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(icon, color: color, size: 20),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
title,
|
||||
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
amount,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import 'widgets/feature.dart';
|
||||
import 'widgets/header.dart';
|
||||
import 'widgets/home_top_products.dart';
|
||||
import 'widgets/home_warnings.dart';
|
||||
import 'widgets/profit_sharing_banner.dart';
|
||||
import 'widgets/stats.dart';
|
||||
|
||||
@RoutePage()
|
||||
@@ -201,6 +202,7 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
HomeFeature(),
|
||||
HomeProfitSharingBanner(),
|
||||
HomeWarnings(),
|
||||
HomeStats(overview: state.dashboard.overview),
|
||||
HomeTopProducts(
|
||||
|
||||
@@ -99,8 +99,8 @@ class _HomeHeaderState extends State<HomeHeader>
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
AppColor.primary,
|
||||
AppColor.primary.withOpacity(0.9),
|
||||
AppColor.primaryDark,
|
||||
AppColor.primaryDark.withOpacity(0.9),
|
||||
AppColor.primaryLight.withOpacity(0.85),
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
|
||||
@@ -17,8 +17,16 @@ class HeaderDateFilter extends StatelessWidget {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white.withOpacity(0.15),
|
||||
color: AppColor.primary.withOpacity(0.8),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColor.white.withOpacity(0.3)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.15),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -39,11 +47,22 @@ class HeaderDateFilter extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
duration: const Duration(milliseconds: 220),
|
||||
curve: Curves.easeOutCubic,
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppColor.white : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
boxShadow: isSelected
|
||||
? [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.18),
|
||||
blurRadius: 8,
|
||||
spreadRadius: 0,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
|
||||
@@ -18,9 +18,17 @@ class HeaderOutletSelector extends StatelessWidget {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white.withOpacity(0.15),
|
||||
color: AppColor.primary.withOpacity(0.8),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColor.white.withOpacity(0.3)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.15),
|
||||
blurRadius: 10,
|
||||
spreadRadius: 0,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
|
||||
@@ -34,85 +34,202 @@ class HeaderSummaryCard extends StatelessWidget {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: AppColor.white.withOpacity(0.2)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Top: Icon + Title + Percentage + Chevron
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: iconColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Icon(icon, color: Colors.white, size: 20),
|
||||
),
|
||||
const SpaceWidth(10),
|
||||
Text(
|
||||
title,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
if (percentage != null) _buildPercentageBadge(),
|
||||
const SpaceWidth(6),
|
||||
Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: AppColor.white.withOpacity(0.7),
|
||||
size: 20,
|
||||
),
|
||||
],
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
gradient: const LinearGradient(
|
||||
colors: [
|
||||
Color(0xFFE8000A), // sedikit lebih terang di atas
|
||||
Color(0xFFC40202), // primary di tengah
|
||||
Color(0xFF990000), // gelap di bawah
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
stops: [0.0, 0.5, 1.0],
|
||||
),
|
||||
boxShadow: [
|
||||
// Shadow merah di bawah — efek floating/3D
|
||||
BoxShadow(
|
||||
color: const Color(0xFFC40202).withOpacity(0.55),
|
||||
blurRadius: 16,
|
||||
spreadRadius: 0,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
|
||||
const SpaceHeight(12),
|
||||
|
||||
// Value (hidden or visible)
|
||||
isValueVisible
|
||||
? Text(
|
||||
value.currencyFormatRp,
|
||||
style: AppStyle.h1.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 26,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
'Rp ••••••',
|
||||
style: AppStyle.h1.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 26,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(4),
|
||||
|
||||
// Subtitle
|
||||
Text(
|
||||
subtitle,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.white.withOpacity(0.7),
|
||||
fontWeight: FontWeight.w400,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
// Inner highlight di atas kiri (simulasi cahaya)
|
||||
BoxShadow(
|
||||
color: AppColor.white.withOpacity(0.08),
|
||||
blurRadius: 0,
|
||||
spreadRadius: -1,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
|
||||
// Mini bar chart
|
||||
_buildMiniBarChart(),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
child: Stack(
|
||||
children: [
|
||||
// Top highlight strip — efek glossy
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
height: 1.5,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
AppColor.white.withOpacity(0.0),
|
||||
AppColor.white.withOpacity(0.35),
|
||||
AppColor.white.withOpacity(0.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Decorative circles
|
||||
Positioned(
|
||||
top: -30,
|
||||
right: -30,
|
||||
child: Container(
|
||||
width: 120,
|
||||
height: 120,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColor.white.withOpacity(0.07),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: -20,
|
||||
right: 20,
|
||||
child: Container(
|
||||
width: 70,
|
||||
height: 70,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColor.white.withOpacity(0.05),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 40,
|
||||
left: -20,
|
||||
child: Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColor.white.withOpacity(0.04),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Content
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Top: Icon + Title + Percentage + Chevron
|
||||
Row(
|
||||
children: [
|
||||
// Icon dengan shadow
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [iconColor.withOpacity(0.9), iconColor],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: iconColor.withOpacity(0.5),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Icon(icon, color: Colors.white, size: 20),
|
||||
),
|
||||
const SpaceWidth(10),
|
||||
Text(
|
||||
title,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
if (percentage != null) _buildPercentageBadge(),
|
||||
const SpaceWidth(6),
|
||||
Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: AppColor.white.withOpacity(0.7),
|
||||
size: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(12),
|
||||
|
||||
// Value
|
||||
isValueVisible
|
||||
? Text(
|
||||
value.currencyFormatRp,
|
||||
style: AppStyle.h1.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 26,
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Colors.black.withOpacity(0.25),
|
||||
blurRadius: 6,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
'Rp ••••••',
|
||||
style: AppStyle.h1.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 26,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(4),
|
||||
|
||||
// Subtitle
|
||||
Text(
|
||||
subtitle,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.white.withOpacity(0.7),
|
||||
fontWeight: FontWeight.w400,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
|
||||
// Mini bar chart
|
||||
_buildMiniBarChart(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -126,6 +243,12 @@ class HeaderSummaryCard extends StatelessWidget {
|
||||
? const Color(0xFF4CAF50).withOpacity(0.25)
|
||||
: const Color(0xFFE53E3E).withOpacity(0.25),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: isPositive
|
||||
? const Color(0xFF4CAF50).withOpacity(0.4)
|
||||
: const Color(0xFFE53E3E).withOpacity(0.4),
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -162,18 +285,31 @@ class HeaderSummaryCard extends StatelessWidget {
|
||||
.fold<int>(0, (a, b) => a > b ? a : b);
|
||||
|
||||
return SizedBox(
|
||||
height: 24,
|
||||
height: 28,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: dailyData.map((d) {
|
||||
final ratio = maxVal > 0 ? d.totalCost / maxVal : 0.0;
|
||||
final isMax = maxVal > 0 && d.totalCost == maxVal;
|
||||
return Expanded(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 1.5),
|
||||
height: 6 + (18 * ratio),
|
||||
height: 6 + (22 * ratio),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white.withOpacity(0.4),
|
||||
// Bar tertinggi lebih terang
|
||||
color: isMax
|
||||
? AppColor.white.withOpacity(0.75)
|
||||
: AppColor.white.withOpacity(0.35),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
boxShadow: isMax
|
||||
? [
|
||||
BoxShadow(
|
||||
color: AppColor.white.withOpacity(0.3),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, -2),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hugeicons/hugeicons.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
import '../../../router/app_router.gr.dart';
|
||||
|
||||
/// Pintu masuk ke halaman bagi hasil dari beranda.
|
||||
class HomeProfitSharingBanner extends StatelessWidget {
|
||||
const HomeProfitSharingBanner({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(AppValue.radius),
|
||||
onTap: () => context.router.push(const ProfitSharingRoute()),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: AppColor.primaryGradient,
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(AppValue.radius),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.primary.withOpacity(0.25),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.18),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const HugeIcon(
|
||||
icon: HugeIcons.strokeRoundedPieChart,
|
||||
color: AppColor.textWhite,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.profit_sharing,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
context.lang.profit_sharing_desc,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.8),
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
const Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: AppColor.textWhite,
|
||||
size: 24,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ class HomeStats extends StatelessWidget {
|
||||
icon: Icons.hexagon_outlined,
|
||||
iconColor: const Color(0xFF4CAF50),
|
||||
blobColor: const Color(0xFF4CAF50),
|
||||
value: '0', // TODO: connect items sold data
|
||||
value: overview.totalItemSold.toString(),
|
||||
label: context.lang.items_sold,
|
||||
),
|
||||
),
|
||||
@@ -58,7 +58,7 @@ class HomeStats extends StatelessWidget {
|
||||
icon: Icons.warning_amber_rounded,
|
||||
iconColor: const Color(0xFFFF9800),
|
||||
blobColor: const Color(0xFFFF9800),
|
||||
value: '0', // TODO: connect low stock data
|
||||
value: overview.totalLowStock.toString(),
|
||||
label: context.lang.low_stock_warning,
|
||||
),
|
||||
),
|
||||
@@ -68,7 +68,7 @@ class HomeStats extends StatelessWidget {
|
||||
icon: Icons.hexagon_outlined,
|
||||
iconColor: const Color(0xFFE53935),
|
||||
blobColor: const Color(0xFFE53935),
|
||||
value: '0', // TODO: connect active products data
|
||||
value: overview.totalProductActive.toString(),
|
||||
label: context.lang.active_products,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -3,17 +3,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../application/analytic/inventory_analytic_loader/inventory_analytic_loader_bloc.dart';
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../../domain/analytic/analytic.dart';
|
||||
import '../../../injection.dart';
|
||||
import '../../components/appbar/appbar.dart';
|
||||
import 'widgets/ingredient_tile.dart';
|
||||
import 'widgets/product_tile.dart';
|
||||
import 'widgets/stat_card.dart';
|
||||
import 'widgets/tabbar_delegate.dart';
|
||||
|
||||
// Custom SliverPersistentHeaderDelegate untuk TabBar
|
||||
import '../../components/spacer/spacer.dart';
|
||||
import 'widgets/inventory_header.dart';
|
||||
import 'widgets/inventory_stock_report.dart';
|
||||
|
||||
@RoutePage()
|
||||
class InventoryPage extends StatefulWidget implements AutoRouteWrapper {
|
||||
@@ -32,400 +26,187 @@ class InventoryPage extends StatefulWidget implements AutoRouteWrapper {
|
||||
}
|
||||
|
||||
class _InventoryPageState extends State<InventoryPage>
|
||||
with TickerProviderStateMixin {
|
||||
late AnimationController _fadeAnimationController;
|
||||
late AnimationController _slideAnimationController;
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _fadeController;
|
||||
late Animation<double> _fadeAnimation;
|
||||
late Animation<Offset> _slideAnimation;
|
||||
late TabController _tabController;
|
||||
|
||||
int _selectedTabIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 2, vsync: this);
|
||||
|
||||
_fadeAnimationController = AnimationController(
|
||||
_fadeController = AnimationController(
|
||||
duration: const Duration(milliseconds: 1000),
|
||||
vsync: this,
|
||||
);
|
||||
_slideAnimationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 800),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(
|
||||
parent: _fadeAnimationController,
|
||||
curve: Curves.easeInOut,
|
||||
),
|
||||
);
|
||||
_fadeAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeIn));
|
||||
|
||||
_slideAnimation =
|
||||
Tween<Offset>(begin: const Offset(0.0, 0.3), end: Offset.zero).animate(
|
||||
CurvedAnimation(
|
||||
parent: _slideAnimationController,
|
||||
curve: Curves.easeOutBack,
|
||||
),
|
||||
);
|
||||
|
||||
_fadeAnimationController.forward();
|
||||
_slideAnimationController.forward();
|
||||
_fadeController.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_fadeAnimationController.dispose();
|
||||
_slideAnimationController.dispose();
|
||||
_tabController.dispose();
|
||||
_fadeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Color getStatusColor(String status) {
|
||||
switch (status) {
|
||||
case 'available':
|
||||
return AppColor.success;
|
||||
case 'low_stock':
|
||||
return AppColor.warning;
|
||||
case 'out_of_stock':
|
||||
return AppColor.error;
|
||||
default:
|
||||
return AppColor.textSecondary;
|
||||
}
|
||||
}
|
||||
|
||||
String getStatusText(String status) {
|
||||
switch (status) {
|
||||
case 'available':
|
||||
return context.lang.available;
|
||||
case 'low_stock':
|
||||
return context.lang.low_stock;
|
||||
case 'out_of_stock':
|
||||
return context.lang.out_of_stock;
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<
|
||||
InventoryAnalyticLoaderBloc,
|
||||
InventoryAnalyticLoaderState
|
||||
>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.dateFrom != current.dateFrom ||
|
||||
previous.dateTo != current.dateTo,
|
||||
listener: (context, state) {
|
||||
context.read<InventoryAnalyticLoaderBloc>().add(
|
||||
InventoryAnalyticLoaderEvent.fetched(),
|
||||
);
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: AppColor.background,
|
||||
body:
|
||||
BlocBuilder<
|
||||
InventoryAnalyticLoaderBloc,
|
||||
InventoryAnalyticLoaderState
|
||||
>(
|
||||
builder: (context, state) {
|
||||
return FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: NestedScrollView(
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) {
|
||||
return [
|
||||
_buildSliverAppBar(),
|
||||
SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate: InventorySliverTabBarDelegate(
|
||||
startDate: state.dateFrom,
|
||||
endDate: state.dateTo,
|
||||
return Scaffold(
|
||||
backgroundColor: AppColor.background,
|
||||
body:
|
||||
BlocListener<
|
||||
InventoryAnalyticLoaderBloc,
|
||||
InventoryAnalyticLoaderState
|
||||
>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.dateFrom != current.dateFrom ||
|
||||
previous.dateTo != current.dateTo,
|
||||
listener: (context, state) {
|
||||
context.read<InventoryAnalyticLoaderBloc>().add(
|
||||
InventoryAnalyticLoaderEvent.fetched(),
|
||||
);
|
||||
},
|
||||
child:
|
||||
BlocBuilder<
|
||||
InventoryAnalyticLoaderBloc,
|
||||
InventoryAnalyticLoaderState
|
||||
>(
|
||||
builder: (context, state) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
// Header with gradient background, tabs, and summary
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: InventoryHeader(
|
||||
state: state,
|
||||
selectedTabIndex: _selectedTabIndex,
|
||||
onTabChanged: (index) {
|
||||
setState(() {
|
||||
_selectedTabIndex = index;
|
||||
});
|
||||
},
|
||||
onDateRangeChanged: (startDate, endDate) {
|
||||
context.read<InventoryAnalyticLoaderBloc>().add(
|
||||
InventoryAnalyticLoaderEvent.rangeDateChanged(
|
||||
startDate!,
|
||||
endDate!,
|
||||
),
|
||||
_onDateRangeChanged(
|
||||
context,
|
||||
startDate,
|
||||
endDate,
|
||||
);
|
||||
},
|
||||
tabBar: TabBar(
|
||||
controller: _tabController,
|
||||
indicator: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: AppColor.primaryGradient,
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.primary.withOpacity(0.3),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
indicatorPadding: const EdgeInsets.all(6),
|
||||
labelColor: AppColor.textWhite,
|
||||
unselectedLabelColor: AppColor.textSecondary,
|
||||
labelStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 13,
|
||||
),
|
||||
unselectedLabelStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 13,
|
||||
),
|
||||
dividerColor: Colors.transparent,
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
overlayColor: MaterialStateProperty.all(
|
||||
Colors.transparent,
|
||||
),
|
||||
tabs: [
|
||||
Tab(
|
||||
height: 40,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.inventory_2_rounded,
|
||||
size: 16,
|
||||
),
|
||||
SizedBox(width: 6),
|
||||
Text(context.lang.product),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
height: 40,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.restaurant_menu_rounded,
|
||||
size: 16,
|
||||
),
|
||||
SizedBox(width: 6),
|
||||
Text(context.lang.ingredients),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
body: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_buildProductTab(state.inventoryAnalytic),
|
||||
_buildIngredientTab(state.inventoryAnalytic),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Stock Report Table
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: state.isFetching
|
||||
? _buildLoadingReport()
|
||||
: InventoryStockReport(
|
||||
inventoryAnalytic: state.inventoryAnalytic,
|
||||
selectedTabIndex: _selectedTabIndex,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Bottom spacing
|
||||
const SliverToBoxAdapter(child: SpaceHeight(100)),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingReport() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.textLight.withOpacity(0.08),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: List.generate(
|
||||
5,
|
||||
(index) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Container(
|
||||
height: 14,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.borderLight,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SpaceWidth(12),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(
|
||||
height: 14,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.borderLight,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SpaceWidth(12),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(
|
||||
height: 14,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.borderLight,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SpaceWidth(12),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(
|
||||
height: 14,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.borderLight,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSliverAppBar() {
|
||||
return SliverAppBar(
|
||||
expandedHeight: 120,
|
||||
floating: false,
|
||||
pinned: true,
|
||||
elevation: 0,
|
||||
backgroundColor: AppColor.primary,
|
||||
flexibleSpace: CustomAppBar(title: context.lang.inventory),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProductTab(InventoryAnalytic inventoryAnalytic) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: _buildProductStats(inventoryAnalytic.summary),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) =>
|
||||
InventoryProductTile(item: inventoryAnalytic.products[index]),
|
||||
childCount: inventoryAnalytic.products.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIngredientTab(InventoryAnalytic inventoryAnalytic) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: _buildIngredientStats(inventoryAnalytic.summary),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => InventoryIngredientTile(
|
||||
item: inventoryAnalytic.ingredients[index],
|
||||
),
|
||||
childCount: inventoryAnalytic.ingredients.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProductStats(InventorySummary inventory) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
context.lang.total_products,
|
||||
inventory.totalProducts.toString(),
|
||||
Icons.inventory_2_rounded,
|
||||
AppColor.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
context.lang.total_sold,
|
||||
inventory.totalSoldProducts.toString(),
|
||||
Icons.check_circle_rounded,
|
||||
AppColor.success,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
context.lang.low_stock,
|
||||
inventory.lowStockProducts.toString(),
|
||||
Icons.warning_rounded,
|
||||
AppColor.warning,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
context.lang.zero_stock,
|
||||
inventory.zeroStockProducts.toString(),
|
||||
Icons.error_rounded,
|
||||
AppColor.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIngredientStats(InventorySummary inventory) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
context.lang.total_ingredients,
|
||||
inventory.totalIngredients.toString(),
|
||||
Icons.restaurant_menu_rounded,
|
||||
AppColor.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
context.lang.total_sold,
|
||||
inventory.totalSoldIngredients.toString(),
|
||||
Icons.check_circle_rounded,
|
||||
AppColor.success,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
context.lang.low_stock,
|
||||
inventory.lowStockIngredients.toString(),
|
||||
Icons.warning_rounded,
|
||||
AppColor.warning,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
context.lang.zero_stock,
|
||||
inventory.zeroStockIngredients.toString(),
|
||||
Icons.error_rounded,
|
||||
AppColor.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatCard(
|
||||
String title,
|
||||
String value,
|
||||
IconData icon,
|
||||
Color color,
|
||||
void _onDateRangeChanged(
|
||||
BuildContext context,
|
||||
DateTime startDate,
|
||||
DateTime endDate,
|
||||
) {
|
||||
return TweenAnimationBuilder<double>(
|
||||
tween: Tween<double>(begin: 0, end: 1),
|
||||
duration: const Duration(milliseconds: 800),
|
||||
builder: (context, animationValue, child) {
|
||||
return Transform.scale(
|
||||
scale: animationValue,
|
||||
child: InventoryStatCard(
|
||||
title: title,
|
||||
value: value,
|
||||
icon: icon,
|
||||
color: color,
|
||||
),
|
||||
);
|
||||
},
|
||||
context.read<InventoryAnalyticLoaderBloc>().add(
|
||||
InventoryAnalyticLoaderEvent.rangeDateChanged(startDate, endDate),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,414 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
import '../../../../application/analytic/inventory_analytic_loader/inventory_analytic_loader_bloc.dart';
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/painter/wave_painter.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../components/bottom_sheet/date_range_bottom_sheet.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
|
||||
class InventoryHeader extends StatelessWidget {
|
||||
final InventoryAnalyticLoaderState state;
|
||||
final int selectedTabIndex;
|
||||
final ValueChanged<int> onTabChanged;
|
||||
final void Function(DateTime startDate, DateTime endDate)? onDateRangeChanged;
|
||||
|
||||
const InventoryHeader({
|
||||
super.key,
|
||||
required this.state,
|
||||
required this.selectedTabIndex,
|
||||
required this.onTabChanged,
|
||||
this.onDateRangeChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final outletLabel = state.inventoryAnalytic.summary.outletName.isNotEmpty
|
||||
? state.inventoryAnalytic.summary.outletName
|
||||
: context.lang.all_outlets;
|
||||
final dateLabel = _formatDateRange(state.dateFrom, state.dateTo);
|
||||
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: AppColor.primaryGradient,
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24),
|
||||
bottomRight: Radius.circular(24),
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
// Decorative circles
|
||||
Positioned(
|
||||
top: -20,
|
||||
right: -30,
|
||||
child: Container(
|
||||
width: 120,
|
||||
height: 120,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColor.textWhite.withOpacity(0.08),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 30,
|
||||
right: 20,
|
||||
child: Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColor.textWhite.withOpacity(0.05),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 10,
|
||||
left: -20,
|
||||
child: Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColor.textWhite.withOpacity(0.04),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Wave pattern
|
||||
Positioned.fill(
|
||||
child: CustomPaint(
|
||||
painter: WavePainter(
|
||||
animation: 0.0,
|
||||
color: AppColor.textWhite.withOpacity(0.1),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Content
|
||||
SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Back button + Title row + Calendar button
|
||||
Row(
|
||||
children: [
|
||||
if (context.router.canPop()) ...[
|
||||
GestureDetector(
|
||||
onTap: () => context.router.maybePop(),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.chevron_left_rounded,
|
||||
color: AppColor.textWhite,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SpaceWidth(12),
|
||||
],
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.inventory,
|
||||
style: AppStyle.xl.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'$dateLabel · $outletLabel',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.75),
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 12,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
// Date filter button
|
||||
GestureDetector(
|
||||
onTap: () => _showDatePicker(context),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.calendar_month_rounded,
|
||||
color: AppColor.textWhite,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(20),
|
||||
|
||||
// Tab selector (Product / Ingredient)
|
||||
_buildTabSelector(context),
|
||||
|
||||
const SpaceHeight(24),
|
||||
|
||||
// Total Value label
|
||||
Text(
|
||||
context.lang.total_inventory_value,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.75),
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(4),
|
||||
|
||||
// Big total value
|
||||
state.isFetching
|
||||
? _buildHeaderValueShimmer()
|
||||
: Text(
|
||||
state
|
||||
.inventoryAnalytic
|
||||
.summary
|
||||
.totalValue
|
||||
.currencyFormatRp,
|
||||
style: AppStyle.h1.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 32,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
// Chips row
|
||||
state.isFetching
|
||||
? _buildHeaderChipsShimmer()
|
||||
: _buildHeaderChips(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTabSelector(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
border: Border.all(color: AppColor.textWhite.withOpacity(0.2)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildTab(
|
||||
icon: Icons.inventory_2_rounded,
|
||||
label: context.lang.product,
|
||||
isSelected: selectedTabIndex == 0,
|
||||
onTap: () => onTabChanged(0),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildTab(
|
||||
icon: Icons.restaurant_menu_rounded,
|
||||
label: context.lang.ingredients,
|
||||
isSelected: selectedTabIndex == 1,
|
||||
onTap: () => onTabChanged(1),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTab({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required bool isSelected,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppColor.white : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(26),
|
||||
),
|
||||
child: Center(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 16,
|
||||
color: isSelected ? AppColor.textPrimary : AppColor.textWhite,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
label,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: isSelected ? AppColor.textPrimary : AppColor.textWhite,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showDatePicker(BuildContext context) {
|
||||
DateRangePickerBottomSheet.show(
|
||||
context: context,
|
||||
primaryColor: AppColor.primary,
|
||||
initialStartDate: state.dateFrom,
|
||||
initialEndDate: state.dateTo,
|
||||
maxDate: DateTime.now(),
|
||||
onChanged: (startDate, endDate) {
|
||||
if (startDate != null && endDate != null) {
|
||||
onDateRangeChanged?.call(startDate, endDate);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeaderValueShimmer() {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: AppColor.textWhite.withOpacity(0.3),
|
||||
highlightColor: AppColor.textWhite.withOpacity(0.6),
|
||||
child: Container(
|
||||
width: 200,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.3),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeaderChipsShimmer() {
|
||||
return Row(
|
||||
children: List.generate(
|
||||
3,
|
||||
(index) => Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Shimmer.fromColors(
|
||||
baseColor: AppColor.textWhite.withOpacity(0.15),
|
||||
highlightColor: AppColor.textWhite.withOpacity(0.3),
|
||||
child: Container(
|
||||
width: 90,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeaderChips(BuildContext context) {
|
||||
final summary = state.inventoryAnalytic.summary;
|
||||
|
||||
if (selectedTabIndex == 0) {
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_buildChip('${summary.totalProducts} ${context.lang.product}'),
|
||||
_buildChip('${summary.lowStockProducts} ${context.lang.low_stock}'),
|
||||
_buildChip('${summary.zeroStockProducts} ${context.lang.zero_stock}'),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_buildChip('${summary.totalIngredients} ${context.lang.ingredients}'),
|
||||
_buildChip(
|
||||
'${summary.lowStockIngredients} ${context.lang.low_stock}',
|
||||
),
|
||||
_buildChip(
|
||||
'${summary.zeroStockIngredients} ${context.lang.zero_stock}',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildChip(String label) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: AppColor.textWhite.withOpacity(0.25)),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDateRange(DateTime from, DateTime to) {
|
||||
const months = [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'Mei',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Agu',
|
||||
'Sep',
|
||||
'Okt',
|
||||
'Nov',
|
||||
'Des',
|
||||
];
|
||||
|
||||
if (from.year == to.year && from.month == to.month && from.day == to.day) {
|
||||
return '${from.day} ${months[from.month - 1]} ${from.year}';
|
||||
}
|
||||
return '${from.day} ${months[from.month - 1]} - ${to.day} ${months[to.month - 1]} ${to.year}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,638 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
|
||||
class InventoryStockReport extends StatelessWidget {
|
||||
final InventoryAnalytic inventoryAnalytic;
|
||||
final int selectedTabIndex;
|
||||
|
||||
const InventoryStockReport({
|
||||
super.key,
|
||||
required this.inventoryAnalytic,
|
||||
required this.selectedTabIndex,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.textLight.withOpacity(0.08),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Title row
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
selectedTabIndex == 0
|
||||
? context.lang.product
|
||||
: context.lang.ingredients,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
selectedTabIndex == 0
|
||||
? '${inventoryAnalytic.products.length} item'
|
||||
: '${inventoryAnalytic.ingredients.length} item',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
// Summary stats row
|
||||
_buildSummaryStats(context),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
const Divider(height: 1, color: AppColor.borderLight),
|
||||
|
||||
const SpaceHeight(12),
|
||||
|
||||
// Table header
|
||||
_buildTableHeader(context),
|
||||
|
||||
const SpaceHeight(8),
|
||||
|
||||
// Items list
|
||||
if (selectedTabIndex == 0)
|
||||
...inventoryAnalytic.products.map(
|
||||
(product) => _buildProductRow(context, product),
|
||||
)
|
||||
else
|
||||
...inventoryAnalytic.ingredients.map(
|
||||
(ingredient) => _buildIngredientRow(context, ingredient),
|
||||
),
|
||||
|
||||
const SpaceHeight(12),
|
||||
|
||||
// Footer totals
|
||||
_buildFooterTotals(context),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSummaryStats(BuildContext context) {
|
||||
final summary = inventoryAnalytic.summary;
|
||||
|
||||
if (selectedTabIndex == 0) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildMiniStat(
|
||||
context.lang.total_sold,
|
||||
summary.totalSoldProducts.toString(),
|
||||
AppColor.success,
|
||||
Icons.check_circle_rounded,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
Expanded(
|
||||
child: _buildMiniStat(
|
||||
context.lang.low_stock,
|
||||
summary.lowStockProducts.toString(),
|
||||
AppColor.warning,
|
||||
Icons.warning_rounded,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
Expanded(
|
||||
child: _buildMiniStat(
|
||||
context.lang.zero_stock,
|
||||
summary.zeroStockProducts.toString(),
|
||||
AppColor.error,
|
||||
Icons.error_rounded,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildMiniStat(
|
||||
context.lang.total_sold,
|
||||
summary.totalSoldIngredients.toString(),
|
||||
AppColor.success,
|
||||
Icons.check_circle_rounded,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
Expanded(
|
||||
child: _buildMiniStat(
|
||||
context.lang.low_stock,
|
||||
summary.lowStockIngredients.toString(),
|
||||
AppColor.warning,
|
||||
Icons.warning_rounded,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
Expanded(
|
||||
child: _buildMiniStat(
|
||||
context.lang.zero_stock,
|
||||
summary.zeroStockIngredients.toString(),
|
||||
AppColor.error,
|
||||
Icons.error_rounded,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildMiniStat(
|
||||
String label,
|
||||
String value,
|
||||
Color color,
|
||||
IconData icon,
|
||||
) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.08),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(icon, size: 18, color: color),
|
||||
const SpaceHeight(4),
|
||||
Text(
|
||||
value,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
const SpaceHeight(2),
|
||||
Text(
|
||||
label,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: color.withOpacity(0.8),
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 10,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTableHeader(BuildContext context) {
|
||||
if (selectedTabIndex == 0) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Text(
|
||||
context.lang.product,
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
context.lang.stock,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
context.lang.in_text,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
context.lang.out_text,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Text(
|
||||
context.lang.ingredients,
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
context.lang.stock,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
context.lang.in_text,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
context.lang.out_text,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildProductRow(BuildContext context, InventoryProduct product) {
|
||||
final statusColor = product.isZeroStock
|
||||
? AppColor.error
|
||||
: product.isLowStock
|
||||
? AppColor.warning
|
||||
: AppColor.textPrimary;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(color: AppColor.borderLight, width: 0.5),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Product name + category
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
product.productName,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SpaceHeight(2),
|
||||
Row(
|
||||
children: [
|
||||
if (product.isZeroStock || product.isLowStock)
|
||||
Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
margin: const EdgeInsets.only(right: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
product.categoryName,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Stock
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
NumberFormat('#,###', 'id_ID').format(product.quantity),
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: statusColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// In
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
product.totalIn > 0
|
||||
? '+${NumberFormat('#,###', 'id_ID').format(product.totalIn)}'
|
||||
: '-',
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: product.totalIn > 0
|
||||
? AppColor.success
|
||||
: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Out
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
product.totalOut > 0
|
||||
? '-${NumberFormat('#,###', 'id_ID').format(product.totalOut)}'
|
||||
: '-',
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: product.totalOut > 0
|
||||
? AppColor.error
|
||||
: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIngredientRow(
|
||||
BuildContext context,
|
||||
InventoryIngredient ingredient,
|
||||
) {
|
||||
final statusColor = ingredient.isZeroStock
|
||||
? AppColor.error
|
||||
: ingredient.isLowStock
|
||||
? AppColor.warning
|
||||
: AppColor.textPrimary;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(color: AppColor.borderLight, width: 0.5),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Ingredient name + unit
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
ingredient.ingredientName,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SpaceHeight(2),
|
||||
Row(
|
||||
children: [
|
||||
if (ingredient.isZeroStock || ingredient.isLowStock)
|
||||
Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
margin: const EdgeInsets.only(right: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
ingredient.unitName,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Stock
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
NumberFormat('#,###', 'id_ID').format(ingredient.quantity),
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: statusColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// In
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
ingredient.totalIn > 0
|
||||
? '+${NumberFormat('#,###', 'id_ID').format(ingredient.totalIn)}'
|
||||
: '-',
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ingredient.totalIn > 0
|
||||
? AppColor.success
|
||||
: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Out
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
ingredient.totalOut > 0
|
||||
? '-${NumberFormat('#,###', 'id_ID').format(ingredient.totalOut)}'
|
||||
: '-',
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ingredient.totalOut > 0
|
||||
? AppColor.error
|
||||
: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFooterTotals(BuildContext context) {
|
||||
int totalStock;
|
||||
int totalIn;
|
||||
int totalOut;
|
||||
|
||||
if (selectedTabIndex == 0) {
|
||||
totalStock = inventoryAnalytic.products.fold<int>(
|
||||
0,
|
||||
(sum, item) => sum + item.quantity,
|
||||
);
|
||||
totalIn = inventoryAnalytic.products.fold<int>(
|
||||
0,
|
||||
(sum, item) => sum + item.totalIn,
|
||||
);
|
||||
totalOut = inventoryAnalytic.products.fold<int>(
|
||||
0,
|
||||
(sum, item) => sum + item.totalOut,
|
||||
);
|
||||
} else {
|
||||
totalStock = inventoryAnalytic.ingredients.fold<int>(
|
||||
0,
|
||||
(sum, item) => sum + item.quantity,
|
||||
);
|
||||
totalIn = inventoryAnalytic.ingredients.fold<int>(
|
||||
0,
|
||||
(sum, item) => sum + item.totalIn,
|
||||
);
|
||||
totalOut = inventoryAnalytic.ingredients.fold<int>(
|
||||
0,
|
||||
(sum, item) => sum + item.totalOut,
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.08),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Text(
|
||||
'TOTAL',
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
NumberFormat('#,###', 'id_ID').format(totalStock),
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
'+${NumberFormat('#,###', 'id_ID').format(totalIn)}',
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.success,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
'-${NumberFormat('#,###', 'id_ID').format(totalOut)}',
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,13 @@ class MainPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AutoTabsRouter.pageView(
|
||||
routes: [HomeRoute(), OrderRoute(), ReportRoute(), ProfileRoute()],
|
||||
routes: [
|
||||
HomeRoute(),
|
||||
OrderRoute(),
|
||||
ExclusiveSummaryRoute(),
|
||||
InventoryRoute(),
|
||||
ProfileRoute(),
|
||||
],
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
builder: (context, child, pageController) {
|
||||
final tabsRouter = AutoTabsRouter.of(context);
|
||||
|
||||
@@ -40,6 +40,11 @@ class _MainBottomNavbarState extends State<MainBottomNavbar> {
|
||||
label: context.lang.report,
|
||||
tooltip: context.lang.report,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: HugeIcon(icon: HugeIcons.strokeRoundedPackage),
|
||||
label: context.lang.stock,
|
||||
tooltip: context.lang.stock,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: HugeIcon(icon: HugeIcons.strokeRoundedUser),
|
||||
label: context.lang.profile,
|
||||
|
||||
@@ -49,7 +49,7 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
// Filter state
|
||||
final List<String> filterOptions = ['All', 'Completed', 'Pending'];
|
||||
final List<String> filterOptions = ['All', 'Pending', 'Completed'];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
||||
@@ -43,23 +43,6 @@ class ProfileAccountInfo extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
|
||||
ProfileTile(
|
||||
icon: LineIcons.envelope,
|
||||
title: context.lang.email,
|
||||
subtitle: user.email,
|
||||
showArrow: false,
|
||||
),
|
||||
|
||||
ProfileDivider(),
|
||||
|
||||
ProfileTile(
|
||||
icon: LineIcons.calendarAlt,
|
||||
title: context.lang.member_since,
|
||||
subtitle: user.createdAt.toDate,
|
||||
showArrow: false,
|
||||
),
|
||||
ProfileDivider(),
|
||||
|
||||
ProfileTile(
|
||||
icon: LineIcons.userEdit,
|
||||
title: context.lang.edit_profile,
|
||||
|
||||
@@ -46,24 +46,6 @@ class ProfileBusinessSetting extends StatelessWidget {
|
||||
subtitle: context.lang.outlet_informatio_desc,
|
||||
onTap: () => context.router.push(OutletInformationRoute()),
|
||||
),
|
||||
|
||||
ProfileDivider(),
|
||||
|
||||
ProfileTile(
|
||||
icon: Icons.people_outline,
|
||||
title: context.lang.staff_management,
|
||||
subtitle: context.lang.staff_management_desc,
|
||||
onTap: () => context.router.push(ComingSoonRoute()),
|
||||
),
|
||||
|
||||
ProfileDivider(),
|
||||
|
||||
ProfileTile(
|
||||
icon: Icons.inventory_2_outlined,
|
||||
title: context.lang.products,
|
||||
subtitle: context.lang.manage_your_products,
|
||||
onTap: () => context.router.push(ProductRoute()),
|
||||
),
|
||||
ProfileDivider(),
|
||||
|
||||
ProfileTile(
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
import '../../../application/analytic/profit_sharing_detail_loader/profit_sharing_detail_loader_bloc.dart';
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../../domain/analytic/analytic.dart';
|
||||
import '../../../injection.dart';
|
||||
import '../../components/spacer/spacer.dart';
|
||||
import 'widgets/profit_sharing_allocation.dart';
|
||||
import 'widgets/profit_sharing_detail_header.dart';
|
||||
import 'widgets/profit_sharing_status.dart';
|
||||
import 'widgets/profit_sharing_subcategories.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ProfitSharingDetailPage extends StatelessWidget
|
||||
implements AutoRouteWrapper {
|
||||
final String parentCategoryId;
|
||||
final String parentCategoryName;
|
||||
final DateTime dateFrom;
|
||||
final DateTime dateTo;
|
||||
|
||||
const ProfitSharingDetailPage({
|
||||
super.key,
|
||||
required this.parentCategoryId,
|
||||
required this.parentCategoryName,
|
||||
required this.dateFrom,
|
||||
required this.dateTo,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(BuildContext context) => BlocProvider(
|
||||
create: (_) => getIt<ProfitSharingDetailLoaderBloc>()..add(_fetchEvent()),
|
||||
child: this,
|
||||
);
|
||||
|
||||
ProfitSharingDetailLoaderEvent _fetchEvent() =>
|
||||
ProfitSharingDetailLoaderEvent.fetched(
|
||||
parentCategoryId: parentCategoryId,
|
||||
dateFrom: dateFrom,
|
||||
dateTo: dateTo,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColor.background,
|
||||
body:
|
||||
BlocBuilder<
|
||||
ProfitSharingDetailLoaderBloc,
|
||||
ProfitSharingDetailLoaderState
|
||||
>(
|
||||
builder: (context, state) {
|
||||
return RefreshIndicator(
|
||||
backgroundColor: AppColor.white,
|
||||
color: AppColor.primary,
|
||||
onRefresh: () async {
|
||||
context.read<ProfitSharingDetailLoaderBloc>().add(
|
||||
_fetchEvent(),
|
||||
);
|
||||
await context
|
||||
.read<ProfitSharingDetailLoaderBloc>()
|
||||
.stream
|
||||
.firstWhere((s) => !s.isFetching);
|
||||
},
|
||||
child: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: ProfitSharingDetailHeader(
|
||||
state: state,
|
||||
fallbackTitle: parentCategoryName,
|
||||
),
|
||||
),
|
||||
|
||||
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||
|
||||
if (state.isFetching)
|
||||
SliverToBoxAdapter(child: _buildShimmer())
|
||||
else ...[
|
||||
SliverToBoxAdapter(
|
||||
child: _SummaryCard(summary: state.detail.summary),
|
||||
),
|
||||
|
||||
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: ProfitSharingAllocation(
|
||||
budget: state.detail.budget,
|
||||
),
|
||||
),
|
||||
|
||||
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: ProfitSharingSubCategories(
|
||||
categories: state.detail.categories,
|
||||
expandedCategoryId: state.expandedCategoryId,
|
||||
onToggle: (categoryId) =>
|
||||
context.read<ProfitSharingDetailLoaderBloc>().add(
|
||||
ProfitSharingDetailLoaderEvent.expandedCategoryChanged(
|
||||
categoryId,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const SliverToBoxAdapter(child: SizedBox(height: 100)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShimmer() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
_shimmerBox(height: 160),
|
||||
const SpaceHeight(16),
|
||||
_shimmerBox(height: 240),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _shimmerBox({required double height}) {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: Colors.grey[300]!,
|
||||
highlightColor: Colors.grey[100]!,
|
||||
child: Container(
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SummaryCard extends StatelessWidget {
|
||||
final ProfitSharingCategory summary;
|
||||
|
||||
const _SummaryCard({required this.summary});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.textLight.withOpacity(0.08),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.summary,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.sub_category,
|
||||
value: '${summary.categoryCount}',
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.products,
|
||||
value: '${summary.productCount}',
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.orders,
|
||||
value: summary.orderCount.thousandFormat,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 12),
|
||||
child: Divider(height: 1, color: AppColor.borderLight),
|
||||
),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.std_hpp,
|
||||
value: formatPercent(summary.standardHppPercentage),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.real_hpp,
|
||||
value: formatPercent(summary.realHppPercentage),
|
||||
valueColor: statusColor(summary.realHppPercentage),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.gross_profit,
|
||||
value: summary.grossProfit.currencyFormatRp,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
import '../../../application/analytic/profit_sharing_loader/profit_sharing_loader_bloc.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../../injection.dart';
|
||||
import '../../components/field/date_range_picker_field.dart';
|
||||
import '../../components/spacer/spacer.dart';
|
||||
import '../../router/app_router.gr.dart';
|
||||
import 'widgets/profit_sharing_allocation.dart';
|
||||
import 'widgets/profit_sharing_categories.dart';
|
||||
import 'widgets/profit_sharing_header.dart';
|
||||
import 'widgets/profit_sharing_periods.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ProfitSharingPage extends StatefulWidget implements AutoRouteWrapper {
|
||||
const ProfitSharingPage({super.key});
|
||||
|
||||
@override
|
||||
State<ProfitSharingPage> createState() => _ProfitSharingPageState();
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(BuildContext context) => BlocProvider(
|
||||
create: (_) =>
|
||||
getIt<ProfitSharingLoaderBloc>()
|
||||
..add(ProfitSharingLoaderEvent.fetched()),
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
|
||||
class _ProfitSharingPageState extends State<ProfitSharingPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _fadeController;
|
||||
late Animation<double> _fadeAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_fadeController = AnimationController(
|
||||
duration: const Duration(milliseconds: 1000),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_fadeAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeIn));
|
||||
|
||||
_fadeController.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_fadeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColor.background,
|
||||
body: BlocListener<ProfitSharingLoaderBloc, ProfitSharingLoaderState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.dateFrom != current.dateFrom ||
|
||||
previous.dateTo != current.dateTo,
|
||||
listener: (context, state) {
|
||||
context.read<ProfitSharingLoaderBloc>().add(
|
||||
const ProfitSharingLoaderEvent.fetched(),
|
||||
);
|
||||
},
|
||||
child: BlocBuilder<ProfitSharingLoaderBloc, ProfitSharingLoaderState>(
|
||||
builder: (context, state) {
|
||||
return RefreshIndicator(
|
||||
backgroundColor: AppColor.white,
|
||||
color: AppColor.primary,
|
||||
onRefresh: () async {
|
||||
context.read<ProfitSharingLoaderBloc>().add(
|
||||
const ProfitSharingLoaderEvent.fetched(),
|
||||
);
|
||||
await context.read<ProfitSharingLoaderBloc>().stream.firstWhere(
|
||||
(s) => !s.isFetching,
|
||||
);
|
||||
},
|
||||
child: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: ProfitSharingHeader(state: state),
|
||||
),
|
||||
),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: DateRangePickerField(
|
||||
startDate: state.dateFrom,
|
||||
endDate: state.dateTo,
|
||||
onChanged: (startDate, endDate) {
|
||||
if (startDate == null || endDate == null) return;
|
||||
context.read<ProfitSharingLoaderBloc>().add(
|
||||
ProfitSharingLoaderEvent.rangeDateChanged(
|
||||
startDate,
|
||||
endDate,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
if (state.isFetching)
|
||||
SliverToBoxAdapter(child: _buildShimmer())
|
||||
else ...[
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: ProfitSharingCategories(
|
||||
categories: state.profitSharing.categories,
|
||||
onCategoryTap: (category) => context.router.push(
|
||||
ProfitSharingDetailRoute(
|
||||
parentCategoryId: category.parentCategoryId,
|
||||
parentCategoryName: category.parentCategoryName,
|
||||
dateFrom: state.dateFrom,
|
||||
dateTo: state.dateTo,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: ProfitSharingAllocation(
|
||||
budget: state.profitSharing.budget,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: ProfitSharingPeriods(
|
||||
state: state,
|
||||
onPeriodTypeChanged: (type) {
|
||||
context.read<ProfitSharingLoaderBloc>().add(
|
||||
ProfitSharingLoaderEvent.periodTypeChanged(type),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const SliverToBoxAdapter(child: SizedBox(height: 100)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShimmer() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
_shimmerBox(height: 240),
|
||||
const SpaceHeight(16),
|
||||
_shimmerBox(height: 300),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _shimmerBox({required double height}) {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: Colors.grey[300]!,
|
||||
highlightColor: Colors.grey[100]!,
|
||||
child: Container(
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
/// Label rentang periode ringkas, contoh: "27 Jul - 2 Agu 2026".
|
||||
String formatPeriodLabel(DateTime from, DateTime to) {
|
||||
final dayMonth = DateFormat('d MMM', 'id_ID');
|
||||
final dayMonthYear = DateFormat('d MMM yyyy', 'id_ID');
|
||||
|
||||
if (from.year == to.year && from.month == to.month && from.day == to.day) {
|
||||
return dayMonthYear.format(from);
|
||||
}
|
||||
if (from.year == to.year) {
|
||||
return '${dayMonth.format(from)} - ${dayMonthYear.format(to)}';
|
||||
}
|
||||
return '${dayMonthYear.format(from)} - ${dayMonthYear.format(to)}';
|
||||
}
|
||||
|
||||
/// Label bulan dari format API "2026-08" menjadi "Agustus 2026".
|
||||
String formatMonthLabel(String month, DateTime fallback) {
|
||||
final parts = month.split('-');
|
||||
if (parts.length == 2) {
|
||||
final year = int.tryParse(parts[0]);
|
||||
final monthNumber = int.tryParse(parts[1]);
|
||||
if (year != null && monthNumber != null) {
|
||||
return DateFormat(
|
||||
'MMMM yyyy',
|
||||
'id_ID',
|
||||
).format(DateTime(year, monthNumber));
|
||||
}
|
||||
}
|
||||
return DateFormat('MMMM yyyy', 'id_ID').format(fallback);
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
|
||||
/// Warna tetap untuk tiap pos bagi hasil supaya konsisten di semua bagian.
|
||||
class ProfitSharingColor {
|
||||
static const Color purchase = Color(0xFF2196F3);
|
||||
static const Color owner = Color(0xFF4CAF50);
|
||||
static const Color team = Color(0xFFFF9800);
|
||||
}
|
||||
|
||||
class ProfitSharingAllocation extends StatelessWidget {
|
||||
final ProfitSharingBudget budget;
|
||||
|
||||
const ProfitSharingAllocation({super.key, required this.budget});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final total = budget.total;
|
||||
final percentages = budget.percentages;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.textLight.withOpacity(0.08),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.profit_sharing_allocation,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
ProfitSharingBar(
|
||||
purchase: percentages.purchase,
|
||||
owner: percentages.owner,
|
||||
team: percentages.team,
|
||||
),
|
||||
|
||||
const SpaceHeight(20),
|
||||
|
||||
_row(
|
||||
context: context,
|
||||
color: ProfitSharingColor.purchase,
|
||||
label: context.lang.share_purchase,
|
||||
percentage: percentages.purchase,
|
||||
amount: total.limitPurchase,
|
||||
),
|
||||
const Divider(height: 24, color: AppColor.borderLight),
|
||||
_row(
|
||||
context: context,
|
||||
color: ProfitSharingColor.owner,
|
||||
label: context.lang.share_owner,
|
||||
percentage: percentages.owner,
|
||||
amount: total.limitOwner,
|
||||
),
|
||||
const Divider(height: 24, color: AppColor.borderLight),
|
||||
_row(
|
||||
context: context,
|
||||
color: ProfitSharingColor.team,
|
||||
label: context.lang.share_team,
|
||||
percentage: percentages.team,
|
||||
amount: total.limitTeam,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _row({
|
||||
required BuildContext context,
|
||||
required Color color,
|
||||
required String label,
|
||||
required double percentage,
|
||||
required int amount,
|
||||
}) {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
),
|
||||
const SpaceWidth(10),
|
||||
Text(
|
||||
label,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.12),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
'${percentage.toStringAsFixed(percentage % 1 == 0 ? 0 : 1)}%',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Flexible(
|
||||
child: Text(
|
||||
amount.currencyFormatRp,
|
||||
textAlign: TextAlign.right,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Bar proporsi belanja / owner / tim.
|
||||
class ProfitSharingBar extends StatelessWidget {
|
||||
final double purchase;
|
||||
final double owner;
|
||||
final double team;
|
||||
final double height;
|
||||
|
||||
const ProfitSharingBar({
|
||||
super.key,
|
||||
required this.purchase,
|
||||
required this.owner,
|
||||
required this.team,
|
||||
this.height = 12,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final total = purchase + owner + team;
|
||||
if (total <= 0) {
|
||||
return Container(
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.borderLight,
|
||||
borderRadius: BorderRadius.circular(height),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(height),
|
||||
child: SizedBox(
|
||||
height: height,
|
||||
child: Row(
|
||||
children: [
|
||||
_segment(purchase, ProfitSharingColor.purchase),
|
||||
_segment(owner, ProfitSharingColor.owner),
|
||||
_segment(team, ProfitSharingColor.team),
|
||||
].whereType<Widget>().toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Segmen dilewati saat porsinya 0 supaya Expanded tidak dipakai dengan flex 0.
|
||||
Widget? _segment(double value, Color color) {
|
||||
final flex = (value * 100).round();
|
||||
if (flex <= 0) return null;
|
||||
return Expanded(
|
||||
flex: flex,
|
||||
child: Container(color: color),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
import 'profit_sharing_status.dart';
|
||||
|
||||
/// Ringkasan omzet & HPP per kategori induk (parent category).
|
||||
/// Tiap kartu bisa diketuk untuk melihat rincian sub kategori & produknya.
|
||||
class ProfitSharingCategories extends StatelessWidget {
|
||||
final List<ProfitSharingCategory> categories;
|
||||
final ValueChanged<ProfitSharingCategory> onCategoryTap;
|
||||
|
||||
const ProfitSharingCategories({
|
||||
super.key,
|
||||
required this.categories,
|
||||
required this.onCategoryTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sorted = [...categories]
|
||||
..sort((a, b) => b.totalRevenue.compareTo(a.totalRevenue));
|
||||
|
||||
final totalRevenue = sorted.fold<int>(0, (sum, e) => sum + e.totalRevenue);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.textLight.withOpacity(0.08),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.parent_category_summary,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
context.lang.tap_row_for_detail,
|
||||
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
if (sorted.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.lang.no_data_yet,
|
||||
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
),
|
||||
)
|
||||
else ...[
|
||||
...sorted.map(
|
||||
(category) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: _CategoryCard(
|
||||
category: category,
|
||||
share: totalRevenue == 0
|
||||
? 0
|
||||
: category.totalRevenue / totalRevenue,
|
||||
onTap: () => onCategoryTap(category),
|
||||
),
|
||||
),
|
||||
),
|
||||
_TotalCard(categories: sorted),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CategoryCard extends StatelessWidget {
|
||||
final ProfitSharingCategory category;
|
||||
final double share;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _CategoryCard({
|
||||
required this.category,
|
||||
required this.share,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.background,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColor.borderLight),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
category.parentCategoryName,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
ProfitSharingStatusBadge(
|
||||
realHppPercentage: category.realHppPercentage,
|
||||
),
|
||||
const Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: AppColor.textSecondary,
|
||||
size: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(8),
|
||||
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
category.totalRevenue.currencyFormatRp,
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
formatPercent(share * 100),
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(8),
|
||||
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: LinearProgressIndicator(
|
||||
value: share.clamp(0.0, 1.0),
|
||||
minHeight: 6,
|
||||
backgroundColor: AppColor.borderLight,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||
AppColor.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(12),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.qty,
|
||||
value: category.totalQuantity.thousandFormat,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.products,
|
||||
value: '${category.productCount}',
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.sub_category,
|
||||
value: '${category.categoryCount}',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 10),
|
||||
child: Divider(height: 1, color: AppColor.border),
|
||||
),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.std_hpp,
|
||||
value: formatPercent(category.standardHppPercentage),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.real_hpp,
|
||||
value: formatPercent(category.realHppPercentage),
|
||||
valueColor: statusColor(category.realHppPercentage),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TotalCard extends StatelessWidget {
|
||||
final List<ProfitSharingCategory> categories;
|
||||
|
||||
const _TotalCard({required this.categories});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final totalQuantity = categories.fold<int>(
|
||||
0,
|
||||
(sum, e) => sum + e.totalQuantity,
|
||||
);
|
||||
final totalRevenue = categories.fold<int>(
|
||||
0,
|
||||
(sum, e) => sum + e.totalRevenue,
|
||||
);
|
||||
final totalStandardHpp = categories.fold<int>(
|
||||
0,
|
||||
(sum, e) => sum + e.totalStandardHpp,
|
||||
);
|
||||
final totalFifoHpp = categories.fold<int>(
|
||||
0,
|
||||
(sum, e) => sum + e.totalFifoHpp,
|
||||
);
|
||||
|
||||
double percentOf(int hpp) =>
|
||||
totalRevenue == 0 ? 0 : (hpp / totalRevenue) * 100;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.06),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
context.lang.grand_total,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
totalRevenue.currencyFormatRp,
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(12),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.qty,
|
||||
value: totalQuantity.thousandFormat,
|
||||
isBold: true,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.std_hpp,
|
||||
value: formatPercent(percentOf(totalStandardHpp)),
|
||||
isBold: true,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.real_hpp,
|
||||
value: formatPercent(percentOf(totalFifoHpp)),
|
||||
isBold: true,
|
||||
valueColor: statusColor(percentOf(totalFifoHpp)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
import '../../../../application/analytic/profit_sharing_detail_loader/profit_sharing_detail_loader_bloc.dart';
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
import 'period_label.dart';
|
||||
import 'profit_sharing_status.dart';
|
||||
|
||||
class ProfitSharingDetailHeader extends StatelessWidget {
|
||||
final ProfitSharingDetailLoaderState state;
|
||||
final String fallbackTitle;
|
||||
|
||||
const ProfitSharingDetailHeader({
|
||||
super.key,
|
||||
required this.state,
|
||||
required this.fallbackTitle,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final detail = state.detail;
|
||||
final summary = detail.summary;
|
||||
final title = detail.parentCategoryName.isNotEmpty
|
||||
? detail.parentCategoryName
|
||||
: fallbackTitle;
|
||||
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: AppColor.primaryGradient,
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24),
|
||||
bottomRight: Radius.circular(24),
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => context.router.maybePop(),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.chevron_left_rounded,
|
||||
color: AppColor.textWhite,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SpaceWidth(12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: AppStyle.xl.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 20,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
formatPeriodLabel(state.dateFrom, state.dateTo),
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.75),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(24),
|
||||
|
||||
if (state.isFetching)
|
||||
_shimmerBox(width: 220, height: 36)
|
||||
else
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
summary.totalRevenue.currencyFormatRp,
|
||||
style: AppStyle.h1.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 30,
|
||||
),
|
||||
),
|
||||
),
|
||||
ProfitSharingStatusBadge(
|
||||
realHppPercentage: summary.realHppPercentage,
|
||||
onDarkBackground: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(4),
|
||||
Text(
|
||||
context.lang.total_revenue,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.75),
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
if (state.isFetching)
|
||||
Row(
|
||||
children: [
|
||||
_shimmerBox(width: 120, height: 32, radius: 20),
|
||||
const SpaceWidth(8),
|
||||
_shimmerBox(width: 120, height: 32, radius: 20),
|
||||
],
|
||||
)
|
||||
else
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_chip(
|
||||
'${context.lang.qty} ${summary.totalQuantity.thousandFormat}',
|
||||
),
|
||||
_chip(context.lang.order_count_label(summary.orderCount)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _chip(String label) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: AppColor.textWhite.withOpacity(0.25)),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _shimmerBox({
|
||||
required double width,
|
||||
required double height,
|
||||
double radius = 8,
|
||||
}) {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: AppColor.textWhite.withOpacity(0.3),
|
||||
highlightColor: AppColor.textWhite.withOpacity(0.6),
|
||||
child: Container(
|
||||
width: width,
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.3),
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user