feat: profit sharing
Build & Deploy iOS to TestFlight / build-and-deploy (push) Canceled after 0s

This commit is contained in:
efrilm
2026-08-21 22:31:54 +07:00
parent f9bfb69254
commit 2e4c77888e
48 changed files with 12553 additions and 162 deletions
+72 -18
View File
@@ -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
+7
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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: **±90120 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, ±90120 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.
@@ -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));
},
);
}
}
@@ -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;
}
@@ -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;
}
@@ -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));
},
);
}
}
@@ -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();
}
}
+3
View File
@@ -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);
}
+2
View File
@@ -14,6 +14,8 @@ class ApiPath {
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 =
+1
View File
@@ -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
@@ -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;
}
@@ -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,
});
}
@@ -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
@@ -1121,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));
}
}
}
@@ -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,
);
}
@@ -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());
}
}
}
+11
View File
@@ -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>()),
);
+63 -1
View File
@@ -538,5 +538,67 @@
"no_data_yet": "No data yet",
"@no_data_yet": {},
"total_inventory_value": "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": {}
}
+63 -1
View File
@@ -538,5 +538,67 @@
"no_data_yet": "Belum ada data",
"@no_data_yet": {},
"total_inventory_value": "Total Nilai Inventori",
"@total_inventory_value": {}
"@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": {}
}
+144
View File
@@ -1564,6 +1564,150 @@ abstract class AppLocalizations {
/// 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> {
+76
View File
@@ -756,4 +756,80 @@ class AppLocalizationsEn extends AppLocalizations {
@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';
}
+76
View File
@@ -756,4 +756,80 @@ class AppLocalizationsId extends AppLocalizations {
@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';
}
@@ -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(
@@ -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,
),
],
),
),
),
);
}
}
@@ -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),
),
),
);
}
}
@@ -0,0 +1,163 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import '../../../../application/analytic/profit_sharing_loader/profit_sharing_loader_bloc.dart';
import '../../../../common/extension/extension.dart';
import '../../../../common/theme/theme.dart';
import '../../../components/spacer/spacer.dart';
class ProfitSharingHeader extends StatelessWidget {
final ProfitSharingLoaderState state;
const ProfitSharingHeader({super.key, required this.state});
@override
Widget build(BuildContext context) {
final budget = state.profitSharing.budget;
final outletLabel = state.profitSharing.outletName.isNotEmpty
? state.profitSharing.outletName
: context.lang.all_outlets;
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: [
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.profit_sharing,
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),
fontSize: 12,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
const SpaceHeight(24),
state.isFetching
? _shimmerBox(width: 220, height: 36)
: Text(
budget.total.revenue.currencyFormatRp,
style: AppStyle.h1.copyWith(
color: AppColor.textWhite,
fontWeight: FontWeight.w900,
fontSize: 32,
),
),
const SpaceHeight(4),
Text(
context.lang.total_revenue,
style: AppStyle.sm.copyWith(
color: AppColor.textWhite.withOpacity(0.75),
fontSize: 13,
),
),
const SpaceHeight(16),
state.isFetching
? _shimmerBox(width: 140, height: 32, radius: 20)
: _chip(
context.lang.order_count_label(budget.total.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),
),
),
);
}
}
@@ -0,0 +1,270 @@
import 'package:flutter/material.dart';
import '../../../../application/analytic/profit_sharing_loader/profit_sharing_loader_bloc.dart';
import '../../../../common/extension/extension.dart';
import '../../../../common/theme/theme.dart';
import '../../../../domain/analytic/analytic.dart';
import '../../../components/spacer/spacer.dart';
import 'period_label.dart';
import 'profit_sharing_allocation.dart';
class ProfitSharingPeriods extends StatelessWidget {
final ProfitSharingLoaderState state;
final ValueChanged<ProfitSharingPeriodType> onPeriodTypeChanged;
const ProfitSharingPeriods({
super.key,
required this.state,
required this.onPeriodTypeChanged,
});
@override
Widget build(BuildContext context) {
final periods = state.periods;
final percentages = state.profitSharing.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.period_breakdown,
style: AppStyle.lg.copyWith(
fontWeight: FontWeight.w700,
color: AppColor.textPrimary,
),
),
const SpaceHeight(16),
_tabs(context),
const SpaceHeight(16),
if (periods.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
...periods.map(
(period) => Padding(
padding: const EdgeInsets.only(bottom: 12),
child: _PeriodCard(period: period, percentages: percentages),
),
),
],
),
);
}
Widget _tabs(BuildContext context) {
return Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: AppColor.background,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
_tab(
context: context,
label: context.lang.weekly,
type: ProfitSharingPeriodType.weekly,
),
_tab(
context: context,
label: context.lang.monthly,
type: ProfitSharingPeriodType.monthly,
),
],
),
);
}
Widget _tab({
required BuildContext context,
required String label,
required ProfitSharingPeriodType type,
}) {
final isSelected = state.periodType == type;
return Expanded(
child: GestureDetector(
onTap: () => onPeriodTypeChanged(type),
behavior: HitTestBehavior.opaque,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color: isSelected ? AppColor.primary : Colors.transparent,
borderRadius: BorderRadius.circular(10),
),
child: Text(
label,
textAlign: TextAlign.center,
style: AppStyle.sm.copyWith(
color: isSelected ? AppColor.textWhite : AppColor.textSecondary,
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500,
),
),
),
),
);
}
}
class _PeriodCard extends StatelessWidget {
final ProfitSharingPeriod period;
final ProfitSharingPercentage percentages;
const _PeriodCard({required this.period, required this.percentages});
@override
Widget build(BuildContext context) {
final isMonthly = period.month.isNotEmpty;
final title = isMonthly
? formatMonthLabel(period.month, period.periodStart)
: formatPeriodLabel(period.periodStart, period.periodEnd);
final subtitle = isMonthly
? '${formatPeriodLabel(period.periodStart, period.periodEnd)} - ${context.lang.week_count(period.weekCount)}'
: context.lang.order_count_label(period.orderCount);
return Container(
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: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: AppStyle.md.copyWith(
fontWeight: FontWeight.w700,
color: AppColor.textPrimary,
),
),
const SizedBox(height: 2),
Text(
subtitle,
style: AppStyle.xs.copyWith(
color: AppColor.textSecondary,
),
),
],
),
),
const SpaceWidth(8),
Text(
period.revenue.currencyFormatRp,
style: AppStyle.md.copyWith(
fontWeight: FontWeight.w800,
color: AppColor.primary,
),
),
],
),
const SpaceHeight(12),
ProfitSharingBar(
purchase: percentages.purchase,
owner: percentages.owner,
team: percentages.team,
height: 6,
),
const SpaceHeight(12),
Row(
children: [
_share(
context.lang.share_purchase,
period.limitPurchase,
ProfitSharingColor.purchase,
),
_share(
context.lang.share_owner,
period.limitOwner,
ProfitSharingColor.owner,
),
_share(
context.lang.share_team,
period.limitTeam,
ProfitSharingColor.team,
),
],
),
],
),
);
}
Widget _share(String label, int amount, Color color) {
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
),
const SpaceWidth(6),
Flexible(
child: Text(
label,
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 4),
Text(
amount.currencyFormatRp,
style: AppStyle.xs.copyWith(
color: AppColor.textPrimary,
fontWeight: FontWeight.w700,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
);
}
}
@@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import '../../../../common/extension/extension.dart';
import '../../../../common/theme/theme.dart';
/// Ambang kesehatan berdasarkan % HPP riil terhadap omzet.
/// Ubah di sini kalau target margin bisnis berubah.
class HppThreshold {
static const double healthy = 70;
static const double watch = 80;
}
String formatPercent(double value) => '${value.toStringAsFixed(1)}%';
Color statusColor(double realHppPercentage) {
if (realHppPercentage <= HppThreshold.healthy) return AppColor.success;
if (realHppPercentage <= HppThreshold.watch) return AppColor.warning;
return AppColor.error;
}
String statusLabel(BuildContext context, double realHppPercentage) {
if (realHppPercentage <= HppThreshold.healthy) {
return context.lang.status_healthy;
}
if (realHppPercentage <= HppThreshold.watch) return context.lang.status_watch;
return context.lang.status_critical;
}
/// Pill status margin (Sehat / Waspada / Tidak Sehat).
class ProfitSharingStatusBadge extends StatelessWidget {
final double realHppPercentage;
final bool onDarkBackground;
const ProfitSharingStatusBadge({
super.key,
required this.realHppPercentage,
this.onDarkBackground = false,
});
@override
Widget build(BuildContext context) {
final color = statusColor(realHppPercentage);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: onDarkBackground
? AppColor.textWhite.withOpacity(0.18)
: color.withOpacity(0.12),
borderRadius: BorderRadius.circular(20),
border: onDarkBackground
? Border.all(color: AppColor.textWhite.withOpacity(0.3))
: null,
),
child: Text(
statusLabel(context, realHppPercentage),
style: AppStyle.xs.copyWith(
color: onDarkBackground ? AppColor.textWhite : color,
fontWeight: FontWeight.w700,
),
),
);
}
}
/// Label kecil dua baris: judul di atas, nilai di bawah.
class ProfitSharingMetric extends StatelessWidget {
final String label;
final String value;
final Color? valueColor;
final bool isBold;
const ProfitSharingMetric({
super.key,
required this.label,
required this.value,
this.valueColor,
this.isBold = false,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
value,
style: AppStyle.sm.copyWith(
color: valueColor ?? AppColor.textPrimary,
fontWeight: isBold ? FontWeight.w800 : FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
);
}
}
@@ -0,0 +1,311 @@
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';
/// Daftar sub kategori; tiap kartu bisa dibuka untuk melihat produknya.
class ProfitSharingSubCategories extends StatelessWidget {
final List<ProfitSharingSubCategory> categories;
final String expandedCategoryId;
final ValueChanged<String> onToggle;
const ProfitSharingSubCategories({
super.key,
required this.categories,
required this.expandedCategoryId,
required this.onToggle,
});
@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.sub_category,
style: AppStyle.lg.copyWith(
fontWeight: FontWeight.w700,
color: AppColor.textPrimary,
),
),
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: _SubCategoryCard(
category: category,
share: totalRevenue == 0
? 0
: category.totalRevenue / totalRevenue,
isExpanded: expandedCategoryId == category.categoryId,
onToggle: () => onToggle(category.categoryId),
),
),
),
],
),
);
}
}
class _SubCategoryCard extends StatelessWidget {
final ProfitSharingSubCategory category;
final double share;
final bool isExpanded;
final VoidCallback onToggle;
const _SubCategoryCard({
required this.category,
required this.share,
required this.isExpanded,
required this.onToggle,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: AppColor.background,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColor.borderLight),
),
child: Column(
children: [
InkWell(
onTap: onToggle,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
category.categoryName,
style: AppStyle.md.copyWith(
color: AppColor.textPrimary,
fontWeight: FontWeight.w700,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const SpaceWidth(8),
Text(
category.totalRevenue.currencyFormatRp,
style: AppStyle.md.copyWith(
color: AppColor.primary,
fontWeight: FontWeight.w800,
),
),
AnimatedRotation(
turns: isExpanded ? 0.5 : 0,
duration: const Duration(milliseconds: 200),
child: const Icon(
Icons.keyboard_arrow_down_rounded,
color: AppColor.textSecondary,
size: 22,
),
),
],
),
const SpaceHeight(8),
ClipRRect(
borderRadius: BorderRadius.circular(6),
child: LinearProgressIndicator(
value: share.clamp(0.0, 1.0),
minHeight: 5,
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.real_hpp,
value: formatPercent(category.realHppPercentage),
valueColor: statusColor(category.realHppPercentage),
),
),
],
),
],
),
),
),
AnimatedCrossFade(
duration: const Duration(milliseconds: 200),
crossFadeState: isExpanded
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
firstChild: _ProductList(products: category.products),
secondChild: const SizedBox(width: double.infinity),
),
],
),
);
}
}
class _ProductList extends StatelessWidget {
final List<ProfitSharingProduct> products;
const _ProductList({required this.products});
@override
Widget build(BuildContext context) {
final sorted = [...products]
..sort((a, b) => b.revenue.compareTo(a.revenue));
return Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(14, 0, 14, 14),
child: Column(
children: [
const Divider(height: 1, color: AppColor.border),
const SpaceHeight(10),
...sorted.map((product) => _ProductRow(product: product)),
],
),
);
}
}
class _ProductRow extends StatelessWidget {
final ProfitSharingProduct product;
const _ProductRow({required this.product});
@override
Widget build(BuildContext context) {
final color = statusColor(product.realHppPercentage);
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(top: 4),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: AppColor.primary.withOpacity(0.08),
borderRadius: BorderRadius.circular(6),
),
child: Text(
'${product.quantitySold}x',
style: AppStyle.xs.copyWith(
color: AppColor.primary,
fontWeight: FontWeight.w700,
),
),
),
const SpaceWidth(10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.productName,
style: AppStyle.sm.copyWith(
color: AppColor.textPrimary,
fontWeight: FontWeight.w600,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
'${product.productSku} · @${product.averagePrice.round().currencyFormatRp}',
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
const SpaceWidth(8),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
product.revenue.currencyFormatRp,
style: AppStyle.sm.copyWith(
color: AppColor.textPrimary,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 2),
Text(
'${context.lang.real_hpp} ${formatPercent(product.realHppPercentage)}',
style: AppStyle.xs.copyWith(
color: color,
fontWeight: FontWeight.w600,
),
),
],
),
],
),
);
}
}
+4
View File
@@ -50,6 +50,10 @@ class AppRouter extends RootStackRouter {
// Finance page
AutoRoute(page: FinanceRoute.page),
// Profit Sharing
AutoRoute(page: ProfitSharingRoute.page),
AutoRoute(page: ProfitSharingDetailRoute.page),
// Exclusive Summary
AutoRoute(page: ExclusiveSummaryRoute.page),
+214 -127
View File
@@ -9,8 +9,8 @@
// coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:apskel_owner_flutter/domain/order/order.dart' as _i29;
import 'package:apskel_owner_flutter/domain/user/user.dart' as _i30;
import 'package:apskel_owner_flutter/domain/order/order.dart' as _i31;
import 'package:apskel_owner_flutter/domain/user/user.dart' as _i32;
import 'package:apskel_owner_flutter/presentation/pages/about_app/about_app_page.dart'
as _i1;
import 'package:apskel_owner_flutter/presentation/pages/auth/login/login_page.dart'
@@ -53,28 +53,32 @@ import 'package:apskel_owner_flutter/presentation/pages/profile/pages/profile_ed
as _i20;
import 'package:apskel_owner_flutter/presentation/pages/profile/profile_page.dart'
as _i21;
import 'package:apskel_owner_flutter/presentation/pages/purchase/purchase_page.dart'
import 'package:apskel_owner_flutter/presentation/pages/profit_sharing/profit_sharing_detail_page.dart'
as _i22;
import 'package:apskel_owner_flutter/presentation/pages/report/report_page.dart'
import 'package:apskel_owner_flutter/presentation/pages/profit_sharing/profit_sharing_page.dart'
as _i23;
import 'package:apskel_owner_flutter/presentation/pages/sales/sales_page.dart'
import 'package:apskel_owner_flutter/presentation/pages/purchase/purchase_page.dart'
as _i24;
import 'package:apskel_owner_flutter/presentation/pages/schedule/schedule_page.dart'
import 'package:apskel_owner_flutter/presentation/pages/report/report_page.dart'
as _i25;
import 'package:apskel_owner_flutter/presentation/pages/splash/splash_page.dart'
import 'package:apskel_owner_flutter/presentation/pages/sales/sales_page.dart'
as _i26;
import 'package:auto_route/auto_route.dart' as _i27;
import 'package:flutter/material.dart' as _i28;
import 'package:apskel_owner_flutter/presentation/pages/schedule/schedule_page.dart'
as _i27;
import 'package:apskel_owner_flutter/presentation/pages/splash/splash_page.dart'
as _i28;
import 'package:auto_route/auto_route.dart' as _i29;
import 'package:flutter/material.dart' as _i30;
/// generated route for
/// [_i1.AboutAppPage]
class AboutAppRoute extends _i27.PageRouteInfo<void> {
const AboutAppRoute({List<_i27.PageRouteInfo>? children})
class AboutAppRoute extends _i29.PageRouteInfo<void> {
const AboutAppRoute({List<_i29.PageRouteInfo>? children})
: super(AboutAppRoute.name, initialChildren: children);
static const String name = 'AboutAppRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return const _i1.AboutAppPage();
@@ -84,13 +88,13 @@ class AboutAppRoute extends _i27.PageRouteInfo<void> {
/// generated route for
/// [_i2.ComingSoonPage]
class ComingSoonRoute extends _i27.PageRouteInfo<void> {
const ComingSoonRoute({List<_i27.PageRouteInfo>? children})
class ComingSoonRoute extends _i29.PageRouteInfo<void> {
const ComingSoonRoute({List<_i29.PageRouteInfo>? children})
: super(ComingSoonRoute.name, initialChildren: children);
static const String name = 'ComingSoonRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return const _i2.ComingSoonPage();
@@ -100,29 +104,29 @@ class ComingSoonRoute extends _i27.PageRouteInfo<void> {
/// generated route for
/// [_i3.CustomerPage]
class CustomerRoute extends _i27.PageRouteInfo<void> {
const CustomerRoute({List<_i27.PageRouteInfo>? children})
class CustomerRoute extends _i29.PageRouteInfo<void> {
const CustomerRoute({List<_i29.PageRouteInfo>? children})
: super(CustomerRoute.name, initialChildren: children);
static const String name = 'CustomerRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i27.WrappedRoute(child: const _i3.CustomerPage());
return _i29.WrappedRoute(child: const _i3.CustomerPage());
},
);
}
/// generated route for
/// [_i4.DailyTasksFormPage]
class DailyTasksFormRoute extends _i27.PageRouteInfo<void> {
const DailyTasksFormRoute({List<_i27.PageRouteInfo>? children})
class DailyTasksFormRoute extends _i29.PageRouteInfo<void> {
const DailyTasksFormRoute({List<_i29.PageRouteInfo>? children})
: super(DailyTasksFormRoute.name, initialChildren: children);
static const String name = 'DailyTasksFormRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return const _i4.DailyTasksFormPage();
@@ -132,32 +136,32 @@ class DailyTasksFormRoute extends _i27.PageRouteInfo<void> {
/// generated route for
/// [_i5.DownloadReportPage]
class DownloadReportRoute extends _i27.PageRouteInfo<void> {
const DownloadReportRoute({List<_i27.PageRouteInfo>? children})
class DownloadReportRoute extends _i29.PageRouteInfo<void> {
const DownloadReportRoute({List<_i29.PageRouteInfo>? children})
: super(DownloadReportRoute.name, initialChildren: children);
static const String name = 'DownloadReportRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i27.WrappedRoute(child: const _i5.DownloadReportPage());
return _i29.WrappedRoute(child: const _i5.DownloadReportPage());
},
);
}
/// generated route for
/// [_i6.ErrorPage]
class ErrorRoute extends _i27.PageRouteInfo<ErrorRouteArgs> {
class ErrorRoute extends _i29.PageRouteInfo<ErrorRouteArgs> {
ErrorRoute({
_i28.Key? key,
_i30.Key? key,
String? title,
String? message,
_i28.VoidCallback? onRetry,
_i28.VoidCallback? onBack,
_i30.VoidCallback? onRetry,
_i30.VoidCallback? onBack,
String? errorCode,
_i28.IconData? errorIcon,
List<_i27.PageRouteInfo>? children,
_i30.IconData? errorIcon,
List<_i29.PageRouteInfo>? children,
}) : super(
ErrorRoute.name,
args: ErrorRouteArgs(
@@ -174,7 +178,7 @@ class ErrorRoute extends _i27.PageRouteInfo<ErrorRouteArgs> {
static const String name = 'ErrorRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
final args = data.argsAs<ErrorRouteArgs>(
@@ -204,19 +208,19 @@ class ErrorRouteArgs {
this.errorIcon,
});
final _i28.Key? key;
final _i30.Key? key;
final String? title;
final String? message;
final _i28.VoidCallback? onRetry;
final _i30.VoidCallback? onRetry;
final _i28.VoidCallback? onBack;
final _i30.VoidCallback? onBack;
final String? errorCode;
final _i28.IconData? errorIcon;
final _i30.IconData? errorIcon;
@override
String toString() {
@@ -226,77 +230,77 @@ class ErrorRouteArgs {
/// generated route for
/// [_i7.ExclusiveSummaryPage]
class ExclusiveSummaryRoute extends _i27.PageRouteInfo<void> {
const ExclusiveSummaryRoute({List<_i27.PageRouteInfo>? children})
class ExclusiveSummaryRoute extends _i29.PageRouteInfo<void> {
const ExclusiveSummaryRoute({List<_i29.PageRouteInfo>? children})
: super(ExclusiveSummaryRoute.name, initialChildren: children);
static const String name = 'ExclusiveSummaryRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i27.WrappedRoute(child: const _i7.ExclusiveSummaryPage());
return _i29.WrappedRoute(child: const _i7.ExclusiveSummaryPage());
},
);
}
/// generated route for
/// [_i8.FinancePage]
class FinanceRoute extends _i27.PageRouteInfo<void> {
const FinanceRoute({List<_i27.PageRouteInfo>? children})
class FinanceRoute extends _i29.PageRouteInfo<void> {
const FinanceRoute({List<_i29.PageRouteInfo>? children})
: super(FinanceRoute.name, initialChildren: children);
static const String name = 'FinanceRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i27.WrappedRoute(child: const _i8.FinancePage());
return _i29.WrappedRoute(child: const _i8.FinancePage());
},
);
}
/// generated route for
/// [_i9.HomePage]
class HomeRoute extends _i27.PageRouteInfo<void> {
const HomeRoute({List<_i27.PageRouteInfo>? children})
class HomeRoute extends _i29.PageRouteInfo<void> {
const HomeRoute({List<_i29.PageRouteInfo>? children})
: super(HomeRoute.name, initialChildren: children);
static const String name = 'HomeRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i27.WrappedRoute(child: const _i9.HomePage());
return _i29.WrappedRoute(child: const _i9.HomePage());
},
);
}
/// generated route for
/// [_i10.InventoryPage]
class InventoryRoute extends _i27.PageRouteInfo<void> {
const InventoryRoute({List<_i27.PageRouteInfo>? children})
class InventoryRoute extends _i29.PageRouteInfo<void> {
const InventoryRoute({List<_i29.PageRouteInfo>? children})
: super(InventoryRoute.name, initialChildren: children);
static const String name = 'InventoryRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i27.WrappedRoute(child: const _i10.InventoryPage());
return _i29.WrappedRoute(child: const _i10.InventoryPage());
},
);
}
/// generated route for
/// [_i11.LanguagePage]
class LanguageRoute extends _i27.PageRouteInfo<void> {
const LanguageRoute({List<_i27.PageRouteInfo>? children})
class LanguageRoute extends _i29.PageRouteInfo<void> {
const LanguageRoute({List<_i29.PageRouteInfo>? children})
: super(LanguageRoute.name, initialChildren: children);
static const String name = 'LanguageRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return const _i11.LanguagePage();
@@ -306,29 +310,29 @@ class LanguageRoute extends _i27.PageRouteInfo<void> {
/// generated route for
/// [_i12.LoginPage]
class LoginRoute extends _i27.PageRouteInfo<void> {
const LoginRoute({List<_i27.PageRouteInfo>? children})
class LoginRoute extends _i29.PageRouteInfo<void> {
const LoginRoute({List<_i29.PageRouteInfo>? children})
: super(LoginRoute.name, initialChildren: children);
static const String name = 'LoginRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i27.WrappedRoute(child: const _i12.LoginPage());
return _i29.WrappedRoute(child: const _i12.LoginPage());
},
);
}
/// generated route for
/// [_i13.MainPage]
class MainRoute extends _i27.PageRouteInfo<void> {
const MainRoute({List<_i27.PageRouteInfo>? children})
class MainRoute extends _i29.PageRouteInfo<void> {
const MainRoute({List<_i29.PageRouteInfo>? children})
: super(MainRoute.name, initialChildren: children);
static const String name = 'MainRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return const _i13.MainPage();
@@ -338,11 +342,11 @@ class MainRoute extends _i27.PageRouteInfo<void> {
/// generated route for
/// [_i14.OrderDetailPage]
class OrderDetailRoute extends _i27.PageRouteInfo<OrderDetailRouteArgs> {
class OrderDetailRoute extends _i29.PageRouteInfo<OrderDetailRouteArgs> {
OrderDetailRoute({
_i28.Key? key,
required _i29.Order order,
List<_i27.PageRouteInfo>? children,
_i30.Key? key,
required _i31.Order order,
List<_i29.PageRouteInfo>? children,
}) : super(
OrderDetailRoute.name,
args: OrderDetailRouteArgs(key: key, order: order),
@@ -351,7 +355,7 @@ class OrderDetailRoute extends _i27.PageRouteInfo<OrderDetailRouteArgs> {
static const String name = 'OrderDetailRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
final args = data.argsAs<OrderDetailRouteArgs>();
@@ -363,9 +367,9 @@ class OrderDetailRoute extends _i27.PageRouteInfo<OrderDetailRouteArgs> {
class OrderDetailRouteArgs {
const OrderDetailRouteArgs({this.key, required this.order});
final _i28.Key? key;
final _i30.Key? key;
final _i29.Order order;
final _i31.Order order;
@override
String toString() {
@@ -375,91 +379,91 @@ class OrderDetailRouteArgs {
/// generated route for
/// [_i15.OrderPage]
class OrderRoute extends _i27.PageRouteInfo<void> {
const OrderRoute({List<_i27.PageRouteInfo>? children})
class OrderRoute extends _i29.PageRouteInfo<void> {
const OrderRoute({List<_i29.PageRouteInfo>? children})
: super(OrderRoute.name, initialChildren: children);
static const String name = 'OrderRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i27.WrappedRoute(child: const _i15.OrderPage());
return _i29.WrappedRoute(child: const _i15.OrderPage());
},
);
}
/// generated route for
/// [_i16.OutletInformationPage]
class OutletInformationRoute extends _i27.PageRouteInfo<void> {
const OutletInformationRoute({List<_i27.PageRouteInfo>? children})
class OutletInformationRoute extends _i29.PageRouteInfo<void> {
const OutletInformationRoute({List<_i29.PageRouteInfo>? children})
: super(OutletInformationRoute.name, initialChildren: children);
static const String name = 'OutletInformationRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i27.WrappedRoute(child: const _i16.OutletInformationPage());
return _i29.WrappedRoute(child: const _i16.OutletInformationPage());
},
);
}
/// generated route for
/// [_i17.ProductAnalyticPage]
class ProductAnalyticRoute extends _i27.PageRouteInfo<void> {
const ProductAnalyticRoute({List<_i27.PageRouteInfo>? children})
class ProductAnalyticRoute extends _i29.PageRouteInfo<void> {
const ProductAnalyticRoute({List<_i29.PageRouteInfo>? children})
: super(ProductAnalyticRoute.name, initialChildren: children);
static const String name = 'ProductAnalyticRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i27.WrappedRoute(child: const _i17.ProductAnalyticPage());
return _i29.WrappedRoute(child: const _i17.ProductAnalyticPage());
},
);
}
/// generated route for
/// [_i18.ProductPage]
class ProductRoute extends _i27.PageRouteInfo<void> {
const ProductRoute({List<_i27.PageRouteInfo>? children})
class ProductRoute extends _i29.PageRouteInfo<void> {
const ProductRoute({List<_i29.PageRouteInfo>? children})
: super(ProductRoute.name, initialChildren: children);
static const String name = 'ProductRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i27.WrappedRoute(child: const _i18.ProductPage());
return _i29.WrappedRoute(child: const _i18.ProductPage());
},
);
}
/// generated route for
/// [_i19.ProfileChangePasswordPage]
class ProfileChangePasswordRoute extends _i27.PageRouteInfo<void> {
const ProfileChangePasswordRoute({List<_i27.PageRouteInfo>? children})
class ProfileChangePasswordRoute extends _i29.PageRouteInfo<void> {
const ProfileChangePasswordRoute({List<_i29.PageRouteInfo>? children})
: super(ProfileChangePasswordRoute.name, initialChildren: children);
static const String name = 'ProfileChangePasswordRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i27.WrappedRoute(child: const _i19.ProfileChangePasswordPage());
return _i29.WrappedRoute(child: const _i19.ProfileChangePasswordPage());
},
);
}
/// generated route for
/// [_i20.ProfileEditPage]
class ProfileEditRoute extends _i27.PageRouteInfo<ProfileEditRouteArgs> {
class ProfileEditRoute extends _i29.PageRouteInfo<ProfileEditRouteArgs> {
ProfileEditRoute({
_i28.Key? key,
required _i30.User user,
List<_i27.PageRouteInfo>? children,
_i30.Key? key,
required _i32.User user,
List<_i29.PageRouteInfo>? children,
}) : super(
ProfileEditRoute.name,
args: ProfileEditRouteArgs(key: key, user: user),
@@ -468,11 +472,11 @@ class ProfileEditRoute extends _i27.PageRouteInfo<ProfileEditRouteArgs> {
static const String name = 'ProfileEditRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
final args = data.argsAs<ProfileEditRouteArgs>();
return _i27.WrappedRoute(
return _i29.WrappedRoute(
child: _i20.ProfileEditPage(key: args.key, user: args.user),
);
},
@@ -482,9 +486,9 @@ class ProfileEditRoute extends _i27.PageRouteInfo<ProfileEditRouteArgs> {
class ProfileEditRouteArgs {
const ProfileEditRouteArgs({this.key, required this.user});
final _i28.Key? key;
final _i30.Key? key;
final _i30.User user;
final _i32.User user;
@override
String toString() {
@@ -494,96 +498,179 @@ class ProfileEditRouteArgs {
/// generated route for
/// [_i21.ProfilePage]
class ProfileRoute extends _i27.PageRouteInfo<void> {
const ProfileRoute({List<_i27.PageRouteInfo>? children})
class ProfileRoute extends _i29.PageRouteInfo<void> {
const ProfileRoute({List<_i29.PageRouteInfo>? children})
: super(ProfileRoute.name, initialChildren: children);
static const String name = 'ProfileRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i27.WrappedRoute(child: const _i21.ProfilePage());
return _i29.WrappedRoute(child: const _i21.ProfilePage());
},
);
}
/// generated route for
/// [_i22.PurchasePage]
class PurchaseRoute extends _i27.PageRouteInfo<void> {
const PurchaseRoute({List<_i27.PageRouteInfo>? children})
/// [_i22.ProfitSharingDetailPage]
class ProfitSharingDetailRoute
extends _i29.PageRouteInfo<ProfitSharingDetailRouteArgs> {
ProfitSharingDetailRoute({
_i30.Key? key,
required String parentCategoryId,
required String parentCategoryName,
required DateTime dateFrom,
required DateTime dateTo,
List<_i29.PageRouteInfo>? children,
}) : super(
ProfitSharingDetailRoute.name,
args: ProfitSharingDetailRouteArgs(
key: key,
parentCategoryId: parentCategoryId,
parentCategoryName: parentCategoryName,
dateFrom: dateFrom,
dateTo: dateTo,
),
initialChildren: children,
);
static const String name = 'ProfitSharingDetailRoute';
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
final args = data.argsAs<ProfitSharingDetailRouteArgs>();
return _i29.WrappedRoute(
child: _i22.ProfitSharingDetailPage(
key: args.key,
parentCategoryId: args.parentCategoryId,
parentCategoryName: args.parentCategoryName,
dateFrom: args.dateFrom,
dateTo: args.dateTo,
),
);
},
);
}
class ProfitSharingDetailRouteArgs {
const ProfitSharingDetailRouteArgs({
this.key,
required this.parentCategoryId,
required this.parentCategoryName,
required this.dateFrom,
required this.dateTo,
});
final _i30.Key? key;
final String parentCategoryId;
final String parentCategoryName;
final DateTime dateFrom;
final DateTime dateTo;
@override
String toString() {
return 'ProfitSharingDetailRouteArgs{key: $key, parentCategoryId: $parentCategoryId, parentCategoryName: $parentCategoryName, dateFrom: $dateFrom, dateTo: $dateTo}';
}
}
/// generated route for
/// [_i23.ProfitSharingPage]
class ProfitSharingRoute extends _i29.PageRouteInfo<void> {
const ProfitSharingRoute({List<_i29.PageRouteInfo>? children})
: super(ProfitSharingRoute.name, initialChildren: children);
static const String name = 'ProfitSharingRoute';
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i29.WrappedRoute(child: const _i23.ProfitSharingPage());
},
);
}
/// generated route for
/// [_i24.PurchasePage]
class PurchaseRoute extends _i29.PageRouteInfo<void> {
const PurchaseRoute({List<_i29.PageRouteInfo>? children})
: super(PurchaseRoute.name, initialChildren: children);
static const String name = 'PurchaseRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i27.WrappedRoute(child: const _i22.PurchasePage());
return _i29.WrappedRoute(child: const _i24.PurchasePage());
},
);
}
/// generated route for
/// [_i23.ReportPage]
class ReportRoute extends _i27.PageRouteInfo<void> {
const ReportRoute({List<_i27.PageRouteInfo>? children})
/// [_i25.ReportPage]
class ReportRoute extends _i29.PageRouteInfo<void> {
const ReportRoute({List<_i29.PageRouteInfo>? children})
: super(ReportRoute.name, initialChildren: children);
static const String name = 'ReportRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i27.WrappedRoute(child: const _i23.ReportPage());
return _i29.WrappedRoute(child: const _i25.ReportPage());
},
);
}
/// generated route for
/// [_i24.SalesPage]
class SalesRoute extends _i27.PageRouteInfo<void> {
const SalesRoute({List<_i27.PageRouteInfo>? children})
/// [_i26.SalesPage]
class SalesRoute extends _i29.PageRouteInfo<void> {
const SalesRoute({List<_i29.PageRouteInfo>? children})
: super(SalesRoute.name, initialChildren: children);
static const String name = 'SalesRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return _i27.WrappedRoute(child: const _i24.SalesPage());
return _i29.WrappedRoute(child: const _i26.SalesPage());
},
);
}
/// generated route for
/// [_i25.SchedulePage]
class ScheduleRoute extends _i27.PageRouteInfo<void> {
const ScheduleRoute({List<_i27.PageRouteInfo>? children})
/// [_i27.SchedulePage]
class ScheduleRoute extends _i29.PageRouteInfo<void> {
const ScheduleRoute({List<_i29.PageRouteInfo>? children})
: super(ScheduleRoute.name, initialChildren: children);
static const String name = 'ScheduleRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return const _i25.SchedulePage();
return const _i27.SchedulePage();
},
);
}
/// generated route for
/// [_i26.SplashPage]
class SplashRoute extends _i27.PageRouteInfo<void> {
const SplashRoute({List<_i27.PageRouteInfo>? children})
/// [_i28.SplashPage]
class SplashRoute extends _i29.PageRouteInfo<void> {
const SplashRoute({List<_i29.PageRouteInfo>? children})
: super(SplashRoute.name, initialChildren: children);
static const String name = 'SplashRoute';
static _i27.PageInfo page = _i27.PageInfo(
static _i29.PageInfo page = _i29.PageInfo(
name,
builder: (data) {
return const _i26.SplashPage();
return const _i28.SplashPage();
},
);
}
+11 -11
View File
@@ -841,10 +841,10 @@ packages:
dependency: "direct main"
description:
name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867"
url: "https://pub.dev"
source: hosted
version: "0.20.2"
version: "0.20.3"
io:
dependency: transitive
description:
@@ -937,10 +937,10 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
url: "https://pub.dev"
source: hosted
version: "0.12.19"
version: "0.12.20"
material_color_utilities:
dependency: transitive
description:
@@ -953,10 +953,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
url: "https://pub.dev"
source: hosted
version: "1.18.0"
version: "1.19.0"
mime:
dependency: transitive
description:
@@ -1526,10 +1526,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
url: "https://pub.dev"
source: hosted
version: "0.7.11"
version: "0.7.12"
time:
dependency: transitive
description:
@@ -1662,10 +1662,10 @@ packages:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
sha256: f36f9f3be64c6198714492bb455c11056e33e2f85d9a0b676a48301e44fdcf47
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.4.2"
vm_service:
dependency: transitive
description:
@@ -1747,5 +1747,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.10.0-0 <4.0.0"
dart: ">=3.11.0-0 <4.0.0"
flutter: ">=3.29.0"
+1 -1
View File
@@ -3,7 +3,7 @@ description: "A new Flutter project."
publish_to: "none"
version: 1.0.4+10
version: 1.0.4+11
environment:
sdk: ^3.8.1