feat: product analytic range date

This commit is contained in:
efrilm
2025-08-18 17:06:41 +07:00
parent ac8c2c0f54
commit a61b19e45c
11 changed files with 554 additions and 309 deletions
@@ -1,16 +1,28 @@
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../application/analytic/product_analytic_loader/product_analytic_loader_bloc.dart';
import '../../../../common/theme/theme.dart';
import '../../../../domain/analytic/analytic.dart';
import '../../../../injection.dart';
import '../../../components/appbar/appbar.dart';
import '../../../components/field/date_range_picker_field.dart';
@RoutePage()
class ProductAnalyticPage extends StatefulWidget {
class ProductAnalyticPage extends StatefulWidget implements AutoRouteWrapper {
const ProductAnalyticPage({super.key});
@override
State<ProductAnalyticPage> createState() => _ProductAnalyticPageState();
@override
Widget wrappedRoute(BuildContext context) => BlocProvider(
create: (context) =>
getIt<ProductAnalyticLoaderBloc>()
..add(ProductAnalyticLoaderEvent.fetched()),
child: this,
);
}
class _ProductAnalyticPageState extends State<ProductAnalyticPage>
@@ -20,66 +32,6 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
// Sample data untuk demo
final ProductAnalytic sampleData = ProductAnalytic(
organizationId: "org_123",
outletId: "outlet_456",
dateFrom: "2024-01-01",
dateTo: "2024-01-31",
data: [
ProductAnalyticData(
productId: "prod_1",
productName: "Nasi Goreng Spesial",
categoryId: "cat_1",
categoryName: "Makanan Utama",
quantitySold: 145,
revenue: 2175000.0,
averagePrice: 15000.0,
orderCount: 98,
),
ProductAnalyticData(
productId: "prod_2",
productName: "Es Teh Manis",
categoryId: "cat_2",
categoryName: "Minuman",
quantitySold: 230,
revenue: 1150000.0,
averagePrice: 5000.0,
orderCount: 180,
),
ProductAnalyticData(
productId: "prod_3",
productName: "Ayam Bakar",
categoryId: "cat_1",
categoryName: "Makanan Utama",
quantitySold: 89,
revenue: 2225000.0,
averagePrice: 25000.0,
orderCount: 75,
),
ProductAnalyticData(
productId: "prod_4",
productName: "Cappuccino",
categoryId: "cat_2",
categoryName: "Minuman",
quantitySold: 67,
revenue: 1005000.0,
averagePrice: 15000.0,
orderCount: 52,
),
ProductAnalyticData(
productId: "prod_5",
productName: "Pisang Goreng",
categoryId: "cat_3",
categoryName: "Snack",
quantitySold: 156,
revenue: 780000.0,
averagePrice: 5000.0,
orderCount: 134,
),
],
);
@override
void initState() {
super.initState();
@@ -126,13 +78,52 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColor.background,
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
_buildSliverAppBar(),
_buildSummarySection(),
_buildProductsList(),
],
body: BlocListener<ProductAnalyticLoaderBloc, ProductAnalyticLoaderState>(
listenWhen: (previous, current) =>
previous.dateFrom != current.dateFrom ||
previous.dateTo != current.dateTo,
listener: (context, state) {
context.read<ProductAnalyticLoaderBloc>().add(
ProductAnalyticLoaderEvent.fetched(),
);
},
child:
BlocBuilder<ProductAnalyticLoaderBloc, ProductAnalyticLoaderState>(
builder: (context, state) {
return CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
_buildSliverAppBar(),
SliverToBoxAdapter(
child: SlideTransition(
position: _slideAnimation,
child: FadeTransition(
opacity: _fadeAnimation,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: DateRangePickerField(
maxDate: DateTime.now(),
startDate: state.dateFrom,
endDate: state.dateTo,
onChanged: (startDate, endDate) {
context.read<ProductAnalyticLoaderBloc>().add(
ProductAnalyticLoaderEvent.rangeDateChanged(
startDate!,
endDate!,
),
);
},
),
),
),
),
),
_buildSummarySection(state.productAnalytic),
_buildProductsList(state.productAnalytic),
],
);
},
),
),
);
}
@@ -161,7 +152,7 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
}
// SUMMARY SECTION
Widget _buildSummarySection() {
Widget _buildSummarySection(ProductAnalytic productAnalytic) {
return SliverToBoxAdapter(
child: FadeTransition(
opacity: _fadeAnimation,
@@ -169,7 +160,7 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
position: _slideAnimation,
child: Container(
margin: const EdgeInsets.all(16),
child: _buildSummaryCards(sampleData.data),
child: _buildSummaryCards(productAnalytic.data),
),
),
),
@@ -177,7 +168,7 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
}
// PRODUCTS LIST
Widget _buildProductsList() {
Widget _buildProductsList(ProductAnalytic productAnalytic) {
return SliverPadding(
padding: const EdgeInsets.all(16),
sliver: SliverList(
@@ -198,12 +189,16 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
offset: Offset(0, 50 * (1 - animValue)),
child: Opacity(
opacity: animValue,
child: _buildProductCard(sampleData.data[index], index),
child: _buildProductCard(
productAnalytic,
productAnalytic.data[index],
index,
),
),
);
},
);
}, childCount: sampleData.data.length),
}, childCount: productAnalytic.data.length),
),
);
}
@@ -330,7 +325,11 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
}
// PRODUCT CARD - NEW CLEAN LAYOUT
Widget _buildProductCard(ProductAnalyticData product, int index) {
Widget _buildProductCard(
ProductAnalytic productAnalytic,
ProductAnalyticData product,
int index,
) {
return TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
duration: Duration(milliseconds: 600 + (index * 100)),
@@ -344,9 +343,9 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
opacity: clampedValue,
child: Container(
margin: EdgeInsets.only(
bottom: index == sampleData.data.length - 1 ? 0 : 16,
bottom: index == productAnalytic.data.length - 1 ? 0 : 16,
),
child: _buildCardContent(product, index),
child: _buildCardContent(productAnalytic, product, index),
),
),
);
@@ -354,7 +353,11 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
);
}
Widget _buildCardContent(ProductAnalyticData product, int index) {
Widget _buildCardContent(
ProductAnalytic productAnalytic,
ProductAnalyticData product,
int index,
) {
return Container(
decoration: BoxDecoration(
color: AppColor.white,
@@ -383,7 +386,7 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
child: Column(
children: [
_buildCardHeader(product, index),
_buildCardBody(product),
_buildCardBody(productAnalytic, product),
],
),
),
@@ -410,8 +413,11 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
);
}
Widget _buildCardBody(ProductAnalyticData product) {
final index = sampleData.data.indexOf(product);
Widget _buildCardBody(
ProductAnalytic productAnalytic,
ProductAnalyticData product,
) {
final index = productAnalytic.data.indexOf(product);
return Padding(
padding: const EdgeInsets.all(24),
@@ -546,7 +552,7 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
),
const SizedBox(height: 8),
Text(
_formatCurrency(product.revenue),
_formatCurrency(product.revenue.toDouble()),
style: _getTextStyle(
AppStyle.xl,
color: AppColor.success,
+1 -1
View File
@@ -321,7 +321,7 @@ class ProductAnalyticRoute extends _i20.PageRouteInfo<void> {
static _i20.PageInfo page = _i20.PageInfo(
name,
builder: (data) {
return const _i12.ProductAnalyticPage();
return _i20.WrappedRoute(child: const _i12.ProductAnalyticPage());
},
);
}