Compare commits
No commits in common. "bdb2c0ba521ed123f322058e73e891851a89142a" and "2a457ea5f67bfe5fdc97d0136a947eaec4b95f0b" have entirely different histories.
bdb2c0ba52
...
2a457ea5f6
@ -5,9 +5,7 @@ import 'package:dio/dio.dart';
|
|||||||
import 'package:enaklo_pos/core/constants/variables.dart';
|
import 'package:enaklo_pos/core/constants/variables.dart';
|
||||||
import 'package:enaklo_pos/core/network/dio_client.dart';
|
import 'package:enaklo_pos/core/network/dio_client.dart';
|
||||||
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
|
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
|
||||||
import 'package:enaklo_pos/data/models/response/dashboard_analytic_response_model.dart';
|
|
||||||
import 'package:enaklo_pos/data/models/response/payment_method_analytic_response_model.dart';
|
import 'package:enaklo_pos/data/models/response/payment_method_analytic_response_model.dart';
|
||||||
import 'package:enaklo_pos/data/models/response/product_analytic_response_model.dart';
|
|
||||||
import 'package:enaklo_pos/data/models/response/sales_analytic_response_model.dart';
|
import 'package:enaklo_pos/data/models/response/sales_analytic_response_model.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
@ -81,72 +79,4 @@ class AnalyticRemoteDatasource {
|
|||||||
return left('Unexpected error occurred');
|
return left('Unexpected error occurred');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<String, ProductAnalyticResponseModel>> getProduct({
|
|
||||||
required DateTime dateFrom,
|
|
||||||
required DateTime dateTo,
|
|
||||||
}) async {
|
|
||||||
final authData = await AuthLocalDataSource().getAuthData();
|
|
||||||
final headers = {
|
|
||||||
'Authorization': 'Bearer ${authData.token}',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
final response = await dio.get(
|
|
||||||
'${Variables.baseUrl}/api/v1/analytics/products',
|
|
||||||
queryParameters: {
|
|
||||||
'date_from': DateFormat('dd-MM-yyyy').format(dateFrom),
|
|
||||||
'date_to': DateFormat('dd-MM-yyyy').format(dateTo),
|
|
||||||
},
|
|
||||||
options: Options(headers: headers),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return right(ProductAnalyticResponseModel.fromMap(response.data));
|
|
||||||
} else {
|
|
||||||
return left('Terjadi Kesalahan, Coba lagi nanti.');
|
|
||||||
}
|
|
||||||
} on DioException catch (e) {
|
|
||||||
log('Dio error: ${e.message}');
|
|
||||||
return left(e.response?.data.toString() ?? e.message ?? 'Unknown error');
|
|
||||||
} catch (e) {
|
|
||||||
log('Unexpected error: $e');
|
|
||||||
return left('Unexpected error occurred');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Either<String, DashboardAnalyticResponseModel>> getDashboard({
|
|
||||||
required DateTime dateFrom,
|
|
||||||
required DateTime dateTo,
|
|
||||||
}) async {
|
|
||||||
final authData = await AuthLocalDataSource().getAuthData();
|
|
||||||
final headers = {
|
|
||||||
'Authorization': 'Bearer ${authData.token}',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
final response = await dio.get(
|
|
||||||
'${Variables.baseUrl}/api/v1/analytics/dashboard',
|
|
||||||
queryParameters: {
|
|
||||||
'date_from': DateFormat('dd-MM-yyyy').format(dateFrom),
|
|
||||||
'date_to': DateFormat('dd-MM-yyyy').format(dateTo),
|
|
||||||
},
|
|
||||||
options: Options(headers: headers),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return right(DashboardAnalyticResponseModel.fromMap(response.data));
|
|
||||||
} else {
|
|
||||||
return left('Terjadi Kesalahan, Coba lagi nanti.');
|
|
||||||
}
|
|
||||||
} on DioException catch (e) {
|
|
||||||
log('Dio error: ${e.message}');
|
|
||||||
return left(e.response?.data.toString() ?? e.message ?? 'Unknown error');
|
|
||||||
} catch (e) {
|
|
||||||
log('Unexpected error: $e');
|
|
||||||
return left('Unexpected error occurred');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,242 +0,0 @@
|
|||||||
class DashboardAnalyticResponseModel {
|
|
||||||
final bool success;
|
|
||||||
final DashboardAnalyticData data;
|
|
||||||
final dynamic errors;
|
|
||||||
|
|
||||||
DashboardAnalyticResponseModel({
|
|
||||||
required this.success,
|
|
||||||
required this.data,
|
|
||||||
this.errors,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Khusus untuk JSON string
|
|
||||||
factory DashboardAnalyticResponseModel.fromJson(Map<String, dynamic> json) =>
|
|
||||||
DashboardAnalyticResponseModel.fromMap(json);
|
|
||||||
|
|
||||||
/// Untuk menerima Map biasa (bukan dari JSON string)
|
|
||||||
factory DashboardAnalyticResponseModel.fromMap(Map<String, dynamic> map) {
|
|
||||||
return DashboardAnalyticResponseModel(
|
|
||||||
success: map['success'] ?? false,
|
|
||||||
data: DashboardAnalyticData.fromMap(map['data'] ?? {}),
|
|
||||||
errors: map['errors'],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => toMap();
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() => {
|
|
||||||
'success': success,
|
|
||||||
'data': data.toMap(),
|
|
||||||
'errors': errors,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class DashboardAnalyticData {
|
|
||||||
final String organizationId;
|
|
||||||
final String outletId;
|
|
||||||
final String dateFrom;
|
|
||||||
final String dateTo;
|
|
||||||
final DashboardOverview overview;
|
|
||||||
final List<TopProduct> topProducts;
|
|
||||||
final List<PaymentMethodAnalytic> paymentMethods;
|
|
||||||
final List<RecentSale> recentSales;
|
|
||||||
|
|
||||||
DashboardAnalyticData({
|
|
||||||
required this.organizationId,
|
|
||||||
required this.outletId,
|
|
||||||
required this.dateFrom,
|
|
||||||
required this.dateTo,
|
|
||||||
required this.overview,
|
|
||||||
required this.topProducts,
|
|
||||||
required this.paymentMethods,
|
|
||||||
required this.recentSales,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory DashboardAnalyticData.fromMap(Map<String, dynamic> map) =>
|
|
||||||
DashboardAnalyticData(
|
|
||||||
organizationId: map['organization_id'],
|
|
||||||
outletId: map['outlet_id'],
|
|
||||||
dateFrom: map['date_from'],
|
|
||||||
dateTo: map['date_to'],
|
|
||||||
overview: DashboardOverview.fromMap(map['overview']),
|
|
||||||
topProducts: List<TopProduct>.from(
|
|
||||||
map['top_products']?.map((x) => TopProduct.fromMap(x))),
|
|
||||||
paymentMethods: List<PaymentMethodAnalytic>.from(map['payment_methods']
|
|
||||||
?.map((x) => PaymentMethodAnalytic.fromMap(x))),
|
|
||||||
recentSales: List<RecentSale>.from(
|
|
||||||
map['recent_sales']?.map((x) => RecentSale.fromMap(x))),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() => {
|
|
||||||
'organization_id': organizationId,
|
|
||||||
'outlet_id': outletId,
|
|
||||||
'date_from': dateFrom,
|
|
||||||
'date_to': dateTo,
|
|
||||||
'overview': overview.toMap(),
|
|
||||||
'top_products': topProducts.map((x) => x.toMap()).toList(),
|
|
||||||
'payment_methods': paymentMethods.map((x) => x.toMap()).toList(),
|
|
||||||
'recent_sales': recentSales.map((x) => x.toMap()).toList(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class DashboardOverview {
|
|
||||||
final int totalSales;
|
|
||||||
final int totalOrders;
|
|
||||||
final double averageOrderValue;
|
|
||||||
final int totalCustomers;
|
|
||||||
final int voidedOrders;
|
|
||||||
final int refundedOrders;
|
|
||||||
|
|
||||||
DashboardOverview({
|
|
||||||
required this.totalSales,
|
|
||||||
required this.totalOrders,
|
|
||||||
required this.averageOrderValue,
|
|
||||||
required this.totalCustomers,
|
|
||||||
required this.voidedOrders,
|
|
||||||
required this.refundedOrders,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory DashboardOverview.fromMap(Map<String, dynamic> map) =>
|
|
||||||
DashboardOverview(
|
|
||||||
totalSales: map['total_sales'],
|
|
||||||
totalOrders: map['total_orders'],
|
|
||||||
averageOrderValue: map['average_order_value']?.toDouble() ?? 0.0,
|
|
||||||
totalCustomers: map['total_customers'],
|
|
||||||
voidedOrders: map['voided_orders'],
|
|
||||||
refundedOrders: map['refunded_orders'],
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() => {
|
|
||||||
'total_sales': totalSales,
|
|
||||||
'total_orders': totalOrders,
|
|
||||||
'average_order_value': averageOrderValue,
|
|
||||||
'total_customers': totalCustomers,
|
|
||||||
'voided_orders': voidedOrders,
|
|
||||||
'refunded_orders': refundedOrders,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class TopProduct {
|
|
||||||
final String productId;
|
|
||||||
final String productName;
|
|
||||||
final String categoryId;
|
|
||||||
final String categoryName;
|
|
||||||
final int quantitySold;
|
|
||||||
final int revenue;
|
|
||||||
final double averagePrice;
|
|
||||||
final int orderCount;
|
|
||||||
|
|
||||||
TopProduct({
|
|
||||||
required this.productId,
|
|
||||||
required this.productName,
|
|
||||||
required this.categoryId,
|
|
||||||
required this.categoryName,
|
|
||||||
required this.quantitySold,
|
|
||||||
required this.revenue,
|
|
||||||
required this.averagePrice,
|
|
||||||
required this.orderCount,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory TopProduct.fromMap(Map<String, dynamic> map) => TopProduct(
|
|
||||||
productId: map['product_id'],
|
|
||||||
productName: map['product_name'],
|
|
||||||
categoryId: map['category_id'],
|
|
||||||
categoryName: map['category_name'],
|
|
||||||
quantitySold: map['quantity_sold'],
|
|
||||||
revenue: map['revenue'],
|
|
||||||
averagePrice: map['average_price']?.toDouble() ?? 0.0,
|
|
||||||
orderCount: map['order_count'],
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() => {
|
|
||||||
'product_id': productId,
|
|
||||||
'product_name': productName,
|
|
||||||
'category_id': categoryId,
|
|
||||||
'category_name': categoryName,
|
|
||||||
'quantity_sold': quantitySold,
|
|
||||||
'revenue': revenue,
|
|
||||||
'average_price': averagePrice,
|
|
||||||
'order_count': orderCount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class PaymentMethodAnalytic {
|
|
||||||
final String paymentMethodId;
|
|
||||||
final String paymentMethodName;
|
|
||||||
final String paymentMethodType;
|
|
||||||
final int totalAmount;
|
|
||||||
final int orderCount;
|
|
||||||
final int paymentCount;
|
|
||||||
final double percentage;
|
|
||||||
|
|
||||||
PaymentMethodAnalytic({
|
|
||||||
required this.paymentMethodId,
|
|
||||||
required this.paymentMethodName,
|
|
||||||
required this.paymentMethodType,
|
|
||||||
required this.totalAmount,
|
|
||||||
required this.orderCount,
|
|
||||||
required this.paymentCount,
|
|
||||||
required this.percentage,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory PaymentMethodAnalytic.fromMap(Map<String, dynamic> map) =>
|
|
||||||
PaymentMethodAnalytic(
|
|
||||||
paymentMethodId: map['payment_method_id'],
|
|
||||||
paymentMethodName: map['payment_method_name'],
|
|
||||||
paymentMethodType: map['payment_method_type'],
|
|
||||||
totalAmount: map['total_amount'],
|
|
||||||
orderCount: map['order_count'],
|
|
||||||
paymentCount: map['payment_count'],
|
|
||||||
percentage: map['percentage']?.toDouble() ?? 0.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() => {
|
|
||||||
'payment_method_id': paymentMethodId,
|
|
||||||
'payment_method_name': paymentMethodName,
|
|
||||||
'payment_method_type': paymentMethodType,
|
|
||||||
'total_amount': totalAmount,
|
|
||||||
'order_count': orderCount,
|
|
||||||
'payment_count': paymentCount,
|
|
||||||
'percentage': percentage,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class RecentSale {
|
|
||||||
final String date;
|
|
||||||
final int sales;
|
|
||||||
final int orders;
|
|
||||||
final int items;
|
|
||||||
final int tax;
|
|
||||||
final int discount;
|
|
||||||
final int netSales;
|
|
||||||
|
|
||||||
RecentSale({
|
|
||||||
required this.date,
|
|
||||||
required this.sales,
|
|
||||||
required this.orders,
|
|
||||||
required this.items,
|
|
||||||
required this.tax,
|
|
||||||
required this.discount,
|
|
||||||
required this.netSales,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory RecentSale.fromMap(Map<String, dynamic> map) => RecentSale(
|
|
||||||
date: map['date'],
|
|
||||||
sales: map['sales'],
|
|
||||||
orders: map['orders'],
|
|
||||||
items: map['items'],
|
|
||||||
tax: map['tax'],
|
|
||||||
discount: map['discount'],
|
|
||||||
netSales: map['net_sales'],
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() => {
|
|
||||||
'date': date,
|
|
||||||
'sales': sales,
|
|
||||||
'orders': orders,
|
|
||||||
'items': items,
|
|
||||||
'tax': tax,
|
|
||||||
'discount': discount,
|
|
||||||
'net_sales': netSales,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,143 +0,0 @@
|
|||||||
class ProductAnalyticResponseModel {
|
|
||||||
final bool success;
|
|
||||||
final ProductAnalyticData data;
|
|
||||||
final dynamic errors;
|
|
||||||
|
|
||||||
ProductAnalyticResponseModel({
|
|
||||||
required this.success,
|
|
||||||
required this.data,
|
|
||||||
this.errors,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory ProductAnalyticResponseModel.fromJson(Map<String, dynamic> json) =>
|
|
||||||
ProductAnalyticResponseModel.fromMap(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => toMap();
|
|
||||||
|
|
||||||
factory ProductAnalyticResponseModel.fromMap(Map<String, dynamic> map) {
|
|
||||||
return ProductAnalyticResponseModel(
|
|
||||||
success: map['success'] ?? false,
|
|
||||||
data: ProductAnalyticData.fromMap(map['data']),
|
|
||||||
errors: map['errors'],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() {
|
|
||||||
return {
|
|
||||||
'success': success,
|
|
||||||
'data': data.toMap(),
|
|
||||||
'errors': errors,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProductAnalyticData {
|
|
||||||
final String organizationId;
|
|
||||||
final String outletId;
|
|
||||||
final DateTime dateFrom;
|
|
||||||
final DateTime dateTo;
|
|
||||||
final List<ProductAnalyticItem> data;
|
|
||||||
|
|
||||||
ProductAnalyticData({
|
|
||||||
required this.organizationId,
|
|
||||||
required this.outletId,
|
|
||||||
required this.dateFrom,
|
|
||||||
required this.dateTo,
|
|
||||||
required this.data,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory ProductAnalyticData.fromMap(Map<String, dynamic> map) =>
|
|
||||||
ProductAnalyticData(
|
|
||||||
organizationId: map['organization_id'],
|
|
||||||
outletId: map['outlet_id'],
|
|
||||||
dateFrom: DateTime.parse(map['date_from']),
|
|
||||||
dateTo: DateTime.parse(map['date_to']),
|
|
||||||
data: List<ProductAnalyticItem>.from(
|
|
||||||
map['data'].map((x) => ProductAnalyticItem.fromMap(x)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() => {
|
|
||||||
'organization_id': organizationId,
|
|
||||||
'outlet_id': outletId,
|
|
||||||
'date_from': dateFrom.toIso8601String(),
|
|
||||||
'date_to': dateTo.toIso8601String(),
|
|
||||||
'data': data.map((x) => x.toMap()).toList(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProductAnalyticItem {
|
|
||||||
final String productId;
|
|
||||||
final String productName;
|
|
||||||
final String categoryId;
|
|
||||||
final String categoryName;
|
|
||||||
final int quantitySold;
|
|
||||||
final int revenue;
|
|
||||||
final double averagePrice;
|
|
||||||
final int orderCount;
|
|
||||||
|
|
||||||
ProductAnalyticItem({
|
|
||||||
required this.productId,
|
|
||||||
required this.productName,
|
|
||||||
required this.categoryId,
|
|
||||||
required this.categoryName,
|
|
||||||
required this.quantitySold,
|
|
||||||
required this.revenue,
|
|
||||||
required this.averagePrice,
|
|
||||||
required this.orderCount,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory ProductAnalyticItem.fromMap(Map<String, dynamic> map) =>
|
|
||||||
ProductAnalyticItem(
|
|
||||||
productId: map['product_id'],
|
|
||||||
productName: map['product_name'],
|
|
||||||
categoryId: map['category_id'],
|
|
||||||
categoryName: map['category_name'],
|
|
||||||
quantitySold: map['quantity_sold'],
|
|
||||||
revenue: map['revenue'],
|
|
||||||
averagePrice: (map['average_price'] as num).toDouble(),
|
|
||||||
orderCount: map['order_count'],
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() => {
|
|
||||||
'product_id': productId,
|
|
||||||
'product_name': productName,
|
|
||||||
'category_id': categoryId,
|
|
||||||
'category_name': categoryName,
|
|
||||||
'quantity_sold': quantitySold,
|
|
||||||
'revenue': revenue,
|
|
||||||
'average_price': averagePrice,
|
|
||||||
'order_count': orderCount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProductInsights {
|
|
||||||
final List<ProductAnalyticItem> topProducts;
|
|
||||||
final ProductAnalyticItem? bestProduct;
|
|
||||||
final List<CategorySummary> categorySummary;
|
|
||||||
final int totalProducts;
|
|
||||||
final int totalRevenue;
|
|
||||||
final int totalQuantitySold;
|
|
||||||
|
|
||||||
ProductInsights({
|
|
||||||
required this.topProducts,
|
|
||||||
required this.bestProduct,
|
|
||||||
required this.categorySummary,
|
|
||||||
required this.totalProducts,
|
|
||||||
required this.totalRevenue,
|
|
||||||
required this.totalQuantitySold,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Category summary class
|
|
||||||
class CategorySummary {
|
|
||||||
final String categoryName;
|
|
||||||
int productCount;
|
|
||||||
int totalRevenue;
|
|
||||||
|
|
||||||
CategorySummary({
|
|
||||||
required this.categoryName,
|
|
||||||
required this.productCount,
|
|
||||||
required this.totalRevenue,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -27,6 +27,7 @@ import 'package:enaklo_pos/data/datasources/midtrans_remote_datasource.dart';
|
|||||||
import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart';
|
import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart';
|
||||||
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
|
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
|
||||||
import 'package:enaklo_pos/data/datasources/product_remote_datasource.dart';
|
import 'package:enaklo_pos/data/datasources/product_remote_datasource.dart';
|
||||||
|
import 'package:enaklo_pos/data/datasources/order_item_remote_datasource.dart';
|
||||||
import 'package:enaklo_pos/data/datasources/payment_methods_remote_datasource.dart';
|
import 'package:enaklo_pos/data/datasources/payment_methods_remote_datasource.dart';
|
||||||
import 'package:enaklo_pos/data/datasources/settings_local_datasource.dart';
|
import 'package:enaklo_pos/data/datasources/settings_local_datasource.dart';
|
||||||
import 'package:enaklo_pos/presentation/auth/bloc/logout/logout_bloc.dart';
|
import 'package:enaklo_pos/presentation/auth/bloc/logout/logout_bloc.dart';
|
||||||
@ -189,10 +190,10 @@ class _MyAppState extends State<MyApp> {
|
|||||||
create: (context) => GetCategoriesBloc(CategoryRemoteDatasource()),
|
create: (context) => GetCategoriesBloc(CategoryRemoteDatasource()),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => SummaryBloc(AnalyticRemoteDatasource()),
|
create: (context) => SummaryBloc(OrderRemoteDatasource()),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => ProductSalesBloc(AnalyticRemoteDatasource()),
|
create: (context) => ProductSalesBloc(OrderItemRemoteDatasource()),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => ItemSalesReportBloc(AnalyticRemoteDatasource()),
|
create: (context) => ItemSalesReportBloc(AnalyticRemoteDatasource()),
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import 'package:enaklo_pos/data/datasources/analytic_remote_datasource.dart';
|
|
||||||
import 'package:enaklo_pos/data/models/response/product_analytic_response_model.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:enaklo_pos/data/datasources/order_item_remote_datasource.dart';
|
||||||
|
import 'package:enaklo_pos/data/models/response/product_sales_response_model.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
part 'product_sales_event.dart';
|
part 'product_sales_event.dart';
|
||||||
@ -8,16 +8,14 @@ part 'product_sales_state.dart';
|
|||||||
part 'product_sales_bloc.freezed.dart';
|
part 'product_sales_bloc.freezed.dart';
|
||||||
|
|
||||||
class ProductSalesBloc extends Bloc<ProductSalesEvent, ProductSalesState> {
|
class ProductSalesBloc extends Bloc<ProductSalesEvent, ProductSalesState> {
|
||||||
final AnalyticRemoteDatasource datasource;
|
final OrderItemRemoteDatasource datasource;
|
||||||
ProductSalesBloc(
|
ProductSalesBloc(
|
||||||
this.datasource,
|
this.datasource,
|
||||||
) : super(const _Initial()) {
|
) : super(const _Initial()) {
|
||||||
on<_GetProductSales>((event, emit) async {
|
on<_GetProductSales>((event, emit) async {
|
||||||
emit(const _Loading());
|
emit(const _Loading());
|
||||||
final result = await datasource.getProduct(
|
final result = await datasource.getProductSalesByRangeDate(
|
||||||
dateFrom: event.startDate,
|
event.startDate, event.endDate);
|
||||||
dateTo: event.endDate,
|
|
||||||
);
|
|
||||||
result.fold((l) => emit(_Error(l)), (r) => emit(_Success(r.data!)));
|
result.fold((l) => emit(_Error(l)), (r) => emit(_Success(r.data!)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,20 +19,19 @@ mixin _$ProductSalesEvent {
|
|||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function() started,
|
required TResult Function() started,
|
||||||
required TResult Function(DateTime startDate, DateTime endDate)
|
required TResult Function(String startDate, String endDate) getProductSales,
|
||||||
getProductSales,
|
|
||||||
}) =>
|
}) =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function()? started,
|
TResult? Function()? started,
|
||||||
TResult? Function(DateTime startDate, DateTime endDate)? getProductSales,
|
TResult? Function(String startDate, String endDate)? getProductSales,
|
||||||
}) =>
|
}) =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function()? started,
|
TResult Function()? started,
|
||||||
TResult Function(DateTime startDate, DateTime endDate)? getProductSales,
|
TResult Function(String startDate, String endDate)? getProductSales,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) =>
|
}) =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@ -120,8 +119,7 @@ class _$StartedImpl implements _Started {
|
|||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function() started,
|
required TResult Function() started,
|
||||||
required TResult Function(DateTime startDate, DateTime endDate)
|
required TResult Function(String startDate, String endDate) getProductSales,
|
||||||
getProductSales,
|
|
||||||
}) {
|
}) {
|
||||||
return started();
|
return started();
|
||||||
}
|
}
|
||||||
@ -130,7 +128,7 @@ class _$StartedImpl implements _Started {
|
|||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function()? started,
|
TResult? Function()? started,
|
||||||
TResult? Function(DateTime startDate, DateTime endDate)? getProductSales,
|
TResult? Function(String startDate, String endDate)? getProductSales,
|
||||||
}) {
|
}) {
|
||||||
return started?.call();
|
return started?.call();
|
||||||
}
|
}
|
||||||
@ -139,7 +137,7 @@ class _$StartedImpl implements _Started {
|
|||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function()? started,
|
TResult Function()? started,
|
||||||
TResult Function(DateTime startDate, DateTime endDate)? getProductSales,
|
TResult Function(String startDate, String endDate)? getProductSales,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) {
|
}) {
|
||||||
if (started != null) {
|
if (started != null) {
|
||||||
@ -190,7 +188,7 @@ abstract class _$$GetProductSalesImplCopyWith<$Res> {
|
|||||||
$Res Function(_$GetProductSalesImpl) then) =
|
$Res Function(_$GetProductSalesImpl) then) =
|
||||||
__$$GetProductSalesImplCopyWithImpl<$Res>;
|
__$$GetProductSalesImplCopyWithImpl<$Res>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({DateTime startDate, DateTime endDate});
|
$Res call({String startDate, String endDate});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -213,11 +211,11 @@ class __$$GetProductSalesImplCopyWithImpl<$Res>
|
|||||||
null == startDate
|
null == startDate
|
||||||
? _value.startDate
|
? _value.startDate
|
||||||
: startDate // ignore: cast_nullable_to_non_nullable
|
: startDate // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,
|
as String,
|
||||||
null == endDate
|
null == endDate
|
||||||
? _value.endDate
|
? _value.endDate
|
||||||
: endDate // ignore: cast_nullable_to_non_nullable
|
: endDate // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,
|
as String,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -228,9 +226,9 @@ class _$GetProductSalesImpl implements _GetProductSales {
|
|||||||
const _$GetProductSalesImpl(this.startDate, this.endDate);
|
const _$GetProductSalesImpl(this.startDate, this.endDate);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final DateTime startDate;
|
final String startDate;
|
||||||
@override
|
@override
|
||||||
final DateTime endDate;
|
final String endDate;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@ -263,8 +261,7 @@ class _$GetProductSalesImpl implements _GetProductSales {
|
|||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function() started,
|
required TResult Function() started,
|
||||||
required TResult Function(DateTime startDate, DateTime endDate)
|
required TResult Function(String startDate, String endDate) getProductSales,
|
||||||
getProductSales,
|
|
||||||
}) {
|
}) {
|
||||||
return getProductSales(startDate, endDate);
|
return getProductSales(startDate, endDate);
|
||||||
}
|
}
|
||||||
@ -273,7 +270,7 @@ class _$GetProductSalesImpl implements _GetProductSales {
|
|||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function()? started,
|
TResult? Function()? started,
|
||||||
TResult? Function(DateTime startDate, DateTime endDate)? getProductSales,
|
TResult? Function(String startDate, String endDate)? getProductSales,
|
||||||
}) {
|
}) {
|
||||||
return getProductSales?.call(startDate, endDate);
|
return getProductSales?.call(startDate, endDate);
|
||||||
}
|
}
|
||||||
@ -282,7 +279,7 @@ class _$GetProductSalesImpl implements _GetProductSales {
|
|||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function()? started,
|
TResult Function()? started,
|
||||||
TResult Function(DateTime startDate, DateTime endDate)? getProductSales,
|
TResult Function(String startDate, String endDate)? getProductSales,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) {
|
}) {
|
||||||
if (getProductSales != null) {
|
if (getProductSales != null) {
|
||||||
@ -324,11 +321,11 @@ class _$GetProductSalesImpl implements _GetProductSales {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class _GetProductSales implements ProductSalesEvent {
|
abstract class _GetProductSales implements ProductSalesEvent {
|
||||||
const factory _GetProductSales(
|
const factory _GetProductSales(final String startDate, final String endDate) =
|
||||||
final DateTime startDate, final DateTime endDate) = _$GetProductSalesImpl;
|
_$GetProductSalesImpl;
|
||||||
|
|
||||||
DateTime get startDate;
|
String get startDate;
|
||||||
DateTime get endDate;
|
String get endDate;
|
||||||
|
|
||||||
/// Create a copy of ProductSalesEvent
|
/// Create a copy of ProductSalesEvent
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -343,7 +340,7 @@ mixin _$ProductSalesState {
|
|||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function() initial,
|
required TResult Function() initial,
|
||||||
required TResult Function() loading,
|
required TResult Function() loading,
|
||||||
required TResult Function(ProductAnalyticData product) success,
|
required TResult Function(List<ProductSales> productSales) success,
|
||||||
required TResult Function(String message) error,
|
required TResult Function(String message) error,
|
||||||
}) =>
|
}) =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@ -351,7 +348,7 @@ mixin _$ProductSalesState {
|
|||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function()? initial,
|
TResult? Function()? initial,
|
||||||
TResult? Function()? loading,
|
TResult? Function()? loading,
|
||||||
TResult? Function(ProductAnalyticData product)? success,
|
TResult? Function(List<ProductSales> productSales)? success,
|
||||||
TResult? Function(String message)? error,
|
TResult? Function(String message)? error,
|
||||||
}) =>
|
}) =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@ -359,7 +356,7 @@ mixin _$ProductSalesState {
|
|||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function()? initial,
|
TResult Function()? initial,
|
||||||
TResult Function()? loading,
|
TResult Function()? loading,
|
||||||
TResult Function(ProductAnalyticData product)? success,
|
TResult Function(List<ProductSales> productSales)? success,
|
||||||
TResult Function(String message)? error,
|
TResult Function(String message)? error,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) =>
|
}) =>
|
||||||
@ -455,7 +452,7 @@ class _$InitialImpl implements _Initial {
|
|||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function() initial,
|
required TResult Function() initial,
|
||||||
required TResult Function() loading,
|
required TResult Function() loading,
|
||||||
required TResult Function(ProductAnalyticData product) success,
|
required TResult Function(List<ProductSales> productSales) success,
|
||||||
required TResult Function(String message) error,
|
required TResult Function(String message) error,
|
||||||
}) {
|
}) {
|
||||||
return initial();
|
return initial();
|
||||||
@ -466,7 +463,7 @@ class _$InitialImpl implements _Initial {
|
|||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function()? initial,
|
TResult? Function()? initial,
|
||||||
TResult? Function()? loading,
|
TResult? Function()? loading,
|
||||||
TResult? Function(ProductAnalyticData product)? success,
|
TResult? Function(List<ProductSales> productSales)? success,
|
||||||
TResult? Function(String message)? error,
|
TResult? Function(String message)? error,
|
||||||
}) {
|
}) {
|
||||||
return initial?.call();
|
return initial?.call();
|
||||||
@ -477,7 +474,7 @@ class _$InitialImpl implements _Initial {
|
|||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function()? initial,
|
TResult Function()? initial,
|
||||||
TResult Function()? loading,
|
TResult Function()? loading,
|
||||||
TResult Function(ProductAnalyticData product)? success,
|
TResult Function(List<ProductSales> productSales)? success,
|
||||||
TResult Function(String message)? error,
|
TResult Function(String message)? error,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) {
|
}) {
|
||||||
@ -572,7 +569,7 @@ class _$LoadingImpl implements _Loading {
|
|||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function() initial,
|
required TResult Function() initial,
|
||||||
required TResult Function() loading,
|
required TResult Function() loading,
|
||||||
required TResult Function(ProductAnalyticData product) success,
|
required TResult Function(List<ProductSales> productSales) success,
|
||||||
required TResult Function(String message) error,
|
required TResult Function(String message) error,
|
||||||
}) {
|
}) {
|
||||||
return loading();
|
return loading();
|
||||||
@ -583,7 +580,7 @@ class _$LoadingImpl implements _Loading {
|
|||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function()? initial,
|
TResult? Function()? initial,
|
||||||
TResult? Function()? loading,
|
TResult? Function()? loading,
|
||||||
TResult? Function(ProductAnalyticData product)? success,
|
TResult? Function(List<ProductSales> productSales)? success,
|
||||||
TResult? Function(String message)? error,
|
TResult? Function(String message)? error,
|
||||||
}) {
|
}) {
|
||||||
return loading?.call();
|
return loading?.call();
|
||||||
@ -594,7 +591,7 @@ class _$LoadingImpl implements _Loading {
|
|||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function()? initial,
|
TResult Function()? initial,
|
||||||
TResult Function()? loading,
|
TResult Function()? loading,
|
||||||
TResult Function(ProductAnalyticData product)? success,
|
TResult Function(List<ProductSales> productSales)? success,
|
||||||
TResult Function(String message)? error,
|
TResult Function(String message)? error,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) {
|
}) {
|
||||||
@ -652,7 +649,7 @@ abstract class _$$SuccessImplCopyWith<$Res> {
|
|||||||
_$SuccessImpl value, $Res Function(_$SuccessImpl) then) =
|
_$SuccessImpl value, $Res Function(_$SuccessImpl) then) =
|
||||||
__$$SuccessImplCopyWithImpl<$Res>;
|
__$$SuccessImplCopyWithImpl<$Res>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({ProductAnalyticData product});
|
$Res call({List<ProductSales> productSales});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -668,13 +665,13 @@ class __$$SuccessImplCopyWithImpl<$Res>
|
|||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? product = null,
|
Object? productSales = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$SuccessImpl(
|
return _then(_$SuccessImpl(
|
||||||
null == product
|
null == productSales
|
||||||
? _value.product
|
? _value._productSales
|
||||||
: product // ignore: cast_nullable_to_non_nullable
|
: productSales // ignore: cast_nullable_to_non_nullable
|
||||||
as ProductAnalyticData,
|
as List<ProductSales>,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -682,14 +679,20 @@ class __$$SuccessImplCopyWithImpl<$Res>
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
||||||
class _$SuccessImpl implements _Success {
|
class _$SuccessImpl implements _Success {
|
||||||
const _$SuccessImpl(this.product);
|
const _$SuccessImpl(final List<ProductSales> productSales)
|
||||||
|
: _productSales = productSales;
|
||||||
|
|
||||||
|
final List<ProductSales> _productSales;
|
||||||
@override
|
@override
|
||||||
final ProductAnalyticData product;
|
List<ProductSales> get productSales {
|
||||||
|
if (_productSales is EqualUnmodifiableListView) return _productSales;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_productSales);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'ProductSalesState.success(product: $product)';
|
return 'ProductSalesState.success(productSales: $productSales)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -697,11 +700,13 @@ class _$SuccessImpl implements _Success {
|
|||||||
return identical(this, other) ||
|
return identical(this, other) ||
|
||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _$SuccessImpl &&
|
other is _$SuccessImpl &&
|
||||||
(identical(other.product, product) || other.product == product));
|
const DeepCollectionEquality()
|
||||||
|
.equals(other._productSales, _productSales));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, product);
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType, const DeepCollectionEquality().hash(_productSales));
|
||||||
|
|
||||||
/// Create a copy of ProductSalesState
|
/// Create a copy of ProductSalesState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -716,10 +721,10 @@ class _$SuccessImpl implements _Success {
|
|||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function() initial,
|
required TResult Function() initial,
|
||||||
required TResult Function() loading,
|
required TResult Function() loading,
|
||||||
required TResult Function(ProductAnalyticData product) success,
|
required TResult Function(List<ProductSales> productSales) success,
|
||||||
required TResult Function(String message) error,
|
required TResult Function(String message) error,
|
||||||
}) {
|
}) {
|
||||||
return success(product);
|
return success(productSales);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -727,10 +732,10 @@ class _$SuccessImpl implements _Success {
|
|||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function()? initial,
|
TResult? Function()? initial,
|
||||||
TResult? Function()? loading,
|
TResult? Function()? loading,
|
||||||
TResult? Function(ProductAnalyticData product)? success,
|
TResult? Function(List<ProductSales> productSales)? success,
|
||||||
TResult? Function(String message)? error,
|
TResult? Function(String message)? error,
|
||||||
}) {
|
}) {
|
||||||
return success?.call(product);
|
return success?.call(productSales);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -738,12 +743,12 @@ class _$SuccessImpl implements _Success {
|
|||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function()? initial,
|
TResult Function()? initial,
|
||||||
TResult Function()? loading,
|
TResult Function()? loading,
|
||||||
TResult Function(ProductAnalyticData product)? success,
|
TResult Function(List<ProductSales> productSales)? success,
|
||||||
TResult Function(String message)? error,
|
TResult Function(String message)? error,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) {
|
}) {
|
||||||
if (success != null) {
|
if (success != null) {
|
||||||
return success(product);
|
return success(productSales);
|
||||||
}
|
}
|
||||||
return orElse();
|
return orElse();
|
||||||
}
|
}
|
||||||
@ -787,9 +792,9 @@ class _$SuccessImpl implements _Success {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class _Success implements ProductSalesState {
|
abstract class _Success implements ProductSalesState {
|
||||||
const factory _Success(final ProductAnalyticData product) = _$SuccessImpl;
|
const factory _Success(final List<ProductSales> productSales) = _$SuccessImpl;
|
||||||
|
|
||||||
ProductAnalyticData get product;
|
List<ProductSales> get productSales;
|
||||||
|
|
||||||
/// Create a copy of ProductSalesState
|
/// Create a copy of ProductSalesState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -868,7 +873,7 @@ class _$ErrorImpl implements _Error {
|
|||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function() initial,
|
required TResult Function() initial,
|
||||||
required TResult Function() loading,
|
required TResult Function() loading,
|
||||||
required TResult Function(ProductAnalyticData product) success,
|
required TResult Function(List<ProductSales> productSales) success,
|
||||||
required TResult Function(String message) error,
|
required TResult Function(String message) error,
|
||||||
}) {
|
}) {
|
||||||
return error(message);
|
return error(message);
|
||||||
@ -879,7 +884,7 @@ class _$ErrorImpl implements _Error {
|
|||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function()? initial,
|
TResult? Function()? initial,
|
||||||
TResult? Function()? loading,
|
TResult? Function()? loading,
|
||||||
TResult? Function(ProductAnalyticData product)? success,
|
TResult? Function(List<ProductSales> productSales)? success,
|
||||||
TResult? Function(String message)? error,
|
TResult? Function(String message)? error,
|
||||||
}) {
|
}) {
|
||||||
return error?.call(message);
|
return error?.call(message);
|
||||||
@ -890,7 +895,7 @@ class _$ErrorImpl implements _Error {
|
|||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function()? initial,
|
TResult Function()? initial,
|
||||||
TResult Function()? loading,
|
TResult Function()? loading,
|
||||||
TResult Function(ProductAnalyticData product)? success,
|
TResult Function(List<ProductSales> productSales)? success,
|
||||||
TResult Function(String message)? error,
|
TResult Function(String message)? error,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ part of 'product_sales_bloc.dart';
|
|||||||
class ProductSalesEvent with _$ProductSalesEvent {
|
class ProductSalesEvent with _$ProductSalesEvent {
|
||||||
const factory ProductSalesEvent.started() = _Started;
|
const factory ProductSalesEvent.started() = _Started;
|
||||||
const factory ProductSalesEvent.getProductSales(
|
const factory ProductSalesEvent.getProductSales(
|
||||||
DateTime startDate,
|
String startDate,
|
||||||
DateTime endDate,
|
String endDate,
|
||||||
) = _GetProductSales;
|
) = _GetProductSales;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ class ProductSalesState with _$ProductSalesState {
|
|||||||
|
|
||||||
const factory ProductSalesState.loading() = _Loading;
|
const factory ProductSalesState.loading() = _Loading;
|
||||||
|
|
||||||
const factory ProductSalesState.success(ProductAnalyticData product) =
|
const factory ProductSalesState.success(List<ProductSales> productSales) =
|
||||||
_Success;
|
_Success;
|
||||||
|
|
||||||
const factory ProductSalesState.error(String message) = _Error;
|
const factory ProductSalesState.error(String message) = _Error;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import 'package:enaklo_pos/data/datasources/analytic_remote_datasource.dart';
|
|
||||||
import 'package:enaklo_pos/data/models/response/dashboard_analytic_response_model.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart';
|
||||||
|
import 'package:enaklo_pos/data/models/response/summary_response_model.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
part 'summary_event.dart';
|
part 'summary_event.dart';
|
||||||
@ -8,17 +8,15 @@ part 'summary_state.dart';
|
|||||||
part 'summary_bloc.freezed.dart';
|
part 'summary_bloc.freezed.dart';
|
||||||
|
|
||||||
class SummaryBloc extends Bloc<SummaryEvent, SummaryState> {
|
class SummaryBloc extends Bloc<SummaryEvent, SummaryState> {
|
||||||
final AnalyticRemoteDatasource datasource;
|
final OrderRemoteDatasource datasource;
|
||||||
SummaryBloc(
|
SummaryBloc(
|
||||||
this.datasource,
|
this.datasource,
|
||||||
) : super(const _Initial()) {
|
) : super(const _Initial()) {
|
||||||
on<_GetSummary>((event, emit) async {
|
on<_GetSummary>((event, emit) async {
|
||||||
emit(const _Loading());
|
emit(const _Loading());
|
||||||
final result = await datasource.getDashboard(
|
final result = await datasource.getSummaryByRangeDate(
|
||||||
dateFrom: event.startDate,
|
event.startDate, event.endDate);
|
||||||
dateTo: event.endDate,
|
result.fold((l) => emit(_Error(l)), (r) => emit(_Success(r.data!)));
|
||||||
);
|
|
||||||
result.fold((l) => emit(_Error(l)), (r) => emit(_Success(r.data)));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,19 +19,19 @@ mixin _$SummaryEvent {
|
|||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function() started,
|
required TResult Function() started,
|
||||||
required TResult Function(DateTime startDate, DateTime endDate) getSummary,
|
required TResult Function(String startDate, String endDate) getSummary,
|
||||||
}) =>
|
}) =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function()? started,
|
TResult? Function()? started,
|
||||||
TResult? Function(DateTime startDate, DateTime endDate)? getSummary,
|
TResult? Function(String startDate, String endDate)? getSummary,
|
||||||
}) =>
|
}) =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function()? started,
|
TResult Function()? started,
|
||||||
TResult Function(DateTime startDate, DateTime endDate)? getSummary,
|
TResult Function(String startDate, String endDate)? getSummary,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) =>
|
}) =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@ -119,7 +119,7 @@ class _$StartedImpl implements _Started {
|
|||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function() started,
|
required TResult Function() started,
|
||||||
required TResult Function(DateTime startDate, DateTime endDate) getSummary,
|
required TResult Function(String startDate, String endDate) getSummary,
|
||||||
}) {
|
}) {
|
||||||
return started();
|
return started();
|
||||||
}
|
}
|
||||||
@ -128,7 +128,7 @@ class _$StartedImpl implements _Started {
|
|||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function()? started,
|
TResult? Function()? started,
|
||||||
TResult? Function(DateTime startDate, DateTime endDate)? getSummary,
|
TResult? Function(String startDate, String endDate)? getSummary,
|
||||||
}) {
|
}) {
|
||||||
return started?.call();
|
return started?.call();
|
||||||
}
|
}
|
||||||
@ -137,7 +137,7 @@ class _$StartedImpl implements _Started {
|
|||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function()? started,
|
TResult Function()? started,
|
||||||
TResult Function(DateTime startDate, DateTime endDate)? getSummary,
|
TResult Function(String startDate, String endDate)? getSummary,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) {
|
}) {
|
||||||
if (started != null) {
|
if (started != null) {
|
||||||
@ -188,7 +188,7 @@ abstract class _$$GetSummaryImplCopyWith<$Res> {
|
|||||||
_$GetSummaryImpl value, $Res Function(_$GetSummaryImpl) then) =
|
_$GetSummaryImpl value, $Res Function(_$GetSummaryImpl) then) =
|
||||||
__$$GetSummaryImplCopyWithImpl<$Res>;
|
__$$GetSummaryImplCopyWithImpl<$Res>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({DateTime startDate, DateTime endDate});
|
$Res call({String startDate, String endDate});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -211,11 +211,11 @@ class __$$GetSummaryImplCopyWithImpl<$Res>
|
|||||||
null == startDate
|
null == startDate
|
||||||
? _value.startDate
|
? _value.startDate
|
||||||
: startDate // ignore: cast_nullable_to_non_nullable
|
: startDate // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,
|
as String,
|
||||||
null == endDate
|
null == endDate
|
||||||
? _value.endDate
|
? _value.endDate
|
||||||
: endDate // ignore: cast_nullable_to_non_nullable
|
: endDate // ignore: cast_nullable_to_non_nullable
|
||||||
as DateTime,
|
as String,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,9 +226,9 @@ class _$GetSummaryImpl implements _GetSummary {
|
|||||||
const _$GetSummaryImpl(this.startDate, this.endDate);
|
const _$GetSummaryImpl(this.startDate, this.endDate);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final DateTime startDate;
|
final String startDate;
|
||||||
@override
|
@override
|
||||||
final DateTime endDate;
|
final String endDate;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@ -260,7 +260,7 @@ class _$GetSummaryImpl implements _GetSummary {
|
|||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function() started,
|
required TResult Function() started,
|
||||||
required TResult Function(DateTime startDate, DateTime endDate) getSummary,
|
required TResult Function(String startDate, String endDate) getSummary,
|
||||||
}) {
|
}) {
|
||||||
return getSummary(startDate, endDate);
|
return getSummary(startDate, endDate);
|
||||||
}
|
}
|
||||||
@ -269,7 +269,7 @@ class _$GetSummaryImpl implements _GetSummary {
|
|||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function()? started,
|
TResult? Function()? started,
|
||||||
TResult? Function(DateTime startDate, DateTime endDate)? getSummary,
|
TResult? Function(String startDate, String endDate)? getSummary,
|
||||||
}) {
|
}) {
|
||||||
return getSummary?.call(startDate, endDate);
|
return getSummary?.call(startDate, endDate);
|
||||||
}
|
}
|
||||||
@ -278,7 +278,7 @@ class _$GetSummaryImpl implements _GetSummary {
|
|||||||
@optionalTypeArgs
|
@optionalTypeArgs
|
||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function()? started,
|
TResult Function()? started,
|
||||||
TResult Function(DateTime startDate, DateTime endDate)? getSummary,
|
TResult Function(String startDate, String endDate)? getSummary,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) {
|
}) {
|
||||||
if (getSummary != null) {
|
if (getSummary != null) {
|
||||||
@ -320,11 +320,11 @@ class _$GetSummaryImpl implements _GetSummary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class _GetSummary implements SummaryEvent {
|
abstract class _GetSummary implements SummaryEvent {
|
||||||
const factory _GetSummary(final DateTime startDate, final DateTime endDate) =
|
const factory _GetSummary(final String startDate, final String endDate) =
|
||||||
_$GetSummaryImpl;
|
_$GetSummaryImpl;
|
||||||
|
|
||||||
DateTime get startDate;
|
String get startDate;
|
||||||
DateTime get endDate;
|
String get endDate;
|
||||||
|
|
||||||
/// Create a copy of SummaryEvent
|
/// Create a copy of SummaryEvent
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -339,7 +339,7 @@ mixin _$SummaryState {
|
|||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function() initial,
|
required TResult Function() initial,
|
||||||
required TResult Function() loading,
|
required TResult Function() loading,
|
||||||
required TResult Function(DashboardAnalyticData data) success,
|
required TResult Function(SummaryModel summary) success,
|
||||||
required TResult Function(String message) error,
|
required TResult Function(String message) error,
|
||||||
}) =>
|
}) =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@ -347,7 +347,7 @@ mixin _$SummaryState {
|
|||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function()? initial,
|
TResult? Function()? initial,
|
||||||
TResult? Function()? loading,
|
TResult? Function()? loading,
|
||||||
TResult? Function(DashboardAnalyticData data)? success,
|
TResult? Function(SummaryModel summary)? success,
|
||||||
TResult? Function(String message)? error,
|
TResult? Function(String message)? error,
|
||||||
}) =>
|
}) =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@ -355,7 +355,7 @@ mixin _$SummaryState {
|
|||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function()? initial,
|
TResult Function()? initial,
|
||||||
TResult Function()? loading,
|
TResult Function()? loading,
|
||||||
TResult Function(DashboardAnalyticData data)? success,
|
TResult Function(SummaryModel summary)? success,
|
||||||
TResult Function(String message)? error,
|
TResult Function(String message)? error,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) =>
|
}) =>
|
||||||
@ -451,7 +451,7 @@ class _$InitialImpl implements _Initial {
|
|||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function() initial,
|
required TResult Function() initial,
|
||||||
required TResult Function() loading,
|
required TResult Function() loading,
|
||||||
required TResult Function(DashboardAnalyticData data) success,
|
required TResult Function(SummaryModel summary) success,
|
||||||
required TResult Function(String message) error,
|
required TResult Function(String message) error,
|
||||||
}) {
|
}) {
|
||||||
return initial();
|
return initial();
|
||||||
@ -462,7 +462,7 @@ class _$InitialImpl implements _Initial {
|
|||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function()? initial,
|
TResult? Function()? initial,
|
||||||
TResult? Function()? loading,
|
TResult? Function()? loading,
|
||||||
TResult? Function(DashboardAnalyticData data)? success,
|
TResult? Function(SummaryModel summary)? success,
|
||||||
TResult? Function(String message)? error,
|
TResult? Function(String message)? error,
|
||||||
}) {
|
}) {
|
||||||
return initial?.call();
|
return initial?.call();
|
||||||
@ -473,7 +473,7 @@ class _$InitialImpl implements _Initial {
|
|||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function()? initial,
|
TResult Function()? initial,
|
||||||
TResult Function()? loading,
|
TResult Function()? loading,
|
||||||
TResult Function(DashboardAnalyticData data)? success,
|
TResult Function(SummaryModel summary)? success,
|
||||||
TResult Function(String message)? error,
|
TResult Function(String message)? error,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) {
|
}) {
|
||||||
@ -568,7 +568,7 @@ class _$LoadingImpl implements _Loading {
|
|||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function() initial,
|
required TResult Function() initial,
|
||||||
required TResult Function() loading,
|
required TResult Function() loading,
|
||||||
required TResult Function(DashboardAnalyticData data) success,
|
required TResult Function(SummaryModel summary) success,
|
||||||
required TResult Function(String message) error,
|
required TResult Function(String message) error,
|
||||||
}) {
|
}) {
|
||||||
return loading();
|
return loading();
|
||||||
@ -579,7 +579,7 @@ class _$LoadingImpl implements _Loading {
|
|||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function()? initial,
|
TResult? Function()? initial,
|
||||||
TResult? Function()? loading,
|
TResult? Function()? loading,
|
||||||
TResult? Function(DashboardAnalyticData data)? success,
|
TResult? Function(SummaryModel summary)? success,
|
||||||
TResult? Function(String message)? error,
|
TResult? Function(String message)? error,
|
||||||
}) {
|
}) {
|
||||||
return loading?.call();
|
return loading?.call();
|
||||||
@ -590,7 +590,7 @@ class _$LoadingImpl implements _Loading {
|
|||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function()? initial,
|
TResult Function()? initial,
|
||||||
TResult Function()? loading,
|
TResult Function()? loading,
|
||||||
TResult Function(DashboardAnalyticData data)? success,
|
TResult Function(SummaryModel summary)? success,
|
||||||
TResult Function(String message)? error,
|
TResult Function(String message)? error,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) {
|
}) {
|
||||||
@ -648,7 +648,7 @@ abstract class _$$SuccessImplCopyWith<$Res> {
|
|||||||
_$SuccessImpl value, $Res Function(_$SuccessImpl) then) =
|
_$SuccessImpl value, $Res Function(_$SuccessImpl) then) =
|
||||||
__$$SuccessImplCopyWithImpl<$Res>;
|
__$$SuccessImplCopyWithImpl<$Res>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({DashboardAnalyticData data});
|
$Res call({SummaryModel summary});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -664,13 +664,13 @@ class __$$SuccessImplCopyWithImpl<$Res>
|
|||||||
@pragma('vm:prefer-inline')
|
@pragma('vm:prefer-inline')
|
||||||
@override
|
@override
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? data = null,
|
Object? summary = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$SuccessImpl(
|
return _then(_$SuccessImpl(
|
||||||
null == data
|
null == summary
|
||||||
? _value.data
|
? _value.summary
|
||||||
: data // ignore: cast_nullable_to_non_nullable
|
: summary // ignore: cast_nullable_to_non_nullable
|
||||||
as DashboardAnalyticData,
|
as SummaryModel,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -678,14 +678,14 @@ class __$$SuccessImplCopyWithImpl<$Res>
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
||||||
class _$SuccessImpl implements _Success {
|
class _$SuccessImpl implements _Success {
|
||||||
const _$SuccessImpl(this.data);
|
const _$SuccessImpl(this.summary);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final DashboardAnalyticData data;
|
final SummaryModel summary;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SummaryState.success(data: $data)';
|
return 'SummaryState.success(summary: $summary)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -693,11 +693,11 @@ class _$SuccessImpl implements _Success {
|
|||||||
return identical(this, other) ||
|
return identical(this, other) ||
|
||||||
(other.runtimeType == runtimeType &&
|
(other.runtimeType == runtimeType &&
|
||||||
other is _$SuccessImpl &&
|
other is _$SuccessImpl &&
|
||||||
(identical(other.data, data) || other.data == data));
|
(identical(other.summary, summary) || other.summary == summary));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, data);
|
int get hashCode => Object.hash(runtimeType, summary);
|
||||||
|
|
||||||
/// Create a copy of SummaryState
|
/// Create a copy of SummaryState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -712,10 +712,10 @@ class _$SuccessImpl implements _Success {
|
|||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function() initial,
|
required TResult Function() initial,
|
||||||
required TResult Function() loading,
|
required TResult Function() loading,
|
||||||
required TResult Function(DashboardAnalyticData data) success,
|
required TResult Function(SummaryModel summary) success,
|
||||||
required TResult Function(String message) error,
|
required TResult Function(String message) error,
|
||||||
}) {
|
}) {
|
||||||
return success(data);
|
return success(summary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -723,10 +723,10 @@ class _$SuccessImpl implements _Success {
|
|||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function()? initial,
|
TResult? Function()? initial,
|
||||||
TResult? Function()? loading,
|
TResult? Function()? loading,
|
||||||
TResult? Function(DashboardAnalyticData data)? success,
|
TResult? Function(SummaryModel summary)? success,
|
||||||
TResult? Function(String message)? error,
|
TResult? Function(String message)? error,
|
||||||
}) {
|
}) {
|
||||||
return success?.call(data);
|
return success?.call(summary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -734,12 +734,12 @@ class _$SuccessImpl implements _Success {
|
|||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function()? initial,
|
TResult Function()? initial,
|
||||||
TResult Function()? loading,
|
TResult Function()? loading,
|
||||||
TResult Function(DashboardAnalyticData data)? success,
|
TResult Function(SummaryModel summary)? success,
|
||||||
TResult Function(String message)? error,
|
TResult Function(String message)? error,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) {
|
}) {
|
||||||
if (success != null) {
|
if (success != null) {
|
||||||
return success(data);
|
return success(summary);
|
||||||
}
|
}
|
||||||
return orElse();
|
return orElse();
|
||||||
}
|
}
|
||||||
@ -783,9 +783,9 @@ class _$SuccessImpl implements _Success {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class _Success implements SummaryState {
|
abstract class _Success implements SummaryState {
|
||||||
const factory _Success(final DashboardAnalyticData data) = _$SuccessImpl;
|
const factory _Success(final SummaryModel summary) = _$SuccessImpl;
|
||||||
|
|
||||||
DashboardAnalyticData get data;
|
SummaryModel get summary;
|
||||||
|
|
||||||
/// Create a copy of SummaryState
|
/// Create a copy of SummaryState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -864,7 +864,7 @@ class _$ErrorImpl implements _Error {
|
|||||||
TResult when<TResult extends Object?>({
|
TResult when<TResult extends Object?>({
|
||||||
required TResult Function() initial,
|
required TResult Function() initial,
|
||||||
required TResult Function() loading,
|
required TResult Function() loading,
|
||||||
required TResult Function(DashboardAnalyticData data) success,
|
required TResult Function(SummaryModel summary) success,
|
||||||
required TResult Function(String message) error,
|
required TResult Function(String message) error,
|
||||||
}) {
|
}) {
|
||||||
return error(message);
|
return error(message);
|
||||||
@ -875,7 +875,7 @@ class _$ErrorImpl implements _Error {
|
|||||||
TResult? whenOrNull<TResult extends Object?>({
|
TResult? whenOrNull<TResult extends Object?>({
|
||||||
TResult? Function()? initial,
|
TResult? Function()? initial,
|
||||||
TResult? Function()? loading,
|
TResult? Function()? loading,
|
||||||
TResult? Function(DashboardAnalyticData data)? success,
|
TResult? Function(SummaryModel summary)? success,
|
||||||
TResult? Function(String message)? error,
|
TResult? Function(String message)? error,
|
||||||
}) {
|
}) {
|
||||||
return error?.call(message);
|
return error?.call(message);
|
||||||
@ -886,7 +886,7 @@ class _$ErrorImpl implements _Error {
|
|||||||
TResult maybeWhen<TResult extends Object?>({
|
TResult maybeWhen<TResult extends Object?>({
|
||||||
TResult Function()? initial,
|
TResult Function()? initial,
|
||||||
TResult Function()? loading,
|
TResult Function()? loading,
|
||||||
TResult Function(DashboardAnalyticData data)? success,
|
TResult Function(SummaryModel summary)? success,
|
||||||
TResult Function(String message)? error,
|
TResult Function(String message)? error,
|
||||||
required TResult orElse(),
|
required TResult orElse(),
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@ -3,6 +3,6 @@ part of 'summary_bloc.dart';
|
|||||||
@freezed
|
@freezed
|
||||||
class SummaryEvent with _$SummaryEvent {
|
class SummaryEvent with _$SummaryEvent {
|
||||||
const factory SummaryEvent.started() = _Started;
|
const factory SummaryEvent.started() = _Started;
|
||||||
const factory SummaryEvent.getSummary(DateTime startDate, DateTime endDate) =
|
const factory SummaryEvent.getSummary(String startDate, String endDate) =
|
||||||
_GetSummary;
|
_GetSummary;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,6 @@ part of 'summary_bloc.dart';
|
|||||||
class SummaryState with _$SummaryState {
|
class SummaryState with _$SummaryState {
|
||||||
const factory SummaryState.initial() = _Initial;
|
const factory SummaryState.initial() = _Initial;
|
||||||
const factory SummaryState.loading() = _Loading;
|
const factory SummaryState.loading() = _Loading;
|
||||||
const factory SummaryState.success(DashboardAnalyticData data) = _Success;
|
const factory SummaryState.success(SummaryModel summary) = _Success;
|
||||||
const factory SummaryState.error(String message) = _Error;
|
const factory SummaryState.error(String message) = _Error;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
|
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
|
||||||
import 'package:enaklo_pos/presentation/report/widgets/dashboard_analytic_widget.dart';
|
|
||||||
import 'package:enaklo_pos/presentation/sales/pages/sales_page.dart';
|
import 'package:enaklo_pos/presentation/sales/pages/sales_page.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:enaklo_pos/core/components/custom_date_picker.dart';
|
import 'package:enaklo_pos/core/components/custom_date_picker.dart';
|
||||||
@ -15,10 +14,11 @@ import 'package:enaklo_pos/presentation/report/blocs/summary/summary_bloc.dart';
|
|||||||
import 'package:enaklo_pos/presentation/report/blocs/transaction_report/transaction_report_bloc.dart';
|
import 'package:enaklo_pos/presentation/report/blocs/transaction_report/transaction_report_bloc.dart';
|
||||||
import 'package:enaklo_pos/presentation/report/widgets/item_sales_report_widget.dart';
|
import 'package:enaklo_pos/presentation/report/widgets/item_sales_report_widget.dart';
|
||||||
import 'package:enaklo_pos/presentation/report/widgets/payment_method_report_widget.dart';
|
import 'package:enaklo_pos/presentation/report/widgets/payment_method_report_widget.dart';
|
||||||
import 'package:enaklo_pos/presentation/report/widgets/product_analytic_widget.dart';
|
import 'package:enaklo_pos/presentation/report/widgets/product_sales_chart_widget.dart';
|
||||||
import 'package:enaklo_pos/presentation/report/widgets/report_menu.dart';
|
import 'package:enaklo_pos/presentation/report/widgets/report_menu.dart';
|
||||||
import 'package:enaklo_pos/presentation/report/widgets/report_title.dart';
|
import 'package:enaklo_pos/presentation/report/widgets/report_title.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/report/widgets/summary_report_widget.dart';
|
||||||
import 'package:enaklo_pos/presentation/report/widgets/transaction_report_widget.dart';
|
import 'package:enaklo_pos/presentation/report/widgets/transaction_report_widget.dart';
|
||||||
|
|
||||||
import '../../../core/components/spaces.dart';
|
import '../../../core/components/spaces.dart';
|
||||||
@ -32,7 +32,7 @@ class ReportPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _ReportPageState extends State<ReportPage> {
|
class _ReportPageState extends State<ReportPage> {
|
||||||
int selectedMenu = 1;
|
int selectedMenu = 1;
|
||||||
String title = 'Laporan Penjualan Item';
|
String title = 'Transaction Report';
|
||||||
DateTime fromDate = DateTime.now().subtract(const Duration(days: 30));
|
DateTime fromDate = DateTime.now().subtract(const Duration(days: 30));
|
||||||
DateTime toDate = DateTime.now();
|
DateTime toDate = DateTime.now();
|
||||||
|
|
||||||
@ -139,19 +139,19 @@ class _ReportPageState extends State<ReportPage> {
|
|||||||
isActive: selectedMenu == 1,
|
isActive: selectedMenu == 1,
|
||||||
),
|
),
|
||||||
ReportMenu(
|
ReportMenu(
|
||||||
label: 'Laporan Penjualan Produk',
|
label: 'Chart Penjualan Produk',
|
||||||
subtitle:
|
subtitle:
|
||||||
'Laporan penjualan berdasarkan masing-masing produk.',
|
'Grafik visual penjualan produk untuk analisa performa penjualan.',
|
||||||
icon: Icons.bar_chart_outlined,
|
icon: Icons.bar_chart_outlined,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
selectedMenu = 2;
|
selectedMenu = 2;
|
||||||
title = 'Laporan Penjualan Produk';
|
title = 'Chart Penjualan Produk';
|
||||||
setState(() {});
|
setState(() {});
|
||||||
context.read<ProductSalesBloc>().add(
|
context.read<ProductSalesBloc>().add(
|
||||||
ProductSalesEvent.getProductSales(
|
ProductSalesEvent.getProductSales(
|
||||||
fromDate,
|
DateFormatter.formatDateTime(
|
||||||
toDate,
|
fromDate),
|
||||||
),
|
DateFormatter.formatDateTime(toDate)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
isActive: selectedMenu == 2,
|
isActive: selectedMenu == 2,
|
||||||
@ -166,7 +166,10 @@ class _ReportPageState extends State<ReportPage> {
|
|||||||
title = 'Ringkasan Laporan Penjualan';
|
title = 'Ringkasan Laporan Penjualan';
|
||||||
setState(() {});
|
setState(() {});
|
||||||
context.read<SummaryBloc>().add(
|
context.read<SummaryBloc>().add(
|
||||||
SummaryEvent.getSummary(fromDate, toDate),
|
SummaryEvent.getSummary(
|
||||||
|
DateFormatter.formatDateTime(
|
||||||
|
fromDate),
|
||||||
|
DateFormatter.formatDateTime(toDate)),
|
||||||
);
|
);
|
||||||
|
|
||||||
log("Date ${DateFormatter.formatDateTime(fromDate)}");
|
log("Date ${DateFormatter.formatDateTime(fromDate)}");
|
||||||
@ -259,12 +262,12 @@ class _ReportPageState extends State<ReportPage> {
|
|||||||
error: (message) {
|
error: (message) {
|
||||||
return Text(message);
|
return Text(message);
|
||||||
},
|
},
|
||||||
success: (products) {
|
success: (productSales) {
|
||||||
return ProductAnalyticsWidget(
|
return ProductSalesChartWidgets(
|
||||||
title: title,
|
title: title,
|
||||||
searchDateFormatted:
|
searchDateFormatted:
|
||||||
searchDateFormatted,
|
searchDateFormatted,
|
||||||
productData: products,
|
productSales: productSales,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -281,9 +284,9 @@ class _ReportPageState extends State<ReportPage> {
|
|||||||
error: (message) {
|
error: (message) {
|
||||||
return Text(message);
|
return Text(message);
|
||||||
},
|
},
|
||||||
success: (data) {
|
success: (summary) {
|
||||||
return DashboardAnalyticWidget(
|
return SummaryReportWidget(
|
||||||
data: data,
|
summary: summary,
|
||||||
title: title,
|
title: title,
|
||||||
searchDateFormatted:
|
searchDateFormatted:
|
||||||
searchDateFormatted,
|
searchDateFormatted,
|
||||||
|
|||||||
@ -1,639 +0,0 @@
|
|||||||
import 'package:enaklo_pos/core/constants/colors.dart';
|
|
||||||
import 'package:enaklo_pos/data/models/response/dashboard_analytic_response_model.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
|
|
||||||
// App Colors
|
|
||||||
class AppColorDashboard {
|
|
||||||
static const secondary = Color(0xff7c3aed);
|
|
||||||
static const success = Color(0xff10b981);
|
|
||||||
static const warning = Color(0xfff59e0b);
|
|
||||||
static const danger = Color(0xffef4444);
|
|
||||||
static const info = Color(0xff3b82f6);
|
|
||||||
}
|
|
||||||
|
|
||||||
class DashboardAnalyticWidget extends StatelessWidget {
|
|
||||||
final String title;
|
|
||||||
final String searchDateFormatted;
|
|
||||||
final DashboardAnalyticData data;
|
|
||||||
|
|
||||||
const DashboardAnalyticWidget({
|
|
||||||
super.key,
|
|
||||||
required this.data,
|
|
||||||
required this.title,
|
|
||||||
required this.searchDateFormatted,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
color: const Color(0xFFF8FAFC),
|
|
||||||
padding: const EdgeInsets.all(24.0),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_buildHeader(),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
_buildKPICards(),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Expanded(flex: 2, child: _buildSalesChart()),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(flex: 1, child: _buildProductChart()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Expanded(flex: 2, child: _buildTopProductsList()),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(flex: 1, child: _buildOrderSummary()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildHeader() {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 28,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Color(0xFF1F2937),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'Analisis performa penjualan outlet',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: Colors.grey[600],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColors.primary,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.calendar_today, color: Colors.white, size: 16),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
searchDateFormatted,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildKPICards() {
|
|
||||||
final successfulOrders = data.overview.totalOrders -
|
|
||||||
data.overview.voidedOrders -
|
|
||||||
data.overview.refundedOrders;
|
|
||||||
|
|
||||||
final kpiData = [
|
|
||||||
{
|
|
||||||
'title': 'Total Penjualan',
|
|
||||||
'value': _formatCurrency(data.overview.totalSales),
|
|
||||||
'icon': Icons.trending_up,
|
|
||||||
'color': AppColorDashboard.success,
|
|
||||||
'bgColor': AppColorDashboard.success.withOpacity(0.1),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'title': 'Total Pesanan',
|
|
||||||
'value': '${data.overview.totalOrders}',
|
|
||||||
'icon': Icons.shopping_cart,
|
|
||||||
'color': AppColorDashboard.info,
|
|
||||||
'bgColor': AppColorDashboard.info.withOpacity(0.1),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'title': 'Rata-rata Pesanan',
|
|
||||||
'value': _formatCurrency(data.overview.averageOrderValue.toInt()),
|
|
||||||
'icon': Icons.attach_money,
|
|
||||||
'color': AppColorDashboard.warning,
|
|
||||||
'bgColor': AppColorDashboard.warning.withOpacity(0.1),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'title': 'Pesanan Sukses',
|
|
||||||
'value': '$successfulOrders',
|
|
||||||
'icon': Icons.check_circle,
|
|
||||||
'color': AppColors.primary,
|
|
||||||
'bgColor': AppColors.primary.withOpacity(0.1),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return Row(
|
|
||||||
children: kpiData.map((kpi) {
|
|
||||||
return Expanded(
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.only(right: 16),
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: Colors.grey[200]!),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: kpi['bgColor'] as Color,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
kpi['icon'] as IconData,
|
|
||||||
color: kpi['color'] as Color,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
Icons.trending_up,
|
|
||||||
color: Colors.grey[400],
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
kpi['value'] as String,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Color(0xFF1F2937),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
kpi['title'] as String,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.grey[600],
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSalesChart() {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: Colors.grey[200]!),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Tren Penjualan Harian',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Color(0xFF1F2937),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
SizedBox(
|
|
||||||
height: 200,
|
|
||||||
child: LineChart(
|
|
||||||
LineChartData(
|
|
||||||
gridData: FlGridData(
|
|
||||||
show: true,
|
|
||||||
drawHorizontalLine: true,
|
|
||||||
drawVerticalLine: false,
|
|
||||||
horizontalInterval: 200000,
|
|
||||||
getDrawingHorizontalLine: (value) {
|
|
||||||
return FlLine(
|
|
||||||
color: Colors.grey[200]!,
|
|
||||||
strokeWidth: 1,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
titlesData: FlTitlesData(
|
|
||||||
leftTitles: AxisTitles(
|
|
||||||
sideTitles: SideTitles(
|
|
||||||
showTitles: true,
|
|
||||||
reservedSize: 60,
|
|
||||||
getTitlesWidget: (value, meta) {
|
|
||||||
return Text(
|
|
||||||
'${(value / 1000).toInt()}K',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.grey[600],
|
|
||||||
fontSize: 10,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
bottomTitles: AxisTitles(
|
|
||||||
sideTitles: SideTitles(
|
|
||||||
showTitles: true,
|
|
||||||
getTitlesWidget: (value, meta) {
|
|
||||||
final index = value.toInt();
|
|
||||||
if (index >= 0 && index < data.recentSales.length) {
|
|
||||||
final date =
|
|
||||||
DateTime.parse(data.recentSales[index].date);
|
|
||||||
final formatter = DateFormat('dd MMM');
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8),
|
|
||||||
child: Text(
|
|
||||||
formatter.format(date),
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.grey[600],
|
|
||||||
fontSize: 10,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return const SizedBox();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rightTitles: const AxisTitles(
|
|
||||||
sideTitles: SideTitles(showTitles: false)),
|
|
||||||
topTitles: const AxisTitles(
|
|
||||||
sideTitles: SideTitles(showTitles: false)),
|
|
||||||
),
|
|
||||||
borderData: FlBorderData(show: false),
|
|
||||||
lineBarsData: [
|
|
||||||
LineChartBarData(
|
|
||||||
spots: data.recentSales.asMap().entries.map((entry) {
|
|
||||||
return FlSpot(
|
|
||||||
entry.key.toDouble(), entry.value.sales.toDouble());
|
|
||||||
}).toList(),
|
|
||||||
isCurved: true,
|
|
||||||
color: AppColors.primary,
|
|
||||||
// strokeWidth: 3,
|
|
||||||
dotData: const FlDotData(show: true),
|
|
||||||
belowBarData: BarAreaData(
|
|
||||||
show: true,
|
|
||||||
color: AppColors.primary.withOpacity(0.1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildProductChart() {
|
|
||||||
final colors = [
|
|
||||||
AppColors.primary,
|
|
||||||
AppColorDashboard.secondary,
|
|
||||||
AppColorDashboard.info,
|
|
||||||
AppColorDashboard.warning,
|
|
||||||
AppColorDashboard.success,
|
|
||||||
];
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: Colors.grey[200]!),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Distribusi Produk',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Color(0xFF1F2937),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
SizedBox(
|
|
||||||
height: 160,
|
|
||||||
child: PieChart(
|
|
||||||
PieChartData(
|
|
||||||
sectionsSpace: 2,
|
|
||||||
centerSpaceRadius: 40,
|
|
||||||
sections: data.topProducts.asMap().entries.map((entry) {
|
|
||||||
return PieChartSectionData(
|
|
||||||
color: colors[entry.key % colors.length],
|
|
||||||
value: entry.value.quantitySold.toDouble(),
|
|
||||||
title: '${entry.value.quantitySold}',
|
|
||||||
radius: 60,
|
|
||||||
titleStyle: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Column(
|
|
||||||
children: data.topProducts.take(3).map((product) {
|
|
||||||
final index = data.topProducts.indexOf(product);
|
|
||||||
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 8),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: colors[index % colors.length],
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
product.productName,
|
|
||||||
style: const TextStyle(fontSize: 12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'${product.quantitySold}',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildTopProductsList() {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: Colors.grey[200]!),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Produk Terlaris',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Color(0xFF1F2937),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Column(
|
|
||||||
children: data.topProducts.map((product) {
|
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFF8FAFC),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
border: Border.all(color: Colors.grey[200]!),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColors.primary.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.local_cafe,
|
|
||||||
color: AppColors.primary,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
product.productName,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 2),
|
|
||||||
Text(
|
|
||||||
product.categoryName,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.grey[600],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'${product.quantitySold} unit',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 2),
|
|
||||||
Text(
|
|
||||||
_formatCurrency(product.revenue),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.grey[600],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildOrderSummary() {
|
|
||||||
final successfulOrders = data.overview.totalOrders -
|
|
||||||
data.overview.voidedOrders -
|
|
||||||
data.overview.refundedOrders;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: Colors.grey[200]!),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Ringkasan Pesanan',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Color(0xFF1F2937),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
_buildSummaryItem('Total Pesanan', '${data.overview.totalOrders}',
|
|
||||||
Icons.shopping_cart, AppColorDashboard.info),
|
|
||||||
_buildSummaryItem('Pesanan Sukses', '$successfulOrders',
|
|
||||||
Icons.check_circle, AppColorDashboard.success),
|
|
||||||
_buildSummaryItem(
|
|
||||||
'Pesanan Dibatalkan',
|
|
||||||
'${data.overview.voidedOrders}',
|
|
||||||
Icons.cancel,
|
|
||||||
AppColorDashboard.danger),
|
|
||||||
_buildSummaryItem('Pesanan Refund', '${data.overview.refundedOrders}',
|
|
||||||
Icons.refresh, AppColorDashboard.warning),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
// Payment Methods
|
|
||||||
if (data.paymentMethods.isNotEmpty)
|
|
||||||
Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColors.primary.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Metode Pembayaran',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Color(0xFF374151),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
...data.paymentMethods
|
|
||||||
.map((method) => Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 4),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
method.paymentMethodType == 'cash'
|
|
||||||
? Icons.payments
|
|
||||||
: Icons.credit_card,
|
|
||||||
color: AppColors.primary,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
method.paymentMethodName,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: AppColors.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSummaryItem(
|
|
||||||
String title, String value, IconData icon, Color color) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 16),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Icon(icon, color: color, size: 16),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
title,
|
|
||||||
style: const TextStyle(fontSize: 12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
value,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _formatCurrency(int amount) {
|
|
||||||
final formatter = NumberFormat.currency(
|
|
||||||
locale: 'id_ID',
|
|
||||||
symbol: 'Rp ',
|
|
||||||
decimalDigits: 0,
|
|
||||||
);
|
|
||||||
return formatter.format(amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -48,7 +48,7 @@ class ItemSalesReportWidget extends StatelessWidget {
|
|||||||
|
|
||||||
// Daily Performance Section
|
// Daily Performance Section
|
||||||
Text(
|
Text(
|
||||||
'Kinerja Harian',
|
'Daily Performance',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@ -124,7 +124,7 @@ class ItemSalesReportWidget extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
title,
|
'Sales Analytics',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
@ -182,9 +182,9 @@ class ItemSalesReportWidget extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildMetricCard(
|
child: _buildMetricCard(
|
||||||
title: 'Jumlah Penjualan',
|
title: 'Total Sales',
|
||||||
value: summary.totalSales.currencyFormatRpV2,
|
value: summary.totalSales.currencyFormatRpV2,
|
||||||
subtitle: 'Penjualan Bersih',
|
subtitle: 'Net Sales',
|
||||||
color: const Color(0xFF3B82F6),
|
color: const Color(0xFF3B82F6),
|
||||||
backgroundColor: const Color(0xFFEFF6FF),
|
backgroundColor: const Color(0xFFEFF6FF),
|
||||||
),
|
),
|
||||||
@ -192,9 +192,9 @@ class ItemSalesReportWidget extends StatelessWidget {
|
|||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildMetricCard(
|
child: _buildMetricCard(
|
||||||
title: 'Jumlah Pesanan',
|
title: 'Total Orders',
|
||||||
value: '${summary.totalOrders}',
|
value: '${summary.totalOrders}',
|
||||||
subtitle: '${summary.totalItems} Item',
|
subtitle: '${summary.totalItems} Items',
|
||||||
color: const Color(0xFF8B5CF6),
|
color: const Color(0xFF8B5CF6),
|
||||||
backgroundColor: const Color(0xFFF3E8FF),
|
backgroundColor: const Color(0xFFF3E8FF),
|
||||||
),
|
),
|
||||||
@ -203,9 +203,9 @@ class ItemSalesReportWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildFullWidthMetricCard(
|
_buildFullWidthMetricCard(
|
||||||
title: 'Nilai Pesanan Rata-rata',
|
title: 'Average Order Value',
|
||||||
value: summary.averageOrderValue.round().currencyFormatRpV2,
|
value: summary.averageOrderValue.round().currencyFormatRpV2,
|
||||||
subtitle: 'Per transaksi',
|
subtitle: 'Per transaction',
|
||||||
color: const Color(0xFFEF4444),
|
color: const Color(0xFFEF4444),
|
||||||
backgroundColor: const Color(0xFFFEF2F2),
|
backgroundColor: const Color(0xFFFEF2F2),
|
||||||
),
|
),
|
||||||
@ -245,7 +245,7 @@ class ItemSalesReportWidget extends StatelessWidget {
|
|||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Kinerja puncak pada $formattedDate dengan pendapatan ${_formatCurrency(highestDay.sales)} (pesanan ${highestDay.orders})',
|
'Peak performance on $formattedDate with ${_formatCurrency(highestDay.sales)} revenue (${highestDay.orders} orders)',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@ -445,7 +445,7 @@ class ItemSalesReportWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Pendapatan',
|
'Revenue',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
@ -461,7 +461,7 @@ class ItemSalesReportWidget extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'$orders pesanan',
|
'$orders orders',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@ -469,7 +469,7 @@ class ItemSalesReportWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'$items item',
|
'$items items',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
|
|||||||
@ -1,498 +0,0 @@
|
|||||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
|
||||||
|
|
||||||
import 'package:enaklo_pos/data/models/response/product_analytic_response_model.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
|
|
||||||
class ProductAnalyticsWidget extends StatelessWidget {
|
|
||||||
final ProductAnalyticData productData;
|
|
||||||
final String title;
|
|
||||||
final String searchDateFormatted;
|
|
||||||
|
|
||||||
const ProductAnalyticsWidget(
|
|
||||||
{super.key,
|
|
||||||
required this.productData,
|
|
||||||
required this.title,
|
|
||||||
required this.searchDateFormatted});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
// Proses data untuk mendapatkan insights
|
|
||||||
final insights = _processProductData(productData);
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
border: Border(
|
|
||||||
left: BorderSide(
|
|
||||||
color: const Color(0xFFD1D5DB),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Header Section dengan Icon dan Stats
|
|
||||||
_buildHeader(insights),
|
|
||||||
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// Category Summary Cards (Horizontal Scroll)
|
|
||||||
SizedBox(
|
|
||||||
height: 80,
|
|
||||||
child: ListView.builder(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
itemCount: insights.categorySummary.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final category = insights.categorySummary[index];
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
right: index == insights.categorySummary.length - 1
|
|
||||||
? 0
|
|
||||||
: 12),
|
|
||||||
child: _buildCategorySummaryCard(
|
|
||||||
categoryName: category.categoryName,
|
|
||||||
productCount: category.productCount,
|
|
||||||
totalRevenue: category.totalRevenue,
|
|
||||||
color: _getCategoryColor(category.categoryName),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// Top Products Section
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Produk Berkinerja Terbaik',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: const Color(0xFF111827),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFF3F4F6),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
border: Border.all(
|
|
||||||
color: const Color(0xFFD1D5DB),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'Berdasarkan Pendapatan',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: const Color(0xFF6B7280),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Product List dengan data dinamis
|
|
||||||
Expanded(
|
|
||||||
child: ListView.builder(
|
|
||||||
itemCount: insights.topProducts.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final product = insights.topProducts[index];
|
|
||||||
return _buildProductItem(
|
|
||||||
rank: index + 1,
|
|
||||||
product: product,
|
|
||||||
isTopPerformer: product == insights.bestProduct,
|
|
||||||
categoryColor: _getCategoryColor(product.categoryName),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Bottom Summary dengan insights dinamis
|
|
||||||
_buildBottomSummary(insights.bestProduct),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method untuk memproses data dan mendapatkan insights
|
|
||||||
ProductInsights _processProductData(ProductAnalyticData data) {
|
|
||||||
// Sort products by revenue (descending) untuk ranking
|
|
||||||
List<ProductAnalyticItem> sortedProducts = List.from(data.data);
|
|
||||||
sortedProducts.sort((a, b) => b.revenue.compareTo(a.revenue));
|
|
||||||
|
|
||||||
// Best product adalah yang revenue tertinggi
|
|
||||||
ProductAnalyticItem? bestProduct;
|
|
||||||
if (sortedProducts.isNotEmpty) {
|
|
||||||
bestProduct = sortedProducts.first;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group by category untuk summary
|
|
||||||
Map<String, CategorySummary> categoryMap = {};
|
|
||||||
|
|
||||||
for (var product in data.data) {
|
|
||||||
if (categoryMap.containsKey(product.categoryName)) {
|
|
||||||
categoryMap[product.categoryName]!.productCount++;
|
|
||||||
categoryMap[product.categoryName]!.totalRevenue += product.revenue;
|
|
||||||
} else {
|
|
||||||
categoryMap[product.categoryName] = CategorySummary(
|
|
||||||
categoryName: product.categoryName,
|
|
||||||
productCount: 1,
|
|
||||||
totalRevenue: product.revenue,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert map to list dan sort by revenue
|
|
||||||
List<CategorySummary> categorySummary = categoryMap.values.toList();
|
|
||||||
categorySummary.sort((a, b) => b.totalRevenue.compareTo(a.totalRevenue));
|
|
||||||
|
|
||||||
// Calculate total metrics
|
|
||||||
int totalProducts = data.data.length;
|
|
||||||
int totalRevenue = data.data.fold(0, (sum, item) => sum + item.revenue);
|
|
||||||
int totalQuantitySold =
|
|
||||||
data.data.fold(0, (sum, item) => sum + item.quantitySold);
|
|
||||||
|
|
||||||
return ProductInsights(
|
|
||||||
topProducts: sortedProducts,
|
|
||||||
bestProduct: bestProduct,
|
|
||||||
categorySummary: categorySummary,
|
|
||||||
totalProducts: totalProducts,
|
|
||||||
totalRevenue: totalRevenue,
|
|
||||||
totalQuantitySold: totalQuantitySold,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildHeader(ProductInsights insights) {
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
// Icon Container
|
|
||||||
Container(
|
|
||||||
width: 48,
|
|
||||||
height: 48,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFF3B82F6),
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
Icons.inventory_2,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 24,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
|
|
||||||
// Title and Period
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: const Color(0xFF111827),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
searchDateFormatted,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: const Color(0xFF6B7280),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Total Products Badge
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFF059669),
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'${insights.totalProducts} Produk',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildBottomSummary(ProductAnalyticItem? bestProduct) {
|
|
||||||
if (bestProduct == null) return Container();
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(14),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFFFEF3C7),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
border: Border.all(
|
|
||||||
color: const Color(0xFFD97706),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.star,
|
|
||||||
color: const Color(0xFFD97706),
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'${bestProduct.productName} memimpin dengan ${bestProduct.quantitySold} unit terjual dan pendapatan ${_formatCurrency(bestProduct.revenue)}',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: const Color(0xff92400E),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper method untuk category color
|
|
||||||
Color _getCategoryColor(String categoryName) {
|
|
||||||
switch (categoryName.toLowerCase()) {
|
|
||||||
case 'minuman':
|
|
||||||
return const Color(0xFF06B6D4);
|
|
||||||
case 'makanan':
|
|
||||||
return const Color(0xFFEF4444);
|
|
||||||
case 'snack':
|
|
||||||
return const Color(0xFF8B5CF6);
|
|
||||||
default:
|
|
||||||
return const Color(0xFF6B7280);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildCategorySummaryCard({
|
|
||||||
required String categoryName,
|
|
||||||
required int productCount,
|
|
||||||
required int totalRevenue,
|
|
||||||
required Color color,
|
|
||||||
}) {
|
|
||||||
return Container(
|
|
||||||
width: 140,
|
|
||||||
padding: const EdgeInsets.all(14),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
border: Border.all(
|
|
||||||
color: color.withOpacity(0.3),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
categoryName,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: color,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'$productCount items',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: const Color(0xFF6B7280),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
_formatCurrency(totalRevenue),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 15,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: const Color(0xFF111827),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildProductItem({
|
|
||||||
required int rank,
|
|
||||||
required ProductAnalyticItem product,
|
|
||||||
required bool isTopPerformer,
|
|
||||||
required Color categoryColor,
|
|
||||||
}) {
|
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.only(bottom: 10),
|
|
||||||
padding: const EdgeInsets.all(14),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color:
|
|
||||||
isTopPerformer ? const Color(0xFFF0F9FF) : const Color(0xFFF9FAFB),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
border: Border.all(
|
|
||||||
color: isTopPerformer
|
|
||||||
? const Color(0xFF3B82F6)
|
|
||||||
: const Color(0xFFE5E7EB),
|
|
||||||
width: isTopPerformer ? 2 : 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
// Rank Badge
|
|
||||||
Container(
|
|
||||||
width: 28,
|
|
||||||
height: 28,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isTopPerformer
|
|
||||||
? const Color(0xFF3B82F6)
|
|
||||||
: const Color(0xFF6B7280),
|
|
||||||
borderRadius: BorderRadius.circular(14),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
'$rank',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
|
|
||||||
// Product Info
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
product.productName,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: const Color(0xFF111827),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
if (isTopPerformer)
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 6, vertical: 2),
|
|
||||||
margin: const EdgeInsets.only(right: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color(0xFF10B981),
|
|
||||||
borderRadius: BorderRadius.circular(3),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'BEST',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 8,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
_formatCurrency(product.revenue),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
color: const Color(0xFF111827),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 6),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
// Category Badge
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 6, vertical: 2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: categoryColor.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
product.categoryName,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 9,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: categoryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
|
|
||||||
// Stats
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'${product.quantitySold} units • ${product.orderCount} orders • Avg ${_formatCurrency(product.averagePrice.round())}',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: const Color(0xFF6B7280),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper method untuk format currency
|
|
||||||
String _formatCurrency(int amount) {
|
|
||||||
if (amount >= 1000000) {
|
|
||||||
return 'Rp ${(amount / 1000000).toStringAsFixed(1)}M';
|
|
||||||
} else if (amount >= 1000) {
|
|
||||||
return 'Rp ${(amount / 1000).toStringAsFixed(0)}K';
|
|
||||||
} else {
|
|
||||||
return 'Rp ${NumberFormat('#,###').format(amount)}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
128
lib/presentation/report/widgets/product_sales_chart_widget.dart
Normal file
128
lib/presentation/report/widgets/product_sales_chart_widget.dart
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
|
|
||||||
|
import 'package:enaklo_pos/core/components/spaces.dart';
|
||||||
|
import 'package:enaklo_pos/presentation/report/widgets/report_page_title.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pie_chart/pie_chart.dart';
|
||||||
|
|
||||||
|
import 'package:enaklo_pos/data/models/response/product_sales_response_model.dart';
|
||||||
|
|
||||||
|
class ProductSalesChartWidgets extends StatefulWidget {
|
||||||
|
final String title;
|
||||||
|
final String searchDateFormatted;
|
||||||
|
final List<ProductSales> productSales;
|
||||||
|
const ProductSalesChartWidgets({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.searchDateFormatted,
|
||||||
|
required this.productSales,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ProductSalesChartWidgets> createState() =>
|
||||||
|
_ProductSalesChartWidgetsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProductSalesChartWidgetsState extends State<ProductSalesChartWidgets> {
|
||||||
|
Map<String, double> dataMap2 = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
loadData();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData() {
|
||||||
|
for (var data in widget.productSales) {
|
||||||
|
dataMap2[data.productName ?? 'Unknown'] =
|
||||||
|
double.parse(data.totalQuantity!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final colorList = <Color>[
|
||||||
|
const Color(0xfffdcb6e),
|
||||||
|
const Color(0xff0984e3),
|
||||||
|
const Color(0xfffd79a8),
|
||||||
|
const Color(0xffe17055),
|
||||||
|
const Color(0xff6c5ce7),
|
||||||
|
const Color(0xfff0932b),
|
||||||
|
const Color(0xff6ab04c),
|
||||||
|
const Color(0xfff8a5c2),
|
||||||
|
const Color(0xffe84393),
|
||||||
|
const Color(0xfffd79a8),
|
||||||
|
const Color(0xffa29bfe),
|
||||||
|
const Color(0xff00b894),
|
||||||
|
const Color(0xffe17055),
|
||||||
|
const Color(0xffd63031),
|
||||||
|
const Color(0xffa29bfe),
|
||||||
|
const Color(0xff6c5ce7),
|
||||||
|
const Color(0xff00cec9),
|
||||||
|
const Color(0xfffad390),
|
||||||
|
const Color(0xff686de0),
|
||||||
|
const Color(0xfffdcb6e),
|
||||||
|
const Color(0xff0984e3),
|
||||||
|
const Color(0xfffd79a8),
|
||||||
|
const Color(0xffe17055),
|
||||||
|
const Color(0xff6c5ce7),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
ReportPageTitle(
|
||||||
|
title: widget.title,
|
||||||
|
searchDateFormatted: widget.searchDateFormatted,
|
||||||
|
onExport: () async {},
|
||||||
|
isExport: false, // Set to false if export is not needed
|
||||||
|
),
|
||||||
|
const SpaceHeight(16.0),
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
margin: const EdgeInsets.all(12.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
PieChart(
|
||||||
|
dataMap: dataMap2,
|
||||||
|
animationDuration: Duration(milliseconds: 800),
|
||||||
|
chartLegendSpacing: 32,
|
||||||
|
chartRadius: MediaQuery.of(context).size.width / 3.2,
|
||||||
|
colorList: colorList,
|
||||||
|
initialAngleInDegree: 0,
|
||||||
|
chartType: ChartType.disc,
|
||||||
|
ringStrokeWidth: 32,
|
||||||
|
// centerText: "HYBRID",
|
||||||
|
legendOptions: LegendOptions(
|
||||||
|
showLegendsInRow: false,
|
||||||
|
legendPosition: LegendPosition.right,
|
||||||
|
showLegends: true,
|
||||||
|
legendShape: BoxShape.circle,
|
||||||
|
legendTextStyle: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
chartValuesOptions: ChartValuesOptions(
|
||||||
|
showChartValueBackground: true,
|
||||||
|
showChartValues: true,
|
||||||
|
showChartValuesInPercentage: false,
|
||||||
|
showChartValuesOutside: false,
|
||||||
|
decimalPlaces: 0,
|
||||||
|
),
|
||||||
|
// gradientList: ---To add gradient colors---
|
||||||
|
// emptyColorGradient: ---Empty Color gradient---
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
pubspec.lock
18
pubspec.lock
@ -374,14 +374,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.6"
|
version: "5.0.6"
|
||||||
equatable:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: equatable
|
|
||||||
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.7"
|
|
||||||
esc_pos_utils_plus:
|
esc_pos_utils_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -454,14 +446,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
fl_chart:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: fl_chart
|
|
||||||
sha256: "577aeac8ca414c25333334d7c4bb246775234c0e44b38b10a82b559dd4d764e7"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.0"
|
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -1564,4 +1548,4 @@ packages:
|
|||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.7.0-0 <4.0.0"
|
dart: ">=3.7.0-0 <4.0.0"
|
||||||
flutter: ">=3.27.4"
|
flutter: ">=3.24.0"
|
||||||
|
|||||||
@ -63,7 +63,6 @@ dependencies:
|
|||||||
awesome_dio_interceptor: ^1.3.0
|
awesome_dio_interceptor: ^1.3.0
|
||||||
another_flushbar: ^1.12.30
|
another_flushbar: ^1.12.30
|
||||||
dropdown_search: ^5.0.6
|
dropdown_search: ^5.0.6
|
||||||
fl_chart: ^1.0.0
|
|
||||||
# imin_printer: ^0.6.10
|
# imin_printer: ^0.6.10
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user