feat: dashboard

This commit is contained in:
efrilm
2025-08-18 01:50:50 +07:00
parent 51289d7829
commit 65ba81f311
25 changed files with 5786 additions and 606 deletions
@@ -9,3 +9,4 @@ part 'dto/sales_analytic_dto.dart';
part 'dto/profit_loss_analytic_dto.dart';
part 'dto/category_analytic_dto.dart';
part 'dto/inventory_analytic_dto.dart';
part 'dto/dashboard_analytic_dto.dart';
File diff suppressed because it is too large Load Diff
@@ -385,3 +385,135 @@ Map<String, dynamic> _$$InventoryIngredientDtoImplToJson(
'is_zero_stock': instance.isZeroStock,
'updated_at': instance.updatedAt,
};
_$DashboardAnalyticDtoImpl _$$DashboardAnalyticDtoImplFromJson(
Map<String, dynamic> json,
) => _$DashboardAnalyticDtoImpl(
organizationId: json['organization_id'] as String?,
outletId: json['outlet_id'] as String?,
dateFrom: json['date_from'] as String?,
dateTo: json['date_to'] as String?,
overview: json['overview'] == null
? null
: DashboardOverviewDto.fromJson(json['overview'] as Map<String, dynamic>),
topProducts: (json['top_products'] as List<dynamic>?)
?.map((e) => DashboardTopProductDto.fromJson(e as Map<String, dynamic>))
.toList(),
paymentMethods: (json['payment_methods'] as List<dynamic>?)
?.map(
(e) => DashboardPaymentMethodDto.fromJson(e as Map<String, dynamic>),
)
.toList(),
recentSales: (json['recent_sales'] as List<dynamic>?)
?.map((e) => DashboardRecentSaleDto.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$DashboardAnalyticDtoImplToJson(
_$DashboardAnalyticDtoImpl instance,
) => <String, dynamic>{
'organization_id': instance.organizationId,
'outlet_id': instance.outletId,
'date_from': instance.dateFrom,
'date_to': instance.dateTo,
'overview': instance.overview,
'top_products': instance.topProducts,
'payment_methods': instance.paymentMethods,
'recent_sales': instance.recentSales,
};
_$DashboardOverviewDtoImpl _$$DashboardOverviewDtoImplFromJson(
Map<String, dynamic> json,
) => _$DashboardOverviewDtoImpl(
totalSales: (json['total_sales'] as num?)?.toInt(),
totalOrders: (json['total_orders'] as num?)?.toInt(),
averageOrderValue: (json['average_order_value'] as num?)?.toDouble(),
totalCustomers: (json['total_customers'] as num?)?.toInt(),
voidedOrders: (json['voided_orders'] as num?)?.toInt(),
refundedOrders: (json['refunded_orders'] as num?)?.toInt(),
);
Map<String, dynamic> _$$DashboardOverviewDtoImplToJson(
_$DashboardOverviewDtoImpl instance,
) => <String, dynamic>{
'total_sales': instance.totalSales,
'total_orders': instance.totalOrders,
'average_order_value': instance.averageOrderValue,
'total_customers': instance.totalCustomers,
'voided_orders': instance.voidedOrders,
'refunded_orders': instance.refundedOrders,
};
_$DashboardTopProductDtoImpl _$$DashboardTopProductDtoImplFromJson(
Map<String, dynamic> json,
) => _$DashboardTopProductDtoImpl(
productId: json['product_id'] as String?,
productName: json['product_name'] as String?,
categoryId: json['category_id'] as String?,
categoryName: json['category_name'] as String?,
quantitySold: (json['quantity_sold'] as num?)?.toInt(),
revenue: (json['revenue'] as num?)?.toInt(),
averagePrice: (json['average_price'] as num?)?.toDouble(),
orderCount: (json['order_count'] as num?)?.toInt(),
);
Map<String, dynamic> _$$DashboardTopProductDtoImplToJson(
_$DashboardTopProductDtoImpl instance,
) => <String, dynamic>{
'product_id': instance.productId,
'product_name': instance.productName,
'category_id': instance.categoryId,
'category_name': instance.categoryName,
'quantity_sold': instance.quantitySold,
'revenue': instance.revenue,
'average_price': instance.averagePrice,
'order_count': instance.orderCount,
};
_$DashboardPaymentMethodDtoImpl _$$DashboardPaymentMethodDtoImplFromJson(
Map<String, dynamic> json,
) => _$DashboardPaymentMethodDtoImpl(
paymentMethodId: json['payment_method_id'] as String?,
paymentMethodName: json['payment_method_name'] as String?,
paymentMethodType: json['payment_method_type'] as String?,
totalAmount: (json['total_amount'] as num?)?.toInt(),
orderCount: (json['order_count'] as num?)?.toInt(),
paymentCount: (json['payment_count'] as num?)?.toInt(),
percentage: (json['percentage'] as num?)?.toDouble(),
);
Map<String, dynamic> _$$DashboardPaymentMethodDtoImplToJson(
_$DashboardPaymentMethodDtoImpl instance,
) => <String, dynamic>{
'payment_method_id': instance.paymentMethodId,
'payment_method_name': instance.paymentMethodName,
'payment_method_type': instance.paymentMethodType,
'total_amount': instance.totalAmount,
'order_count': instance.orderCount,
'payment_count': instance.paymentCount,
'percentage': instance.percentage,
};
_$DashboardRecentSaleDtoImpl _$$DashboardRecentSaleDtoImplFromJson(
Map<String, dynamic> json,
) => _$DashboardRecentSaleDtoImpl(
date: json['date'] as String?,
sales: (json['sales'] as num?)?.toInt(),
orders: (json['orders'] as num?)?.toInt(),
items: (json['items'] as num?)?.toInt(),
tax: (json['tax'] as num?)?.toInt(),
discount: (json['discount'] as num?)?.toInt(),
netSales: (json['net_sales'] as num?)?.toInt(),
);
Map<String, dynamic> _$$DashboardRecentSaleDtoImplToJson(
_$DashboardRecentSaleDtoImpl instance,
) => <String, dynamic>{
'date': instance.date,
'sales': instance.sales,
'orders': instance.orders,
'items': instance.items,
'tax': instance.tax,
'discount': instance.discount,
'net_sales': instance.netSales,
};
@@ -126,4 +126,32 @@ class AnalyticRemoteDataProvider {
return DC.error(AnalyticFailure.serverError(e));
}
}
Future<DC<AnalyticFailure, DashboardAnalyticDto>> fetchDashboard({
required String outletId,
required DateTime dateFrom,
required DateTime dateTo,
}) async {
try {
final response = await _apiClient.get(
ApiPath.dashboardAnalytic,
params: {
'date_from': dateFrom.toServerDate,
'date_to': dateTo.toServerDate,
},
headers: getAuthorizationHeader(),
);
if (response.data['data'] == null) {
return DC.error(AnalyticFailure.empty());
}
final dto = DashboardAnalyticDto.fromJson(response.data['data']);
return DC.data(dto);
} on ApiFailure catch (e, s) {
log('fetchDashboardError', name: _logName, error: e, stackTrace: s);
return DC.error(AnalyticFailure.serverError(e));
}
}
}
@@ -0,0 +1,145 @@
part of '../analytic_dtos.dart';
@freezed
class DashboardAnalyticDto with _$DashboardAnalyticDto {
const DashboardAnalyticDto._();
const factory DashboardAnalyticDto({
@JsonKey(name: 'organization_id') String? organizationId,
@JsonKey(name: 'outlet_id') String? outletId,
@JsonKey(name: 'date_from') String? dateFrom,
@JsonKey(name: 'date_to') String? dateTo,
@JsonKey(name: 'overview') DashboardOverviewDto? overview,
@JsonKey(name: 'top_products') List<DashboardTopProductDto>? topProducts,
@JsonKey(name: 'payment_methods')
List<DashboardPaymentMethodDto>? paymentMethods,
@JsonKey(name: 'recent_sales') List<DashboardRecentSaleDto>? recentSales,
}) = _DashboardAnalyticDto;
factory DashboardAnalyticDto.fromJson(Map<String, dynamic> json) =>
_$DashboardAnalyticDtoFromJson(json);
DashboardAnalytic toDomain() => DashboardAnalytic(
organizationId: organizationId ?? '',
outletId: outletId ?? '',
dateFrom: dateFrom ?? '',
dateTo: dateTo ?? '',
overview: overview?.toDomain() ?? DashboardOverview.empty(),
topProducts: topProducts?.map((e) => e.toDomain()).toList() ?? [],
paymentMethods: paymentMethods?.map((e) => e.toDomain()).toList() ?? [],
recentSales: recentSales?.map((e) => e.toDomain()).toList() ?? [],
);
}
@freezed
class DashboardOverviewDto with _$DashboardOverviewDto {
const DashboardOverviewDto._();
const factory DashboardOverviewDto({
@JsonKey(name: 'total_sales') int? totalSales,
@JsonKey(name: 'total_orders') int? totalOrders,
@JsonKey(name: 'average_order_value') double? averageOrderValue,
@JsonKey(name: 'total_customers') int? totalCustomers,
@JsonKey(name: 'voided_orders') int? voidedOrders,
@JsonKey(name: 'refunded_orders') int? refundedOrders,
}) = _DashboardOverviewDto;
factory DashboardOverviewDto.fromJson(Map<String, dynamic> json) =>
_$DashboardOverviewDtoFromJson(json);
DashboardOverview toDomain() => DashboardOverview(
totalSales: totalSales ?? 0,
totalOrders: totalOrders ?? 0,
averageOrderValue: averageOrderValue ?? 0.0,
totalCustomers: totalCustomers ?? 0,
voidedOrders: voidedOrders ?? 0,
refundedOrders: refundedOrders ?? 0,
);
}
@freezed
class DashboardTopProductDto with _$DashboardTopProductDto {
const DashboardTopProductDto._();
const factory DashboardTopProductDto({
@JsonKey(name: 'product_id') String? productId,
@JsonKey(name: 'product_name') String? productName,
@JsonKey(name: 'category_id') String? categoryId,
@JsonKey(name: 'category_name') String? categoryName,
@JsonKey(name: 'quantity_sold') int? quantitySold,
@JsonKey(name: 'revenue') int? revenue,
@JsonKey(name: 'average_price') double? averagePrice,
@JsonKey(name: 'order_count') int? orderCount,
}) = _DashboardTopProductDto;
factory DashboardTopProductDto.fromJson(Map<String, dynamic> json) =>
_$DashboardTopProductDtoFromJson(json);
DashboardTopProduct toDomain() => DashboardTopProduct(
productId: productId ?? '',
productName: productName ?? '',
categoryId: categoryId ?? '',
categoryName: categoryName ?? '',
quantitySold: quantitySold ?? 0,
revenue: revenue ?? 0,
averagePrice: averagePrice ?? 0.0,
orderCount: orderCount ?? 0,
);
}
@freezed
class DashboardPaymentMethodDto with _$DashboardPaymentMethodDto {
const DashboardPaymentMethodDto._();
const factory DashboardPaymentMethodDto({
@JsonKey(name: 'payment_method_id') String? paymentMethodId,
@JsonKey(name: 'payment_method_name') String? paymentMethodName,
@JsonKey(name: 'payment_method_type') String? paymentMethodType,
@JsonKey(name: 'total_amount') int? totalAmount,
@JsonKey(name: 'order_count') int? orderCount,
@JsonKey(name: 'payment_count') int? paymentCount,
@JsonKey(name: 'percentage') double? percentage,
}) = _DashboardPaymentMethodDto;
factory DashboardPaymentMethodDto.fromJson(Map<String, dynamic> json) =>
_$DashboardPaymentMethodDtoFromJson(json);
DashboardPaymentMethod toDomain() => DashboardPaymentMethod(
paymentMethodId: paymentMethodId ?? '',
paymentMethodName: paymentMethodName ?? '',
paymentMethodType: paymentMethodType ?? '',
totalAmount: totalAmount ?? 0,
orderCount: orderCount ?? 0,
paymentCount: paymentCount ?? 0,
percentage: percentage ?? 0.0,
);
}
/// ===================== RECENT SALE DTO =====================
@freezed
class DashboardRecentSaleDto with _$DashboardRecentSaleDto {
const DashboardRecentSaleDto._();
const factory DashboardRecentSaleDto({
@JsonKey(name: 'date') String? date,
@JsonKey(name: 'sales') int? sales,
@JsonKey(name: 'orders') int? orders,
@JsonKey(name: 'items') int? items,
@JsonKey(name: 'tax') int? tax,
@JsonKey(name: 'discount') int? discount,
@JsonKey(name: 'net_sales') int? netSales,
}) = _DashboardRecentSaleDto;
factory DashboardRecentSaleDto.fromJson(Map<String, dynamic> json) =>
_$DashboardRecentSaleDtoFromJson(json);
DashboardRecentSale toDomain() => DashboardRecentSale(
date: date ?? '',
sales: sales ?? 0,
orders: orders ?? 0,
items: items ?? 0,
tax: tax ?? 0,
discount: discount ?? 0,
netSales: netSales ?? 0,
);
}
@@ -115,4 +115,31 @@ class AnalyticRepository implements IAnalyticRepository {
return left(const AnalyticFailure.unexpectedError());
}
}
@override
Future<Either<AnalyticFailure, DashboardAnalytic>> getDashboard({
required DateTime dateFrom,
required DateTime dateTo,
}) async {
try {
User currentUser = await _authLocalDataProvider.currentUser();
final result = await _dataProvider.fetchDashboard(
outletId: currentUser.outletId,
dateFrom: dateFrom,
dateTo: dateTo,
);
if (result.hasError) {
return left(result.error!);
}
final auth = result.data!.toDomain();
return right(auth);
} catch (e, s) {
log('getDashboardError', name: _logName, error: e, stackTrace: s);
return left(const AnalyticFailure.unexpectedError());
}
}
}