feat: product analytic range date
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user