feat: change total to amount
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-20 18:22:02 +07:00
parent 7c8b36b65d
commit 24f94fa747
11 changed files with 326 additions and 87 deletions
@@ -50,12 +50,12 @@ class OrderLoaderBloc extends Bloc<OrderLoaderEvent, OrderLoaderState> {
emit(state.copyWith(isFetchingSummary: true));
final results = await Future.wait([
_repository.getTotalCount(
_repository.getTotal(
outletId: state.outletId,
dateFrom: state.dateFrom,
dateTo: state.dateTo,
),
_repository.getTotalCount(
_repository.getTotal(
status: 'pending',
outletId: state.outletId,
dateFrom: state.dateFrom,
@@ -66,8 +66,8 @@ class OrderLoaderBloc extends Bloc<OrderLoaderEvent, OrderLoaderState> {
emit(
state.copyWith(
isFetchingSummary: false,
totalOrder: results[0].getOrElse(() => 0),
totalPendingOrder: results[1].getOrElse(() => 0),
totalOrder: results[0].getOrElse(() => OrderTotal.empty()),
totalPendingOrder: results[1].getOrElse(() => OrderTotal.empty()),
),
);
},
@@ -1094,8 +1094,8 @@ 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;
OrderTotal get totalOrder => throw _privateConstructorUsedError;
OrderTotal get totalPendingOrder => throw _privateConstructorUsedError;
bool get isFetchingSummary => throw _privateConstructorUsedError;
DateTime get dateFrom => throw _privateConstructorUsedError;
DateTime get dateTo => throw _privateConstructorUsedError;
@@ -1123,12 +1123,15 @@ abstract class $OrderLoaderStateCopyWith<$Res> {
bool isFetching,
bool hasReachedMax,
int page,
int totalOrder,
int totalPendingOrder,
OrderTotal totalOrder,
OrderTotal totalPendingOrder,
bool isFetchingSummary,
DateTime dateFrom,
DateTime dateTo,
});
$OrderTotalCopyWith<$Res> get totalOrder;
$OrderTotalCopyWith<$Res> get totalPendingOrder;
}
/// @nodoc
@@ -1197,11 +1200,11 @@ class _$OrderLoaderStateCopyWithImpl<$Res, $Val extends OrderLoaderState>
totalOrder: null == totalOrder
? _value.totalOrder
: totalOrder // ignore: cast_nullable_to_non_nullable
as int,
as OrderTotal,
totalPendingOrder: null == totalPendingOrder
? _value.totalPendingOrder
: totalPendingOrder // ignore: cast_nullable_to_non_nullable
as int,
as OrderTotal,
isFetchingSummary: null == isFetchingSummary
? _value.isFetchingSummary
: isFetchingSummary // ignore: cast_nullable_to_non_nullable
@@ -1218,6 +1221,26 @@ class _$OrderLoaderStateCopyWithImpl<$Res, $Val extends OrderLoaderState>
as $Val,
);
}
/// Create a copy of OrderLoaderState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$OrderTotalCopyWith<$Res> get totalOrder {
return $OrderTotalCopyWith<$Res>(_value.totalOrder, (value) {
return _then(_value.copyWith(totalOrder: value) as $Val);
});
}
/// Create a copy of OrderLoaderState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$OrderTotalCopyWith<$Res> get totalPendingOrder {
return $OrderTotalCopyWith<$Res>(_value.totalPendingOrder, (value) {
return _then(_value.copyWith(totalPendingOrder: value) as $Val);
});
}
}
/// @nodoc
@@ -1238,12 +1261,17 @@ abstract class _$$OrderLoaderStateImplCopyWith<$Res>
bool isFetching,
bool hasReachedMax,
int page,
int totalOrder,
int totalPendingOrder,
OrderTotal totalOrder,
OrderTotal totalPendingOrder,
bool isFetchingSummary,
DateTime dateFrom,
DateTime dateTo,
});
@override
$OrderTotalCopyWith<$Res> get totalOrder;
@override
$OrderTotalCopyWith<$Res> get totalPendingOrder;
}
/// @nodoc
@@ -1311,11 +1339,11 @@ class __$$OrderLoaderStateImplCopyWithImpl<$Res>
totalOrder: null == totalOrder
? _value.totalOrder
: totalOrder // ignore: cast_nullable_to_non_nullable
as int,
as OrderTotal,
totalPendingOrder: null == totalPendingOrder
? _value.totalPendingOrder
: totalPendingOrder // ignore: cast_nullable_to_non_nullable
as int,
as OrderTotal,
isFetchingSummary: null == isFetchingSummary
? _value.isFetchingSummary
: isFetchingSummary // ignore: cast_nullable_to_non_nullable
@@ -1345,8 +1373,8 @@ class _$OrderLoaderStateImpl implements _OrderLoaderState {
this.isFetching = false,
this.hasReachedMax = false,
this.page = 1,
this.totalOrder = 0,
this.totalPendingOrder = 0,
required this.totalOrder,
required this.totalPendingOrder,
this.isFetchingSummary = false,
required this.dateFrom,
required this.dateTo,
@@ -1378,11 +1406,9 @@ class _$OrderLoaderStateImpl implements _OrderLoaderState {
@JsonKey()
final int page;
@override
@JsonKey()
final int totalOrder;
final OrderTotal totalOrder;
@override
@JsonKey()
final int totalPendingOrder;
final OrderTotal totalPendingOrder;
@override
@JsonKey()
final bool isFetchingSummary;
@@ -1464,8 +1490,8 @@ abstract class _OrderLoaderState implements OrderLoaderState {
final bool isFetching,
final bool hasReachedMax,
final int page,
final int totalOrder,
final int totalPendingOrder,
required final OrderTotal totalOrder,
required final OrderTotal totalPendingOrder,
final bool isFetchingSummary,
required final DateTime dateFrom,
required final DateTime dateTo,
@@ -1488,9 +1514,9 @@ abstract class _OrderLoaderState implements OrderLoaderState {
@override
int get page;
@override
int get totalOrder;
OrderTotal get totalOrder;
@override
int get totalPendingOrder;
OrderTotal get totalPendingOrder;
@override
bool get isFetchingSummary;
@override
@@ -11,8 +11,8 @@ class OrderLoaderState with _$OrderLoaderState {
@Default(false) bool isFetching,
@Default(false) bool hasReachedMax,
@Default(1) int page,
@Default(0) int totalOrder,
@Default(0) int totalPendingOrder,
required OrderTotal totalOrder,
required OrderTotal totalPendingOrder,
@Default(false) bool isFetchingSummary,
required DateTime dateFrom,
required DateTime dateTo,
@@ -24,5 +24,7 @@ class OrderLoaderState with _$OrderLoaderState {
dateFrom: DateTime.now(),
dateTo: DateTime.now(),
status: 'pending',
totalOrder: OrderTotal.empty(),
totalPendingOrder: OrderTotal.empty(),
);
}
@@ -159,3 +159,12 @@ class PaymentOrderItem with _$PaymentOrderItem {
updatedAt: '',
);
}
/// Ringkasan order untuk kartu summary: jumlah order + total nominalnya.
@freezed
class OrderTotal with _$OrderTotal {
const factory OrderTotal({required int count, required int amount}) =
_OrderTotal;
factory OrderTotal.empty() => const OrderTotal(count: 0, amount: 0);
}
+149
View File
@@ -1904,6 +1904,155 @@ abstract class _PaymentOrderItem implements PaymentOrderItem {
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$OrderTotal {
int get count => throw _privateConstructorUsedError;
int get amount => throw _privateConstructorUsedError;
/// Create a copy of OrderTotal
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$OrderTotalCopyWith<OrderTotal> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $OrderTotalCopyWith<$Res> {
factory $OrderTotalCopyWith(
OrderTotal value,
$Res Function(OrderTotal) then,
) = _$OrderTotalCopyWithImpl<$Res, OrderTotal>;
@useResult
$Res call({int count, int amount});
}
/// @nodoc
class _$OrderTotalCopyWithImpl<$Res, $Val extends OrderTotal>
implements $OrderTotalCopyWith<$Res> {
_$OrderTotalCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of OrderTotal
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? count = null, Object? amount = null}) {
return _then(
_value.copyWith(
count: null == count
? _value.count
: count // ignore: cast_nullable_to_non_nullable
as int,
amount: null == amount
? _value.amount
: amount // ignore: cast_nullable_to_non_nullable
as int,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$OrderTotalImplCopyWith<$Res>
implements $OrderTotalCopyWith<$Res> {
factory _$$OrderTotalImplCopyWith(
_$OrderTotalImpl value,
$Res Function(_$OrderTotalImpl) then,
) = __$$OrderTotalImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({int count, int amount});
}
/// @nodoc
class __$$OrderTotalImplCopyWithImpl<$Res>
extends _$OrderTotalCopyWithImpl<$Res, _$OrderTotalImpl>
implements _$$OrderTotalImplCopyWith<$Res> {
__$$OrderTotalImplCopyWithImpl(
_$OrderTotalImpl _value,
$Res Function(_$OrderTotalImpl) _then,
) : super(_value, _then);
/// Create a copy of OrderTotal
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({Object? count = null, Object? amount = null}) {
return _then(
_$OrderTotalImpl(
count: null == count
? _value.count
: count // ignore: cast_nullable_to_non_nullable
as int,
amount: null == amount
? _value.amount
: amount // ignore: cast_nullable_to_non_nullable
as int,
),
);
}
}
/// @nodoc
class _$OrderTotalImpl implements _OrderTotal {
const _$OrderTotalImpl({required this.count, required this.amount});
@override
final int count;
@override
final int amount;
@override
String toString() {
return 'OrderTotal(count: $count, amount: $amount)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$OrderTotalImpl &&
(identical(other.count, count) || other.count == count) &&
(identical(other.amount, amount) || other.amount == amount));
}
@override
int get hashCode => Object.hash(runtimeType, count, amount);
/// Create a copy of OrderTotal
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$OrderTotalImplCopyWith<_$OrderTotalImpl> get copyWith =>
__$$OrderTotalImplCopyWithImpl<_$OrderTotalImpl>(this, _$identity);
}
abstract class _OrderTotal implements OrderTotal {
const factory _OrderTotal({
required final int count,
required final int amount,
}) = _$OrderTotalImpl;
@override
int get count;
@override
int get amount;
/// Create a copy of OrderTotal
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$OrderTotalImplCopyWith<_$OrderTotalImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$OrderFailure {
@optionalTypeArgs
@@ -11,7 +11,7 @@ abstract class IOrderRepository {
required DateTime dateTo,
});
Future<Either<OrderFailure, int>> getTotalCount({
Future<Either<OrderFailure, OrderTotal>> getTotal({
String? status,
String? outletId,
required DateTime dateFrom,
@@ -69,17 +69,19 @@ class OrderRemoteDataProvider {
}
/// Backend belum punya endpoint ringkasan order, jadi jumlahnya diambil
/// dari `total_count` list order (limit 1 supaya payload kecil).
Future<DC<OrderFailure, int>> fetchTotalCount({
/// dari `total_count` list order, dan nominalnya dijumlahkan dari
/// `total_amount` tiap order (diambil per halaman sampai habis).
Future<DC<OrderFailure, OrderTotal>> fetchTotal({
String? status,
String? outletId,
required DateTime dateFrom,
required DateTime dateTo,
}) async {
const limit = 100;
try {
Map<String, dynamic> params = {
'page': 1,
'limit': 1,
'limit': limit,
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
};
@@ -92,21 +94,42 @@ class OrderRemoteDataProvider {
params['outlet_id'] = outletId;
}
int page = 1;
int count = 0;
int fetched = 0;
int amount = 0;
while (true) {
final response = await _apiClient.get(
ApiPath.order,
params: params,
params: {...params, 'page': page},
headers: getAuthorizationHeader(),
);
if (response.data['data'] == null) {
return DC.error(OrderFailure.empty());
if (page == 1) return DC.error(OrderFailure.empty());
break;
}
final total = response.data['data']['total_count'] as num? ?? 0;
final data = response.data['data'];
count = (data['total_count'] as num? ?? 0).toInt();
return DC.data(total.toInt());
final orders = (data['orders'] as List? ?? [])
.map((item) => OrderDto.fromJson(item))
.toList();
for (final order in orders) {
amount += order.totalAmount ?? 0;
}
fetched += orders.length;
if (orders.isEmpty || fetched >= count) break;
page++;
}
return DC.data(OrderTotal(count: count, amount: amount));
} on ApiFailure catch (e, s) {
log('fetchOrderTotalCountError', name: _logName, error: e, stackTrace: s);
log('fetchOrderTotalError', name: _logName, error: e, stackTrace: s);
return DC.error(OrderFailure.serverError(e));
}
}
@@ -49,14 +49,14 @@ class OrderRepository implements IOrderRepository {
}
@override
Future<Either<OrderFailure, int>> getTotalCount({
Future<Either<OrderFailure, OrderTotal>> getTotal({
String? status,
String? outletId,
required DateTime dateFrom,
required DateTime dateTo,
}) async {
try {
final result = await _dataProvider.fetchTotalCount(
final result = await _dataProvider.fetchTotal(
status: status,
outletId: outletId,
dateFrom: dateFrom,
@@ -69,7 +69,7 @@ class OrderRepository implements IOrderRepository {
return right(result.data!);
} catch (e, s) {
log('getOrderTotalCountError', name: _logName, error: e, stackTrace: s);
log('getOrderTotalError', name: _logName, error: e, stackTrace: s);
return left(const OrderFailure.unexpectedError());
}
}
@@ -3,6 +3,7 @@ import 'package:shimmer/shimmer.dart';
import '../../../../../common/extension/extension.dart';
import '../../../../../common/theme/theme.dart';
import '../../../../../domain/order/order.dart';
import '../../../../components/spacer/spacer.dart';
import '../../../../components/widgets/particle_card.dart';
@@ -10,8 +11,8 @@ import '../../../../components/widgets/particle_card.dart';
const Color _pendingColor = Color(0xFFFFB300);
class OrderSummary extends StatelessWidget {
final int totalOrder;
final int totalPendingOrder;
final OrderTotal totalOrder;
final OrderTotal totalPendingOrder;
final bool isLoading;
const OrderSummary({
@@ -23,8 +24,8 @@ class OrderSummary extends StatelessWidget {
@override
Widget build(BuildContext context) {
final pendingRatio = totalOrder > 0
? (totalPendingOrder / totalOrder).clamp(0.0, 1.0)
final pendingRatio = totalOrder.count > 0
? (totalPendingOrder.count / totalOrder.count).clamp(0.0, 1.0)
: 0.0;
return ParticleCard(
@@ -39,7 +40,8 @@ class OrderSummary extends StatelessWidget {
Expanded(
child: _SummaryStat(
title: context.lang.total_orders,
value: totalOrder,
value: totalOrder.count,
amount: totalOrder.amount,
icon: Icons.receipt_long_rounded,
iconBackground: AppColor.white.withOpacity(0.2),
isLoading: isLoading,
@@ -53,7 +55,8 @@ class OrderSummary extends StatelessWidget {
Expanded(
child: _SummaryStat(
title: context.lang.pending_order,
value: totalPendingOrder,
value: totalPendingOrder.count,
amount: totalPendingOrder.amount,
icon: Icons.hourglass_top_rounded,
iconBackground: _pendingColor,
isLoading: isLoading,
@@ -83,6 +86,7 @@ class OrderSummary extends StatelessWidget {
class _SummaryStat extends StatelessWidget {
final String title;
final int value;
final int amount;
final IconData icon;
final Color iconBackground;
final bool isLoading;
@@ -90,6 +94,7 @@ class _SummaryStat extends StatelessWidget {
const _SummaryStat({
required this.title,
required this.value,
required this.amount,
required this.icon,
required this.iconBackground,
required this.isLoading,
@@ -125,20 +130,11 @@ class _SummaryStat extends StatelessWidget {
],
),
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
if (isLoading) ...[
_shimmerBox(width: 56, height: 30),
const SpaceHeight(6),
_shimmerBox(width: 96, height: 16),
] else ...[
TweenAnimationBuilder<int>(
tween: IntTween(begin: 0, end: value),
duration: const Duration(milliseconds: 900),
@@ -160,11 +156,45 @@ class _SummaryStat extends StatelessWidget {
),
),
),
const SpaceHeight(6),
TweenAnimationBuilder<int>(
tween: IntTween(begin: 0, end: amount),
duration: const Duration(milliseconds: 900),
curve: Curves.easeOutCubic,
builder: (context, animatedValue, _) => FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(
animatedValue.currencyFormatRp,
maxLines: 1,
style: AppStyle.md.copyWith(
color: AppColor.white.withOpacity(0.9),
fontWeight: FontWeight.w700,
),
),
),
),
],
],
);
}
}
Widget _shimmerBox({required double width, required double height}) {
return Shimmer.fromColors(
baseColor: AppColor.white.withOpacity(0.2),
highlightColor: AppColor.white.withOpacity(0.45),
child: Container(
width: width,
height: height,
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(6),
),
),
);
}
class _PendingRatioBar extends StatelessWidget {
final double ratio;
+21 -21
View File
@@ -213,10 +213,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.1"
version: "1.4.0"
checked_yaml:
dependency: transitive
description:
@@ -841,10 +841,10 @@ packages:
dependency: "direct main"
description:
name: intl
sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867"
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
version: "0.20.3"
version: "0.20.2"
io:
dependency: transitive
description:
@@ -881,26 +881,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
version: "10.0.9"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.dev"
source: hosted
version: "3.0.10"
version: "3.0.9"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
version: "3.0.1"
line_icons:
dependency: "direct main"
description:
@@ -937,26 +937,26 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.20"
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.13.0"
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
version: "1.19.0"
version: "1.16.0"
mime:
dependency: transitive
description:
@@ -1526,10 +1526,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev"
source: hosted
version: "0.7.12"
version: "0.7.4"
time:
dependency: transitive
description:
@@ -1662,10 +1662,10 @@ packages:
dependency: transitive
description:
name: vector_math
sha256: "92b9910f66ed1057fd4da7b040ae7c74cafacf885bdc81be496928d5049b032d"
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.4.3"
version: "2.1.4"
vm_service:
dependency: transitive
description:
@@ -1747,5 +1747,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.11.0-0 <4.0.0"
dart: ">=3.8.1 <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+15
version: 1.0.4+16
environment:
sdk: ^3.8.1