feat: add total count at order page
This commit is contained in:
@@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user