feat: add total count at order page
Build Android APK (split per ABI) / build-apk (push) Waiting to run
Build & Deploy iOS to TestFlight / build-and-deploy (push) Waiting to run

This commit is contained in:
efrilm
2026-10-19 23:04:03 +07:00
parent 243b7e02aa
commit 7c8b36b65d
17 changed files with 652 additions and 27 deletions
+47
View File
@@ -0,0 +1,47 @@
name: Build Android APK (split per ABI)
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build-apk:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "17"
cache: "gradle"
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
# Samain dengan versi di ios.yml biar konsisten.
flutter-version: "3.47.0"
cache: true
- name: Install Dependencies
run: flutter pub get
# ── Build ───────────────────────────────────────────────────────────────
- name: Build APK (split per ABI)
run: flutter build apk --release --split-per-abi
# ── Artifact ────────────────────────────────────────────────────────────
- name: Upload APKs as Artifact
uses: actions/upload-artifact@v4
with:
name: android-apk
path: build/app/outputs/flutter-apk/app-*-release.apk
retention-days: 7
@@ -46,6 +46,31 @@ class OrderLoaderBloc extends Bloc<OrderLoaderEvent, OrderLoaderState> {
emit(newState);
},
summaryFetched: (e) async {
emit(state.copyWith(isFetchingSummary: true));
final results = await Future.wait([
_repository.getTotalCount(
outletId: state.outletId,
dateFrom: state.dateFrom,
dateTo: state.dateTo,
),
_repository.getTotalCount(
status: 'pending',
outletId: state.outletId,
dateFrom: state.dateFrom,
dateTo: state.dateTo,
),
]);
emit(
state.copyWith(
isFetchingSummary: false,
totalOrder: results[0].getOrElse(() => 0),
totalPendingOrder: results[1].getOrElse(() => 0),
),
);
},
);
}
@@ -25,6 +25,7 @@ mixin _$OrderLoaderEvent {
required TResult Function(String search) searchChanged,
required TResult Function(String? outletId) outletChanged,
required TResult Function(bool isRefresh) fetched,
required TResult Function() summaryFetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
@@ -33,6 +34,7 @@ mixin _$OrderLoaderEvent {
TResult? Function(String search)? searchChanged,
TResult? Function(String? outletId)? outletChanged,
TResult? Function(bool isRefresh)? fetched,
TResult? Function()? summaryFetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
@@ -41,6 +43,7 @@ mixin _$OrderLoaderEvent {
TResult Function(String search)? searchChanged,
TResult Function(String? outletId)? outletChanged,
TResult Function(bool isRefresh)? fetched,
TResult Function()? summaryFetched,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
@@ -50,6 +53,7 @@ mixin _$OrderLoaderEvent {
required TResult Function(_SearchChanged value) searchChanged,
required TResult Function(_OutletChanged value) outletChanged,
required TResult Function(_Fetched value) fetched,
required TResult Function(_SummaryFetched value) summaryFetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
@@ -58,6 +62,7 @@ mixin _$OrderLoaderEvent {
TResult? Function(_SearchChanged value)? searchChanged,
TResult? Function(_OutletChanged value)? outletChanged,
TResult? Function(_Fetched value)? fetched,
TResult? Function(_SummaryFetched value)? summaryFetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
@@ -66,6 +71,7 @@ mixin _$OrderLoaderEvent {
TResult Function(_SearchChanged value)? searchChanged,
TResult Function(_OutletChanged value)? outletChanged,
TResult Function(_Fetched value)? fetched,
TResult Function(_SummaryFetched value)? summaryFetched,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
}
@@ -179,6 +185,7 @@ class _$RangeDateChangedImpl implements _RangeDateChanged {
required TResult Function(String search) searchChanged,
required TResult Function(String? outletId) outletChanged,
required TResult Function(bool isRefresh) fetched,
required TResult Function() summaryFetched,
}) {
return rangeDateChanged(dateFrom, dateTo);
}
@@ -191,6 +198,7 @@ class _$RangeDateChangedImpl implements _RangeDateChanged {
TResult? Function(String search)? searchChanged,
TResult? Function(String? outletId)? outletChanged,
TResult? Function(bool isRefresh)? fetched,
TResult? Function()? summaryFetched,
}) {
return rangeDateChanged?.call(dateFrom, dateTo);
}
@@ -203,6 +211,7 @@ class _$RangeDateChangedImpl implements _RangeDateChanged {
TResult Function(String search)? searchChanged,
TResult Function(String? outletId)? outletChanged,
TResult Function(bool isRefresh)? fetched,
TResult Function()? summaryFetched,
required TResult orElse(),
}) {
if (rangeDateChanged != null) {
@@ -219,6 +228,7 @@ class _$RangeDateChangedImpl implements _RangeDateChanged {
required TResult Function(_SearchChanged value) searchChanged,
required TResult Function(_OutletChanged value) outletChanged,
required TResult Function(_Fetched value) fetched,
required TResult Function(_SummaryFetched value) summaryFetched,
}) {
return rangeDateChanged(this);
}
@@ -231,6 +241,7 @@ class _$RangeDateChangedImpl implements _RangeDateChanged {
TResult? Function(_SearchChanged value)? searchChanged,
TResult? Function(_OutletChanged value)? outletChanged,
TResult? Function(_Fetched value)? fetched,
TResult? Function(_SummaryFetched value)? summaryFetched,
}) {
return rangeDateChanged?.call(this);
}
@@ -243,6 +254,7 @@ class _$RangeDateChangedImpl implements _RangeDateChanged {
TResult Function(_SearchChanged value)? searchChanged,
TResult Function(_OutletChanged value)? outletChanged,
TResult Function(_Fetched value)? fetched,
TResult Function(_SummaryFetched value)? summaryFetched,
required TResult orElse(),
}) {
if (rangeDateChanged != null) {
@@ -344,6 +356,7 @@ class _$StatusChangedImpl implements _StatusChanged {
required TResult Function(String search) searchChanged,
required TResult Function(String? outletId) outletChanged,
required TResult Function(bool isRefresh) fetched,
required TResult Function() summaryFetched,
}) {
return statusChanged(status);
}
@@ -356,6 +369,7 @@ class _$StatusChangedImpl implements _StatusChanged {
TResult? Function(String search)? searchChanged,
TResult? Function(String? outletId)? outletChanged,
TResult? Function(bool isRefresh)? fetched,
TResult? Function()? summaryFetched,
}) {
return statusChanged?.call(status);
}
@@ -368,6 +382,7 @@ class _$StatusChangedImpl implements _StatusChanged {
TResult Function(String search)? searchChanged,
TResult Function(String? outletId)? outletChanged,
TResult Function(bool isRefresh)? fetched,
TResult Function()? summaryFetched,
required TResult orElse(),
}) {
if (statusChanged != null) {
@@ -384,6 +399,7 @@ class _$StatusChangedImpl implements _StatusChanged {
required TResult Function(_SearchChanged value) searchChanged,
required TResult Function(_OutletChanged value) outletChanged,
required TResult Function(_Fetched value) fetched,
required TResult Function(_SummaryFetched value) summaryFetched,
}) {
return statusChanged(this);
}
@@ -396,6 +412,7 @@ class _$StatusChangedImpl implements _StatusChanged {
TResult? Function(_SearchChanged value)? searchChanged,
TResult? Function(_OutletChanged value)? outletChanged,
TResult? Function(_Fetched value)? fetched,
TResult? Function(_SummaryFetched value)? summaryFetched,
}) {
return statusChanged?.call(this);
}
@@ -408,6 +425,7 @@ class _$StatusChangedImpl implements _StatusChanged {
TResult Function(_SearchChanged value)? searchChanged,
TResult Function(_OutletChanged value)? outletChanged,
TResult Function(_Fetched value)? fetched,
TResult Function(_SummaryFetched value)? summaryFetched,
required TResult orElse(),
}) {
if (statusChanged != null) {
@@ -505,6 +523,7 @@ class _$SearchChangedImpl implements _SearchChanged {
required TResult Function(String search) searchChanged,
required TResult Function(String? outletId) outletChanged,
required TResult Function(bool isRefresh) fetched,
required TResult Function() summaryFetched,
}) {
return searchChanged(search);
}
@@ -517,6 +536,7 @@ class _$SearchChangedImpl implements _SearchChanged {
TResult? Function(String search)? searchChanged,
TResult? Function(String? outletId)? outletChanged,
TResult? Function(bool isRefresh)? fetched,
TResult? Function()? summaryFetched,
}) {
return searchChanged?.call(search);
}
@@ -529,6 +549,7 @@ class _$SearchChangedImpl implements _SearchChanged {
TResult Function(String search)? searchChanged,
TResult Function(String? outletId)? outletChanged,
TResult Function(bool isRefresh)? fetched,
TResult Function()? summaryFetched,
required TResult orElse(),
}) {
if (searchChanged != null) {
@@ -545,6 +566,7 @@ class _$SearchChangedImpl implements _SearchChanged {
required TResult Function(_SearchChanged value) searchChanged,
required TResult Function(_OutletChanged value) outletChanged,
required TResult Function(_Fetched value) fetched,
required TResult Function(_SummaryFetched value) summaryFetched,
}) {
return searchChanged(this);
}
@@ -557,6 +579,7 @@ class _$SearchChangedImpl implements _SearchChanged {
TResult? Function(_SearchChanged value)? searchChanged,
TResult? Function(_OutletChanged value)? outletChanged,
TResult? Function(_Fetched value)? fetched,
TResult? Function(_SummaryFetched value)? summaryFetched,
}) {
return searchChanged?.call(this);
}
@@ -569,6 +592,7 @@ class _$SearchChangedImpl implements _SearchChanged {
TResult Function(_SearchChanged value)? searchChanged,
TResult Function(_OutletChanged value)? outletChanged,
TResult Function(_Fetched value)? fetched,
TResult Function(_SummaryFetched value)? summaryFetched,
required TResult orElse(),
}) {
if (searchChanged != null) {
@@ -667,6 +691,7 @@ class _$OutletChangedImpl implements _OutletChanged {
required TResult Function(String search) searchChanged,
required TResult Function(String? outletId) outletChanged,
required TResult Function(bool isRefresh) fetched,
required TResult Function() summaryFetched,
}) {
return outletChanged(outletId);
}
@@ -679,6 +704,7 @@ class _$OutletChangedImpl implements _OutletChanged {
TResult? Function(String search)? searchChanged,
TResult? Function(String? outletId)? outletChanged,
TResult? Function(bool isRefresh)? fetched,
TResult? Function()? summaryFetched,
}) {
return outletChanged?.call(outletId);
}
@@ -691,6 +717,7 @@ class _$OutletChangedImpl implements _OutletChanged {
TResult Function(String search)? searchChanged,
TResult Function(String? outletId)? outletChanged,
TResult Function(bool isRefresh)? fetched,
TResult Function()? summaryFetched,
required TResult orElse(),
}) {
if (outletChanged != null) {
@@ -707,6 +734,7 @@ class _$OutletChangedImpl implements _OutletChanged {
required TResult Function(_SearchChanged value) searchChanged,
required TResult Function(_OutletChanged value) outletChanged,
required TResult Function(_Fetched value) fetched,
required TResult Function(_SummaryFetched value) summaryFetched,
}) {
return outletChanged(this);
}
@@ -719,6 +747,7 @@ class _$OutletChangedImpl implements _OutletChanged {
TResult? Function(_SearchChanged value)? searchChanged,
TResult? Function(_OutletChanged value)? outletChanged,
TResult? Function(_Fetched value)? fetched,
TResult? Function(_SummaryFetched value)? summaryFetched,
}) {
return outletChanged?.call(this);
}
@@ -731,6 +760,7 @@ class _$OutletChangedImpl implements _OutletChanged {
TResult Function(_SearchChanged value)? searchChanged,
TResult Function(_OutletChanged value)? outletChanged,
TResult Function(_Fetched value)? fetched,
TResult Function(_SummaryFetched value)? summaryFetched,
required TResult orElse(),
}) {
if (outletChanged != null) {
@@ -830,6 +860,7 @@ class _$FetchedImpl implements _Fetched {
required TResult Function(String search) searchChanged,
required TResult Function(String? outletId) outletChanged,
required TResult Function(bool isRefresh) fetched,
required TResult Function() summaryFetched,
}) {
return fetched(isRefresh);
}
@@ -842,6 +873,7 @@ class _$FetchedImpl implements _Fetched {
TResult? Function(String search)? searchChanged,
TResult? Function(String? outletId)? outletChanged,
TResult? Function(bool isRefresh)? fetched,
TResult? Function()? summaryFetched,
}) {
return fetched?.call(isRefresh);
}
@@ -854,6 +886,7 @@ class _$FetchedImpl implements _Fetched {
TResult Function(String search)? searchChanged,
TResult Function(String? outletId)? outletChanged,
TResult Function(bool isRefresh)? fetched,
TResult Function()? summaryFetched,
required TResult orElse(),
}) {
if (fetched != null) {
@@ -870,6 +903,7 @@ class _$FetchedImpl implements _Fetched {
required TResult Function(_SearchChanged value) searchChanged,
required TResult Function(_OutletChanged value) outletChanged,
required TResult Function(_Fetched value) fetched,
required TResult Function(_SummaryFetched value) summaryFetched,
}) {
return fetched(this);
}
@@ -882,6 +916,7 @@ class _$FetchedImpl implements _Fetched {
TResult? Function(_SearchChanged value)? searchChanged,
TResult? Function(_OutletChanged value)? outletChanged,
TResult? Function(_Fetched value)? fetched,
TResult? Function(_SummaryFetched value)? summaryFetched,
}) {
return fetched?.call(this);
}
@@ -894,6 +929,7 @@ class _$FetchedImpl implements _Fetched {
TResult Function(_SearchChanged value)? searchChanged,
TResult Function(_OutletChanged value)? outletChanged,
TResult Function(_Fetched value)? fetched,
TResult Function(_SummaryFetched value)? summaryFetched,
required TResult orElse(),
}) {
if (fetched != null) {
@@ -915,6 +951,138 @@ abstract class _Fetched implements OrderLoaderEvent {
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$SummaryFetchedImplCopyWith<$Res> {
factory _$$SummaryFetchedImplCopyWith(
_$SummaryFetchedImpl value,
$Res Function(_$SummaryFetchedImpl) then,
) = __$$SummaryFetchedImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$SummaryFetchedImplCopyWithImpl<$Res>
extends _$OrderLoaderEventCopyWithImpl<$Res, _$SummaryFetchedImpl>
implements _$$SummaryFetchedImplCopyWith<$Res> {
__$$SummaryFetchedImplCopyWithImpl(
_$SummaryFetchedImpl _value,
$Res Function(_$SummaryFetchedImpl) _then,
) : super(_value, _then);
/// Create a copy of OrderLoaderEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$SummaryFetchedImpl implements _SummaryFetched {
const _$SummaryFetchedImpl();
@override
String toString() {
return 'OrderLoaderEvent.summaryFetched()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$SummaryFetchedImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(DateTime dateFrom, DateTime dateTo)
rangeDateChanged,
required TResult Function(String status) statusChanged,
required TResult Function(String search) searchChanged,
required TResult Function(String? outletId) outletChanged,
required TResult Function(bool isRefresh) fetched,
required TResult Function() summaryFetched,
}) {
return summaryFetched();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
TResult? Function(String status)? statusChanged,
TResult? Function(String search)? searchChanged,
TResult? Function(String? outletId)? outletChanged,
TResult? Function(bool isRefresh)? fetched,
TResult? Function()? summaryFetched,
}) {
return summaryFetched?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(DateTime dateFrom, DateTime dateTo)? rangeDateChanged,
TResult Function(String status)? statusChanged,
TResult Function(String search)? searchChanged,
TResult Function(String? outletId)? outletChanged,
TResult Function(bool isRefresh)? fetched,
TResult Function()? summaryFetched,
required TResult orElse(),
}) {
if (summaryFetched != null) {
return summaryFetched();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_RangeDateChanged value) rangeDateChanged,
required TResult Function(_StatusChanged value) statusChanged,
required TResult Function(_SearchChanged value) searchChanged,
required TResult Function(_OutletChanged value) outletChanged,
required TResult Function(_Fetched value) fetched,
required TResult Function(_SummaryFetched value) summaryFetched,
}) {
return summaryFetched(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_RangeDateChanged value)? rangeDateChanged,
TResult? Function(_StatusChanged value)? statusChanged,
TResult? Function(_SearchChanged value)? searchChanged,
TResult? Function(_OutletChanged value)? outletChanged,
TResult? Function(_Fetched value)? fetched,
TResult? Function(_SummaryFetched value)? summaryFetched,
}) {
return summaryFetched?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_RangeDateChanged value)? rangeDateChanged,
TResult Function(_StatusChanged value)? statusChanged,
TResult Function(_SearchChanged value)? searchChanged,
TResult Function(_OutletChanged value)? outletChanged,
TResult Function(_Fetched value)? fetched,
TResult Function(_SummaryFetched value)? summaryFetched,
required TResult orElse(),
}) {
if (summaryFetched != null) {
return summaryFetched(this);
}
return orElse();
}
}
abstract class _SummaryFetched implements OrderLoaderEvent {
const factory _SummaryFetched() = _$SummaryFetchedImpl;
}
/// @nodoc
mixin _$OrderLoaderState {
List<Order> get orders => throw _privateConstructorUsedError;
@@ -926,6 +1094,9 @@ mixin _$OrderLoaderState {
bool get isFetching => throw _privateConstructorUsedError;
bool get hasReachedMax => throw _privateConstructorUsedError;
int get page => throw _privateConstructorUsedError;
int get totalOrder => throw _privateConstructorUsedError;
int get totalPendingOrder => throw _privateConstructorUsedError;
bool get isFetchingSummary => throw _privateConstructorUsedError;
DateTime get dateFrom => throw _privateConstructorUsedError;
DateTime get dateTo => throw _privateConstructorUsedError;
@@ -952,6 +1123,9 @@ abstract class $OrderLoaderStateCopyWith<$Res> {
bool isFetching,
bool hasReachedMax,
int page,
int totalOrder,
int totalPendingOrder,
bool isFetchingSummary,
DateTime dateFrom,
DateTime dateTo,
});
@@ -980,6 +1154,9 @@ class _$OrderLoaderStateCopyWithImpl<$Res, $Val extends OrderLoaderState>
Object? isFetching = null,
Object? hasReachedMax = null,
Object? page = null,
Object? totalOrder = null,
Object? totalPendingOrder = null,
Object? isFetchingSummary = null,
Object? dateFrom = null,
Object? dateTo = null,
}) {
@@ -1017,6 +1194,18 @@ class _$OrderLoaderStateCopyWithImpl<$Res, $Val extends OrderLoaderState>
? _value.page
: page // ignore: cast_nullable_to_non_nullable
as int,
totalOrder: null == totalOrder
? _value.totalOrder
: totalOrder // ignore: cast_nullable_to_non_nullable
as int,
totalPendingOrder: null == totalPendingOrder
? _value.totalPendingOrder
: totalPendingOrder // ignore: cast_nullable_to_non_nullable
as int,
isFetchingSummary: null == isFetchingSummary
? _value.isFetchingSummary
: isFetchingSummary // ignore: cast_nullable_to_non_nullable
as bool,
dateFrom: null == dateFrom
? _value.dateFrom
: dateFrom // ignore: cast_nullable_to_non_nullable
@@ -1049,6 +1238,9 @@ abstract class _$$OrderLoaderStateImplCopyWith<$Res>
bool isFetching,
bool hasReachedMax,
int page,
int totalOrder,
int totalPendingOrder,
bool isFetchingSummary,
DateTime dateFrom,
DateTime dateTo,
});
@@ -1076,6 +1268,9 @@ class __$$OrderLoaderStateImplCopyWithImpl<$Res>
Object? isFetching = null,
Object? hasReachedMax = null,
Object? page = null,
Object? totalOrder = null,
Object? totalPendingOrder = null,
Object? isFetchingSummary = null,
Object? dateFrom = null,
Object? dateTo = null,
}) {
@@ -1113,6 +1308,18 @@ class __$$OrderLoaderStateImplCopyWithImpl<$Res>
? _value.page
: page // ignore: cast_nullable_to_non_nullable
as int,
totalOrder: null == totalOrder
? _value.totalOrder
: totalOrder // ignore: cast_nullable_to_non_nullable
as int,
totalPendingOrder: null == totalPendingOrder
? _value.totalPendingOrder
: totalPendingOrder // ignore: cast_nullable_to_non_nullable
as int,
isFetchingSummary: null == isFetchingSummary
? _value.isFetchingSummary
: isFetchingSummary // ignore: cast_nullable_to_non_nullable
as bool,
dateFrom: null == dateFrom
? _value.dateFrom
: dateFrom // ignore: cast_nullable_to_non_nullable
@@ -1138,6 +1345,9 @@ class _$OrderLoaderStateImpl implements _OrderLoaderState {
this.isFetching = false,
this.hasReachedMax = false,
this.page = 1,
this.totalOrder = 0,
this.totalPendingOrder = 0,
this.isFetchingSummary = false,
required this.dateFrom,
required this.dateTo,
}) : _orders = orders;
@@ -1168,13 +1378,22 @@ class _$OrderLoaderStateImpl implements _OrderLoaderState {
@JsonKey()
final int page;
@override
@JsonKey()
final int totalOrder;
@override
@JsonKey()
final int totalPendingOrder;
@override
@JsonKey()
final bool isFetchingSummary;
@override
final DateTime dateFrom;
@override
final DateTime dateTo;
@override
String toString() {
return 'OrderLoaderState(orders: $orders, failureOptionOrder: $failureOptionOrder, status: $status, search: $search, outletId: $outletId, isFetching: $isFetching, hasReachedMax: $hasReachedMax, page: $page, dateFrom: $dateFrom, dateTo: $dateTo)';
return 'OrderLoaderState(orders: $orders, failureOptionOrder: $failureOptionOrder, status: $status, search: $search, outletId: $outletId, isFetching: $isFetching, hasReachedMax: $hasReachedMax, page: $page, totalOrder: $totalOrder, totalPendingOrder: $totalPendingOrder, isFetchingSummary: $isFetchingSummary, dateFrom: $dateFrom, dateTo: $dateTo)';
}
@override
@@ -1194,6 +1413,12 @@ class _$OrderLoaderStateImpl implements _OrderLoaderState {
(identical(other.hasReachedMax, hasReachedMax) ||
other.hasReachedMax == hasReachedMax) &&
(identical(other.page, page) || other.page == page) &&
(identical(other.totalOrder, totalOrder) ||
other.totalOrder == totalOrder) &&
(identical(other.totalPendingOrder, totalPendingOrder) ||
other.totalPendingOrder == totalPendingOrder) &&
(identical(other.isFetchingSummary, isFetchingSummary) ||
other.isFetchingSummary == isFetchingSummary) &&
(identical(other.dateFrom, dateFrom) ||
other.dateFrom == dateFrom) &&
(identical(other.dateTo, dateTo) || other.dateTo == dateTo));
@@ -1210,6 +1435,9 @@ class _$OrderLoaderStateImpl implements _OrderLoaderState {
isFetching,
hasReachedMax,
page,
totalOrder,
totalPendingOrder,
isFetchingSummary,
dateFrom,
dateTo,
);
@@ -1236,6 +1464,9 @@ abstract class _OrderLoaderState implements OrderLoaderState {
final bool isFetching,
final bool hasReachedMax,
final int page,
final int totalOrder,
final int totalPendingOrder,
final bool isFetchingSummary,
required final DateTime dateFrom,
required final DateTime dateTo,
}) = _$OrderLoaderStateImpl;
@@ -1257,6 +1488,12 @@ abstract class _OrderLoaderState implements OrderLoaderState {
@override
int get page;
@override
int get totalOrder;
@override
int get totalPendingOrder;
@override
bool get isFetchingSummary;
@override
DateTime get dateFrom;
@override
DateTime get dateTo;
@@ -12,4 +12,5 @@ class OrderLoaderEvent with _$OrderLoaderEvent {
_OutletChanged;
const factory OrderLoaderEvent.fetched({@Default(false) bool isRefresh}) =
_Fetched;
const factory OrderLoaderEvent.summaryFetched() = _SummaryFetched;
}
@@ -11,6 +11,9 @@ class OrderLoaderState with _$OrderLoaderState {
@Default(false) bool isFetching,
@Default(false) bool hasReachedMax,
@Default(1) int page,
@Default(0) int totalOrder,
@Default(0) int totalPendingOrder,
@Default(false) bool isFetchingSummary,
required DateTime dateFrom,
required DateTime dateTo,
}) = _OrderLoaderState;
@@ -10,4 +10,11 @@ abstract class IOrderRepository {
required DateTime dateFrom,
required DateTime dateTo,
});
Future<Either<OrderFailure, int>> getTotalCount({
String? status,
String? outletId,
required DateTime dateFrom,
required DateTime dateTo,
});
}
@@ -43,9 +43,9 @@ class OrderRemoteDataProvider {
params['search'] = search;
}
// if (outletId != null) {
// params['outlet_id'] = outletId;
// }
if (outletId != null) {
params['outlet_id'] = outletId;
}
final response = await _apiClient.get(
ApiPath.order,
@@ -67,4 +67,47 @@ class OrderRemoteDataProvider {
return DC.error(OrderFailure.serverError(e));
}
}
/// Backend belum punya endpoint ringkasan order, jadi jumlahnya diambil
/// dari `total_count` list order (limit 1 supaya payload kecil).
Future<DC<OrderFailure, int>> fetchTotalCount({
String? status,
String? outletId,
required DateTime dateFrom,
required DateTime dateTo,
}) async {
try {
Map<String, dynamic> params = {
'page': 1,
'limit': 1,
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
};
if (status != null && status.isNotEmpty) {
params['status'] = status;
}
if (outletId != null) {
params['outlet_id'] = outletId;
}
final response = await _apiClient.get(
ApiPath.order,
params: params,
headers: getAuthorizationHeader(),
);
if (response.data['data'] == null) {
return DC.error(OrderFailure.empty());
}
final total = response.data['data']['total_count'] as num? ?? 0;
return DC.data(total.toInt());
} on ApiFailure catch (e, s) {
log('fetchOrderTotalCountError', name: _logName, error: e, stackTrace: s);
return DC.error(OrderFailure.serverError(e));
}
}
}
@@ -4,16 +4,15 @@ import 'package:dartz/dartz.dart' hide Order;
import 'package:injectable/injectable.dart' hide Order;
import '../../../domain/order/order.dart';
import '../../outlet/datasource/local_data_provider.dart';
import '../datasource/remote_data_provider.dart';
/// `outletId` null = semua outlet (sesuai pilihan "Semua Outlet" di filter).
@Injectable(as: IOrderRepository)
class OrderRepository implements IOrderRepository {
final OrderRemoteDataProvider _dataProvider;
final OutletLocalDataProvider _outletLocalDataProvider;
final String _logName = 'OrderRepository';
OrderRepository(this._dataProvider, this._outletLocalDataProvider);
OrderRepository(this._dataProvider);
@override
Future<Either<OrderFailure, List<Order>>> get({
@@ -26,15 +25,12 @@ class OrderRepository implements IOrderRepository {
required DateTime dateTo,
}) async {
try {
final resolvedOutletId =
outletId ?? _outletLocalDataProvider.getSelectedOutletId();
final result = await _dataProvider.fetch(
page: page,
limit: limit,
status: status,
search: search,
outletId: resolvedOutletId,
outletId: outletId,
dateFrom: dateFrom,
dateTo: dateTo,
);
@@ -51,4 +47,30 @@ class OrderRepository implements IOrderRepository {
return left(const OrderFailure.unexpectedError());
}
}
@override
Future<Either<OrderFailure, int>> getTotalCount({
String? status,
String? outletId,
required DateTime dateFrom,
required DateTime dateTo,
}) async {
try {
final result = await _dataProvider.fetchTotalCount(
status: status,
outletId: outletId,
dateFrom: dateFrom,
dateTo: dateTo,
);
if (result.hasError) {
return left(result.error!);
}
return right(result.data!);
} catch (e, s) {
log('getOrderTotalCountError', name: _logName, error: e, stackTrace: s);
return left(const OrderFailure.unexpectedError());
}
}
}
+3 -6
View File
@@ -207,6 +207,9 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i48.ICustomerRepository>(
() => _i550.CustomerRepository(gh<_i1006.CustomerRemoteDataProvider>()),
);
gh.factory<_i219.IOrderRepository>(
() => _i641.OrderRepository(gh<_i130.OrderRemoteDataProvider>()),
);
gh.factory<_i49.IAuthRepository>(
() => _i1035.AuthRepository(
gh<_i991.AuthLocalDataProvider>(),
@@ -225,12 +228,6 @@ extension GetItInjectableX on _i174.GetIt {
gh.factory<_i419.IProductRepository>(
() => _i121.ProductRepository(gh<_i823.ProductRemoteDataProvider>()),
);
gh.factory<_i219.IOrderRepository>(
() => _i641.OrderRepository(
gh<_i130.OrderRemoteDataProvider>(),
gh<_i850.OutletLocalDataProvider>(),
),
);
gh.factory<_i972.CustomerLoaderBloc>(
() => _i972.CustomerLoaderBloc(gh<_i48.ICustomerRepository>()),
);
+10 -1
View File
@@ -621,5 +621,14 @@
"status_critical": "Unhealthy",
"@status_critical": {},
"tap_row_for_detail": "Tap a card to see sub category and product details",
"@tap_row_for_detail": {}
"@tap_row_for_detail": {},
"order_unpaid_ratio": "{percent}% of orders still awaiting payment",
"@order_unpaid_ratio": {
"placeholders": {
"percent": {
"type": "String",
"example": "25"
}
}
}
}
+10 -1
View File
@@ -621,5 +621,14 @@
"status_critical": "Tidak Sehat",
"@status_critical": {},
"tap_row_for_detail": "Ketuk kartu untuk melihat rincian sub kategori dan produk",
"@tap_row_for_detail": {}
"@tap_row_for_detail": {},
"order_unpaid_ratio": "{percent}% pesanan masih menunggu pembayaran",
"@order_unpaid_ratio": {
"placeholders": {
"percent": {
"type": "String",
"example": "25"
}
}
}
}
+6
View File
@@ -1750,6 +1750,12 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Tap a card to see sub category and product details'**
String get tap_row_for_detail;
/// No description provided for @order_unpaid_ratio.
///
/// In en, this message translates to:
/// **'{percent}% of orders still awaiting payment'**
String order_unpaid_ratio(String percent);
}
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
+5
View File
@@ -855,4 +855,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get tap_row_for_detail => 'Tap a card to see sub category and product details';
@override
String order_unpaid_ratio(String percent) {
return '$percent% of orders still awaiting payment';
}
}
+5
View File
@@ -855,4 +855,9 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get tap_row_for_detail => 'Ketuk kartu untuk melihat rincian sub kategori dan produk';
@override
String order_unpaid_ratio(String percent) {
return '$percent% pesanan masih menunggu pembayaran';
}
}
@@ -14,6 +14,7 @@ import '../../../components/appbar/appbar.dart';
import '../../../components/widgets/empty_widget.dart';
import '../../../router/app_router.gr.dart';
import 'widgets/filter_header_delegate.dart';
import 'widgets/order_summary.dart';
import 'widgets/order_tile.dart';
@RoutePage()
@@ -29,7 +30,8 @@ class OrderPage extends StatefulWidget implements AutoRouteWrapper {
BlocProvider(
create: (_) =>
getIt<OrderLoaderBloc>()
..add(OrderLoaderEvent.fetched(isRefresh: true)),
..add(OrderLoaderEvent.fetched(isRefresh: true))
..add(const OrderLoaderEvent.summaryFetched()),
),
BlocProvider(
create: (_) =>
@@ -192,9 +194,9 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
BlocListener<OrderLoaderBloc, OrderLoaderState>(
listenWhen: (p, c) => p.outletId != c.outletId,
listener: (context, state) {
context.read<OrderLoaderBloc>().add(
OrderLoaderEvent.fetched(isRefresh: true),
);
context.read<OrderLoaderBloc>()
..add(OrderLoaderEvent.fetched(isRefresh: true))
..add(const OrderLoaderEvent.summaryFetched());
},
),
BlocListener<OrderLoaderBloc, OrderLoaderState>(
@@ -210,9 +212,9 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
previous.dateFrom != current.dateFrom ||
previous.dateTo != current.dateTo,
listener: (context, state) {
context.read<OrderLoaderBloc>().add(
OrderLoaderEvent.fetched(isRefresh: true),
);
context.read<OrderLoaderBloc>()
..add(OrderLoaderEvent.fetched(isRefresh: true))
..add(const OrderLoaderEvent.summaryFetched());
},
),
],
@@ -301,6 +303,14 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
position: _slideAnimation,
child: Column(
children: [
OrderSummary(
totalOrder: state.totalOrder,
totalPendingOrder:
state.totalPendingOrder,
isLoading: state.isFetchingSummary,
),
const SizedBox(height: 16),
// Show filtered transaction count
if (state.status != 'all' &&
!state.isFetching)
@@ -0,0 +1,199 @@
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import '../../../../../common/extension/extension.dart';
import '../../../../../common/theme/theme.dart';
import '../../../../components/spacer/spacer.dart';
import '../../../../components/widgets/particle_card.dart';
/// Aksen pesanan pending — amber supaya kontras di atas gradient merah.
const Color _pendingColor = Color(0xFFFFB300);
class OrderSummary extends StatelessWidget {
final int totalOrder;
final int totalPendingOrder;
final bool isLoading;
const OrderSummary({
super.key,
required this.totalOrder,
required this.totalPendingOrder,
this.isLoading = false,
});
@override
Widget build(BuildContext context) {
final pendingRatio = totalOrder > 0
? (totalPendingOrder / totalOrder).clamp(0.0, 1.0)
: 0.0;
return ParticleCard(
borderRadius: 18,
decorationOpacity: 0.8,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IntrinsicHeight(
child: Row(
children: [
Expanded(
child: _SummaryStat(
title: context.lang.total_orders,
value: totalOrder,
icon: Icons.receipt_long_rounded,
iconBackground: AppColor.white.withOpacity(0.2),
isLoading: isLoading,
),
),
VerticalDivider(
width: 28,
thickness: 1,
color: AppColor.white.withOpacity(0.25),
),
Expanded(
child: _SummaryStat(
title: context.lang.pending_order,
value: totalPendingOrder,
icon: Icons.hourglass_top_rounded,
iconBackground: _pendingColor,
isLoading: isLoading,
),
),
],
),
),
const SpaceHeight(16),
_PendingRatioBar(ratio: pendingRatio),
const SpaceHeight(8),
Text(
context.lang.order_unpaid_ratio(
(pendingRatio * 100).round().toString(),
),
style: AppStyle.xs.copyWith(
color: AppColor.white.withOpacity(0.8),
fontWeight: FontWeight.w500,
),
),
],
),
);
}
}
class _SummaryStat extends StatelessWidget {
final String title;
final int value;
final IconData icon;
final Color iconBackground;
final bool isLoading;
const _SummaryStat({
required this.title,
required this.value,
required this.icon,
required this.iconBackground,
required this.isLoading,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(7),
decoration: BoxDecoration(
color: iconBackground,
borderRadius: BorderRadius.circular(10),
),
child: Icon(icon, color: AppColor.white, size: 16),
),
const SpaceWidth(8),
Expanded(
child: Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: AppStyle.sm.copyWith(
color: AppColor.white.withOpacity(0.9),
fontWeight: FontWeight.w600,
),
),
),
],
),
const SpaceHeight(12),
if (isLoading)
Shimmer.fromColors(
baseColor: AppColor.white.withOpacity(0.2),
highlightColor: AppColor.white.withOpacity(0.45),
child: Container(
width: 56,
height: 30,
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(6),
),
),
)
else
TweenAnimationBuilder<int>(
tween: IntTween(begin: 0, end: value),
duration: const Duration(milliseconds: 900),
curve: Curves.easeOutCubic,
builder: (context, animatedValue, _) => Text(
animatedValue.thousandFormat,
style: AppStyle.h1.copyWith(
color: AppColor.white,
fontWeight: FontWeight.w900,
fontSize: 28,
height: 1.1,
shadows: [
Shadow(
color: Colors.black.withOpacity(0.25),
blurRadius: 6,
offset: const Offset(0, 2),
),
],
),
),
),
],
);
}
}
class _PendingRatioBar extends StatelessWidget {
final double ratio;
const _PendingRatioBar({required this.ratio});
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Container(
height: 6,
color: AppColor.white.withOpacity(0.2),
alignment: Alignment.centerLeft,
child: TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0, end: ratio),
duration: const Duration(milliseconds: 900),
curve: Curves.easeOutCubic,
builder: (context, value, _) => FractionallySizedBox(
widthFactor: value,
heightFactor: 1,
child: Container(
decoration: BoxDecoration(
color: _pendingColor,
borderRadius: BorderRadius.circular(4),
),
),
),
),
),
);
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ description: "A new Flutter project."
publish_to: "none"
version: 1.0.4+14
version: 1.0.4+15
environment:
sdk: ^3.8.1