From 0917c5132bf8d21c9090e1350a3297860d30f491 Mon Sep 17 00:00:00 2001 From: Efril Date: Wed, 24 Jun 2026 10:14:37 +0700 Subject: [PATCH] feat: update profit loss ui --- lib/domain/analytic/analytic.freezed.dart | 1160 +++++++++++++- .../entities/profit_loss_analytic_entity.dart | 88 ++ .../analytic/analytic_dtos.freezed.dart | 1386 ++++++++++++++++- .../analytic/analytic_dtos.g.dart | 113 ++ .../dto/profit_loss_analytic_dto.dart | 112 ++ lib/l10n/app_en.arb | 26 +- lib/l10n/app_id.arb | 26 +- lib/l10n/app_localizations.dart | 30 + lib/l10n/app_localizations_en.dart | 19 + lib/l10n/app_localizations_id.dart | 19 + .../pages/finance/finance_page.dart | 291 +--- .../pages/finance/widgets/cash_flow.dart | 476 ------ .../pages/finance/widgets/category.dart | 209 --- .../pages/finance/widgets/cost_breakdown.dart | 143 ++ .../pages/finance/widgets/product.dart | 148 -- .../pages/finance/widgets/profit_loss.dart | 192 --- .../finance/widgets/profit_loss_header.dart | 389 +++++ .../finance/widgets/profit_loss_report.dart | 238 +++ .../pages/finance/widgets/summary_card.dart | 70 - .../pages/purchase/purchase_page.dart | 397 +---- .../purchase/widgets/purchase_header.dart | 318 ++++ .../widgets/purchase_rincian_card.dart | 167 ++ 22 files changed, 4332 insertions(+), 1685 deletions(-) delete mode 100644 lib/presentation/pages/finance/widgets/cash_flow.dart delete mode 100644 lib/presentation/pages/finance/widgets/category.dart create mode 100644 lib/presentation/pages/finance/widgets/cost_breakdown.dart delete mode 100644 lib/presentation/pages/finance/widgets/product.dart delete mode 100644 lib/presentation/pages/finance/widgets/profit_loss.dart create mode 100644 lib/presentation/pages/finance/widgets/profit_loss_header.dart create mode 100644 lib/presentation/pages/finance/widgets/profit_loss_report.dart delete mode 100644 lib/presentation/pages/finance/widgets/summary_card.dart create mode 100644 lib/presentation/pages/purchase/widgets/purchase_header.dart create mode 100644 lib/presentation/pages/purchase/widgets/purchase_rincian_card.dart diff --git a/lib/domain/analytic/analytic.freezed.dart b/lib/domain/analytic/analytic.freezed.dart index 933f11d..d29a836 100644 --- a/lib/domain/analytic/analytic.freezed.dart +++ b/lib/domain/analytic/analytic.freezed.dart @@ -901,6 +901,8 @@ abstract class _SalesAnalyticData implements SalesAnalyticData { /// @nodoc mixin _$ProfitLossAnalytic { String get organizationId => throw _privateConstructorUsedError; + String get outletId => throw _privateConstructorUsedError; + String get outletName => throw _privateConstructorUsedError; String get dateFrom => throw _privateConstructorUsedError; String get dateTo => throw _privateConstructorUsedError; String get groupBy => throw _privateConstructorUsedError; @@ -908,6 +910,12 @@ mixin _$ProfitLossAnalytic { List get data => throw _privateConstructorUsedError; List get productData => throw _privateConstructorUsedError; + List get mainSummary => + throw _privateConstructorUsedError; + ProfitLossPurchasing get purchasing => throw _privateConstructorUsedError; + List get operationalExpenses => + throw _privateConstructorUsedError; + int get operationalExpensesTotal => throw _privateConstructorUsedError; /// Create a copy of ProfitLossAnalytic /// with the given fields replaced by the non-null parameter values. @@ -925,15 +933,22 @@ abstract class $ProfitLossAnalyticCopyWith<$Res> { @useResult $Res call({ String organizationId, + String outletId, + String outletName, String dateFrom, String dateTo, String groupBy, ProfitLossSummary summary, List data, List productData, + List mainSummary, + ProfitLossPurchasing purchasing, + List operationalExpenses, + int operationalExpensesTotal, }); $ProfitLossSummaryCopyWith<$Res> get summary; + $ProfitLossPurchasingCopyWith<$Res> get purchasing; } /// @nodoc @@ -952,12 +967,18 @@ class _$ProfitLossAnalyticCopyWithImpl<$Res, $Val extends ProfitLossAnalytic> @override $Res call({ Object? organizationId = null, + Object? outletId = null, + Object? outletName = null, Object? dateFrom = null, Object? dateTo = null, Object? groupBy = null, Object? summary = null, Object? data = null, Object? productData = null, + Object? mainSummary = null, + Object? purchasing = null, + Object? operationalExpenses = null, + Object? operationalExpensesTotal = null, }) { return _then( _value.copyWith( @@ -965,6 +986,14 @@ class _$ProfitLossAnalyticCopyWithImpl<$Res, $Val extends ProfitLossAnalytic> ? _value.organizationId : organizationId // ignore: cast_nullable_to_non_nullable as String, + outletId: null == outletId + ? _value.outletId + : outletId // ignore: cast_nullable_to_non_nullable + as String, + outletName: null == outletName + ? _value.outletName + : outletName // ignore: cast_nullable_to_non_nullable + as String, dateFrom: null == dateFrom ? _value.dateFrom : dateFrom // ignore: cast_nullable_to_non_nullable @@ -989,6 +1018,22 @@ class _$ProfitLossAnalyticCopyWithImpl<$Res, $Val extends ProfitLossAnalytic> ? _value.productData : productData // ignore: cast_nullable_to_non_nullable as List, + mainSummary: null == mainSummary + ? _value.mainSummary + : mainSummary // ignore: cast_nullable_to_non_nullable + as List, + purchasing: null == purchasing + ? _value.purchasing + : purchasing // ignore: cast_nullable_to_non_nullable + as ProfitLossPurchasing, + operationalExpenses: null == operationalExpenses + ? _value.operationalExpenses + : operationalExpenses // ignore: cast_nullable_to_non_nullable + as List, + operationalExpensesTotal: null == operationalExpensesTotal + ? _value.operationalExpensesTotal + : operationalExpensesTotal // ignore: cast_nullable_to_non_nullable + as int, ) as $Val, ); @@ -1003,6 +1048,16 @@ class _$ProfitLossAnalyticCopyWithImpl<$Res, $Val extends ProfitLossAnalytic> return _then(_value.copyWith(summary: value) as $Val); }); } + + /// Create a copy of ProfitLossAnalytic + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ProfitLossPurchasingCopyWith<$Res> get purchasing { + return $ProfitLossPurchasingCopyWith<$Res>(_value.purchasing, (value) { + return _then(_value.copyWith(purchasing: value) as $Val); + }); + } } /// @nodoc @@ -1016,16 +1071,24 @@ abstract class _$$ProfitLossAnalyticImplCopyWith<$Res> @useResult $Res call({ String organizationId, + String outletId, + String outletName, String dateFrom, String dateTo, String groupBy, ProfitLossSummary summary, List data, List productData, + List mainSummary, + ProfitLossPurchasing purchasing, + List operationalExpenses, + int operationalExpensesTotal, }); @override $ProfitLossSummaryCopyWith<$Res> get summary; + @override + $ProfitLossPurchasingCopyWith<$Res> get purchasing; } /// @nodoc @@ -1043,12 +1106,18 @@ class __$$ProfitLossAnalyticImplCopyWithImpl<$Res> @override $Res call({ Object? organizationId = null, + Object? outletId = null, + Object? outletName = null, Object? dateFrom = null, Object? dateTo = null, Object? groupBy = null, Object? summary = null, Object? data = null, Object? productData = null, + Object? mainSummary = null, + Object? purchasing = null, + Object? operationalExpenses = null, + Object? operationalExpensesTotal = null, }) { return _then( _$ProfitLossAnalyticImpl( @@ -1056,6 +1125,14 @@ class __$$ProfitLossAnalyticImplCopyWithImpl<$Res> ? _value.organizationId : organizationId // ignore: cast_nullable_to_non_nullable as String, + outletId: null == outletId + ? _value.outletId + : outletId // ignore: cast_nullable_to_non_nullable + as String, + outletName: null == outletName + ? _value.outletName + : outletName // ignore: cast_nullable_to_non_nullable + as String, dateFrom: null == dateFrom ? _value.dateFrom : dateFrom // ignore: cast_nullable_to_non_nullable @@ -1080,6 +1157,22 @@ class __$$ProfitLossAnalyticImplCopyWithImpl<$Res> ? _value._productData : productData // ignore: cast_nullable_to_non_nullable as List, + mainSummary: null == mainSummary + ? _value._mainSummary + : mainSummary // ignore: cast_nullable_to_non_nullable + as List, + purchasing: null == purchasing + ? _value.purchasing + : purchasing // ignore: cast_nullable_to_non_nullable + as ProfitLossPurchasing, + operationalExpenses: null == operationalExpenses + ? _value._operationalExpenses + : operationalExpenses // ignore: cast_nullable_to_non_nullable + as List, + operationalExpensesTotal: null == operationalExpensesTotal + ? _value.operationalExpensesTotal + : operationalExpensesTotal // ignore: cast_nullable_to_non_nullable + as int, ), ); } @@ -1090,18 +1183,30 @@ class __$$ProfitLossAnalyticImplCopyWithImpl<$Res> class _$ProfitLossAnalyticImpl implements _ProfitLossAnalytic { const _$ProfitLossAnalyticImpl({ required this.organizationId, + required this.outletId, + required this.outletName, required this.dateFrom, required this.dateTo, required this.groupBy, required this.summary, required final List data, required final List productData, + required final List mainSummary, + required this.purchasing, + required final List operationalExpenses, + required this.operationalExpensesTotal, }) : _data = data, - _productData = productData; + _productData = productData, + _mainSummary = mainSummary, + _operationalExpenses = operationalExpenses; @override final String organizationId; @override + final String outletId; + @override + final String outletName; + @override final String dateFrom; @override final String dateTo; @@ -1125,9 +1230,31 @@ class _$ProfitLossAnalyticImpl implements _ProfitLossAnalytic { return EqualUnmodifiableListView(_productData); } + final List _mainSummary; + @override + List get mainSummary { + if (_mainSummary is EqualUnmodifiableListView) return _mainSummary; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_mainSummary); + } + + @override + final ProfitLossPurchasing purchasing; + final List _operationalExpenses; + @override + List get operationalExpenses { + if (_operationalExpenses is EqualUnmodifiableListView) + return _operationalExpenses; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_operationalExpenses); + } + + @override + final int operationalExpensesTotal; + @override String toString() { - return 'ProfitLossAnalytic(organizationId: $organizationId, dateFrom: $dateFrom, dateTo: $dateTo, groupBy: $groupBy, summary: $summary, data: $data, productData: $productData)'; + return 'ProfitLossAnalytic(organizationId: $organizationId, outletId: $outletId, outletName: $outletName, dateFrom: $dateFrom, dateTo: $dateTo, groupBy: $groupBy, summary: $summary, data: $data, productData: $productData, mainSummary: $mainSummary, purchasing: $purchasing, operationalExpenses: $operationalExpenses, operationalExpensesTotal: $operationalExpensesTotal)'; } @override @@ -1137,6 +1264,10 @@ class _$ProfitLossAnalyticImpl implements _ProfitLossAnalytic { other is _$ProfitLossAnalyticImpl && (identical(other.organizationId, organizationId) || other.organizationId == organizationId) && + (identical(other.outletId, outletId) || + other.outletId == outletId) && + (identical(other.outletName, outletName) || + other.outletName == outletName) && (identical(other.dateFrom, dateFrom) || other.dateFrom == dateFrom) && (identical(other.dateTo, dateTo) || other.dateTo == dateTo) && @@ -1146,19 +1277,40 @@ class _$ProfitLossAnalyticImpl implements _ProfitLossAnalytic { const DeepCollectionEquality().equals( other._productData, _productData, - )); + ) && + const DeepCollectionEquality().equals( + other._mainSummary, + _mainSummary, + ) && + (identical(other.purchasing, purchasing) || + other.purchasing == purchasing) && + const DeepCollectionEquality().equals( + other._operationalExpenses, + _operationalExpenses, + ) && + (identical( + other.operationalExpensesTotal, + operationalExpensesTotal, + ) || + other.operationalExpensesTotal == operationalExpensesTotal)); } @override int get hashCode => Object.hash( runtimeType, organizationId, + outletId, + outletName, dateFrom, dateTo, groupBy, summary, const DeepCollectionEquality().hash(_data), const DeepCollectionEquality().hash(_productData), + const DeepCollectionEquality().hash(_mainSummary), + purchasing, + const DeepCollectionEquality().hash(_operationalExpenses), + operationalExpensesTotal, ); /// Create a copy of ProfitLossAnalytic @@ -1176,17 +1328,27 @@ class _$ProfitLossAnalyticImpl implements _ProfitLossAnalytic { abstract class _ProfitLossAnalytic implements ProfitLossAnalytic { const factory _ProfitLossAnalytic({ required final String organizationId, + required final String outletId, + required final String outletName, required final String dateFrom, required final String dateTo, required final String groupBy, required final ProfitLossSummary summary, required final List data, required final List productData, + required final List mainSummary, + required final ProfitLossPurchasing purchasing, + required final List operationalExpenses, + required final int operationalExpensesTotal, }) = _$ProfitLossAnalyticImpl; @override String get organizationId; @override + String get outletId; + @override + String get outletName; + @override String get dateFrom; @override String get dateTo; @@ -1198,6 +1360,14 @@ abstract class _ProfitLossAnalytic implements ProfitLossAnalytic { List get data; @override List get productData; + @override + List get mainSummary; + @override + ProfitLossPurchasing get purchasing; + @override + List get operationalExpenses; + @override + int get operationalExpensesTotal; /// Create a copy of ProfitLossAnalytic /// with the given fields replaced by the non-null parameter values. @@ -2318,6 +2488,990 @@ abstract class _ProfitLossProductData implements ProfitLossProductData { get copyWith => throw _privateConstructorUsedError; } +/// @nodoc +mixin _$ProfitLossMainSummaryItem { + String get id => throw _privateConstructorUsedError; + String get label => throw _privateConstructorUsedError; + bool get isBold => throw _privateConstructorUsedError; + int get todayNominal => throw _privateConstructorUsedError; + double get todayPct => throw _privateConstructorUsedError; + int get mtdNominal => throw _privateConstructorUsedError; + double get mtdPct => throw _privateConstructorUsedError; + List get subItems => + throw _privateConstructorUsedError; + + /// Create a copy of ProfitLossMainSummaryItem + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProfitLossMainSummaryItemCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProfitLossMainSummaryItemCopyWith<$Res> { + factory $ProfitLossMainSummaryItemCopyWith( + ProfitLossMainSummaryItem value, + $Res Function(ProfitLossMainSummaryItem) then, + ) = _$ProfitLossMainSummaryItemCopyWithImpl<$Res, ProfitLossMainSummaryItem>; + @useResult + $Res call({ + String id, + String label, + bool isBold, + int todayNominal, + double todayPct, + int mtdNominal, + double mtdPct, + List subItems, + }); +} + +/// @nodoc +class _$ProfitLossMainSummaryItemCopyWithImpl< + $Res, + $Val extends ProfitLossMainSummaryItem +> + implements $ProfitLossMainSummaryItemCopyWith<$Res> { + _$ProfitLossMainSummaryItemCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProfitLossMainSummaryItem + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? label = null, + Object? isBold = null, + Object? todayNominal = null, + Object? todayPct = null, + Object? mtdNominal = null, + Object? mtdPct = null, + Object? subItems = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + label: null == label + ? _value.label + : label // ignore: cast_nullable_to_non_nullable + as String, + isBold: null == isBold + ? _value.isBold + : isBold // ignore: cast_nullable_to_non_nullable + as bool, + todayNominal: null == todayNominal + ? _value.todayNominal + : todayNominal // ignore: cast_nullable_to_non_nullable + as int, + todayPct: null == todayPct + ? _value.todayPct + : todayPct // ignore: cast_nullable_to_non_nullable + as double, + mtdNominal: null == mtdNominal + ? _value.mtdNominal + : mtdNominal // ignore: cast_nullable_to_non_nullable + as int, + mtdPct: null == mtdPct + ? _value.mtdPct + : mtdPct // ignore: cast_nullable_to_non_nullable + as double, + subItems: null == subItems + ? _value.subItems + : subItems // ignore: cast_nullable_to_non_nullable + as List, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProfitLossMainSummaryItemImplCopyWith<$Res> + implements $ProfitLossMainSummaryItemCopyWith<$Res> { + factory _$$ProfitLossMainSummaryItemImplCopyWith( + _$ProfitLossMainSummaryItemImpl value, + $Res Function(_$ProfitLossMainSummaryItemImpl) then, + ) = __$$ProfitLossMainSummaryItemImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + String label, + bool isBold, + int todayNominal, + double todayPct, + int mtdNominal, + double mtdPct, + List subItems, + }); +} + +/// @nodoc +class __$$ProfitLossMainSummaryItemImplCopyWithImpl<$Res> + extends + _$ProfitLossMainSummaryItemCopyWithImpl< + $Res, + _$ProfitLossMainSummaryItemImpl + > + implements _$$ProfitLossMainSummaryItemImplCopyWith<$Res> { + __$$ProfitLossMainSummaryItemImplCopyWithImpl( + _$ProfitLossMainSummaryItemImpl _value, + $Res Function(_$ProfitLossMainSummaryItemImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProfitLossMainSummaryItem + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? label = null, + Object? isBold = null, + Object? todayNominal = null, + Object? todayPct = null, + Object? mtdNominal = null, + Object? mtdPct = null, + Object? subItems = null, + }) { + return _then( + _$ProfitLossMainSummaryItemImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + label: null == label + ? _value.label + : label // ignore: cast_nullable_to_non_nullable + as String, + isBold: null == isBold + ? _value.isBold + : isBold // ignore: cast_nullable_to_non_nullable + as bool, + todayNominal: null == todayNominal + ? _value.todayNominal + : todayNominal // ignore: cast_nullable_to_non_nullable + as int, + todayPct: null == todayPct + ? _value.todayPct + : todayPct // ignore: cast_nullable_to_non_nullable + as double, + mtdNominal: null == mtdNominal + ? _value.mtdNominal + : mtdNominal // ignore: cast_nullable_to_non_nullable + as int, + mtdPct: null == mtdPct + ? _value.mtdPct + : mtdPct // ignore: cast_nullable_to_non_nullable + as double, + subItems: null == subItems + ? _value._subItems + : subItems // ignore: cast_nullable_to_non_nullable + as List, + ), + ); + } +} + +/// @nodoc + +class _$ProfitLossMainSummaryItemImpl implements _ProfitLossMainSummaryItem { + const _$ProfitLossMainSummaryItemImpl({ + required this.id, + required this.label, + required this.isBold, + required this.todayNominal, + required this.todayPct, + required this.mtdNominal, + required this.mtdPct, + final List subItems = const [], + }) : _subItems = subItems; + + @override + final String id; + @override + final String label; + @override + final bool isBold; + @override + final int todayNominal; + @override + final double todayPct; + @override + final int mtdNominal; + @override + final double mtdPct; + final List _subItems; + @override + @JsonKey() + List get subItems { + if (_subItems is EqualUnmodifiableListView) return _subItems; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_subItems); + } + + @override + String toString() { + return 'ProfitLossMainSummaryItem(id: $id, label: $label, isBold: $isBold, todayNominal: $todayNominal, todayPct: $todayPct, mtdNominal: $mtdNominal, mtdPct: $mtdPct, subItems: $subItems)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProfitLossMainSummaryItemImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.label, label) || other.label == label) && + (identical(other.isBold, isBold) || other.isBold == isBold) && + (identical(other.todayNominal, todayNominal) || + other.todayNominal == todayNominal) && + (identical(other.todayPct, todayPct) || + other.todayPct == todayPct) && + (identical(other.mtdNominal, mtdNominal) || + other.mtdNominal == mtdNominal) && + (identical(other.mtdPct, mtdPct) || other.mtdPct == mtdPct) && + const DeepCollectionEquality().equals(other._subItems, _subItems)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + id, + label, + isBold, + todayNominal, + todayPct, + mtdNominal, + mtdPct, + const DeepCollectionEquality().hash(_subItems), + ); + + /// Create a copy of ProfitLossMainSummaryItem + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProfitLossMainSummaryItemImplCopyWith<_$ProfitLossMainSummaryItemImpl> + get copyWith => + __$$ProfitLossMainSummaryItemImplCopyWithImpl< + _$ProfitLossMainSummaryItemImpl + >(this, _$identity); +} + +abstract class _ProfitLossMainSummaryItem implements ProfitLossMainSummaryItem { + const factory _ProfitLossMainSummaryItem({ + required final String id, + required final String label, + required final bool isBold, + required final int todayNominal, + required final double todayPct, + required final int mtdNominal, + required final double mtdPct, + final List subItems, + }) = _$ProfitLossMainSummaryItemImpl; + + @override + String get id; + @override + String get label; + @override + bool get isBold; + @override + int get todayNominal; + @override + double get todayPct; + @override + int get mtdNominal; + @override + double get mtdPct; + @override + List get subItems; + + /// Create a copy of ProfitLossMainSummaryItem + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProfitLossMainSummaryItemImplCopyWith<_$ProfitLossMainSummaryItemImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$ProfitLossPurchasing { + int get todayTotal => throw _privateConstructorUsedError; + int get mtdTotal => throw _privateConstructorUsedError; + int get todayRawMaterial => throw _privateConstructorUsedError; + int get mtdRawMaterial => throw _privateConstructorUsedError; + int get todayExpense => throw _privateConstructorUsedError; + int get mtdExpense => throw _privateConstructorUsedError; + List get items => + throw _privateConstructorUsedError; + + /// Create a copy of ProfitLossPurchasing + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProfitLossPurchasingCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProfitLossPurchasingCopyWith<$Res> { + factory $ProfitLossPurchasingCopyWith( + ProfitLossPurchasing value, + $Res Function(ProfitLossPurchasing) then, + ) = _$ProfitLossPurchasingCopyWithImpl<$Res, ProfitLossPurchasing>; + @useResult + $Res call({ + int todayTotal, + int mtdTotal, + int todayRawMaterial, + int mtdRawMaterial, + int todayExpense, + int mtdExpense, + List items, + }); +} + +/// @nodoc +class _$ProfitLossPurchasingCopyWithImpl< + $Res, + $Val extends ProfitLossPurchasing +> + implements $ProfitLossPurchasingCopyWith<$Res> { + _$ProfitLossPurchasingCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProfitLossPurchasing + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? todayTotal = null, + Object? mtdTotal = null, + Object? todayRawMaterial = null, + Object? mtdRawMaterial = null, + Object? todayExpense = null, + Object? mtdExpense = null, + Object? items = null, + }) { + return _then( + _value.copyWith( + todayTotal: null == todayTotal + ? _value.todayTotal + : todayTotal // ignore: cast_nullable_to_non_nullable + as int, + mtdTotal: null == mtdTotal + ? _value.mtdTotal + : mtdTotal // ignore: cast_nullable_to_non_nullable + as int, + todayRawMaterial: null == todayRawMaterial + ? _value.todayRawMaterial + : todayRawMaterial // ignore: cast_nullable_to_non_nullable + as int, + mtdRawMaterial: null == mtdRawMaterial + ? _value.mtdRawMaterial + : mtdRawMaterial // ignore: cast_nullable_to_non_nullable + as int, + todayExpense: null == todayExpense + ? _value.todayExpense + : todayExpense // ignore: cast_nullable_to_non_nullable + as int, + mtdExpense: null == mtdExpense + ? _value.mtdExpense + : mtdExpense // ignore: cast_nullable_to_non_nullable + as int, + items: null == items + ? _value.items + : items // ignore: cast_nullable_to_non_nullable + as List, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProfitLossPurchasingImplCopyWith<$Res> + implements $ProfitLossPurchasingCopyWith<$Res> { + factory _$$ProfitLossPurchasingImplCopyWith( + _$ProfitLossPurchasingImpl value, + $Res Function(_$ProfitLossPurchasingImpl) then, + ) = __$$ProfitLossPurchasingImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + int todayTotal, + int mtdTotal, + int todayRawMaterial, + int mtdRawMaterial, + int todayExpense, + int mtdExpense, + List items, + }); +} + +/// @nodoc +class __$$ProfitLossPurchasingImplCopyWithImpl<$Res> + extends _$ProfitLossPurchasingCopyWithImpl<$Res, _$ProfitLossPurchasingImpl> + implements _$$ProfitLossPurchasingImplCopyWith<$Res> { + __$$ProfitLossPurchasingImplCopyWithImpl( + _$ProfitLossPurchasingImpl _value, + $Res Function(_$ProfitLossPurchasingImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProfitLossPurchasing + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? todayTotal = null, + Object? mtdTotal = null, + Object? todayRawMaterial = null, + Object? mtdRawMaterial = null, + Object? todayExpense = null, + Object? mtdExpense = null, + Object? items = null, + }) { + return _then( + _$ProfitLossPurchasingImpl( + todayTotal: null == todayTotal + ? _value.todayTotal + : todayTotal // ignore: cast_nullable_to_non_nullable + as int, + mtdTotal: null == mtdTotal + ? _value.mtdTotal + : mtdTotal // ignore: cast_nullable_to_non_nullable + as int, + todayRawMaterial: null == todayRawMaterial + ? _value.todayRawMaterial + : todayRawMaterial // ignore: cast_nullable_to_non_nullable + as int, + mtdRawMaterial: null == mtdRawMaterial + ? _value.mtdRawMaterial + : mtdRawMaterial // ignore: cast_nullable_to_non_nullable + as int, + todayExpense: null == todayExpense + ? _value.todayExpense + : todayExpense // ignore: cast_nullable_to_non_nullable + as int, + mtdExpense: null == mtdExpense + ? _value.mtdExpense + : mtdExpense // ignore: cast_nullable_to_non_nullable + as int, + items: null == items + ? _value._items + : items // ignore: cast_nullable_to_non_nullable + as List, + ), + ); + } +} + +/// @nodoc + +class _$ProfitLossPurchasingImpl implements _ProfitLossPurchasing { + const _$ProfitLossPurchasingImpl({ + required this.todayTotal, + required this.mtdTotal, + required this.todayRawMaterial, + required this.mtdRawMaterial, + required this.todayExpense, + required this.mtdExpense, + final List items = const [], + }) : _items = items; + + @override + final int todayTotal; + @override + final int mtdTotal; + @override + final int todayRawMaterial; + @override + final int mtdRawMaterial; + @override + final int todayExpense; + @override + final int mtdExpense; + final List _items; + @override + @JsonKey() + List get items { + if (_items is EqualUnmodifiableListView) return _items; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_items); + } + + @override + String toString() { + return 'ProfitLossPurchasing(todayTotal: $todayTotal, mtdTotal: $mtdTotal, todayRawMaterial: $todayRawMaterial, mtdRawMaterial: $mtdRawMaterial, todayExpense: $todayExpense, mtdExpense: $mtdExpense, items: $items)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProfitLossPurchasingImpl && + (identical(other.todayTotal, todayTotal) || + other.todayTotal == todayTotal) && + (identical(other.mtdTotal, mtdTotal) || + other.mtdTotal == mtdTotal) && + (identical(other.todayRawMaterial, todayRawMaterial) || + other.todayRawMaterial == todayRawMaterial) && + (identical(other.mtdRawMaterial, mtdRawMaterial) || + other.mtdRawMaterial == mtdRawMaterial) && + (identical(other.todayExpense, todayExpense) || + other.todayExpense == todayExpense) && + (identical(other.mtdExpense, mtdExpense) || + other.mtdExpense == mtdExpense) && + const DeepCollectionEquality().equals(other._items, _items)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + todayTotal, + mtdTotal, + todayRawMaterial, + mtdRawMaterial, + todayExpense, + mtdExpense, + const DeepCollectionEquality().hash(_items), + ); + + /// Create a copy of ProfitLossPurchasing + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProfitLossPurchasingImplCopyWith<_$ProfitLossPurchasingImpl> + get copyWith => + __$$ProfitLossPurchasingImplCopyWithImpl<_$ProfitLossPurchasingImpl>( + this, + _$identity, + ); +} + +abstract class _ProfitLossPurchasing implements ProfitLossPurchasing { + const factory _ProfitLossPurchasing({ + required final int todayTotal, + required final int mtdTotal, + required final int todayRawMaterial, + required final int mtdRawMaterial, + required final int todayExpense, + required final int mtdExpense, + final List items, + }) = _$ProfitLossPurchasingImpl; + + @override + int get todayTotal; + @override + int get mtdTotal; + @override + int get todayRawMaterial; + @override + int get mtdRawMaterial; + @override + int get todayExpense; + @override + int get mtdExpense; + @override + List get items; + + /// Create a copy of ProfitLossPurchasing + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProfitLossPurchasingImplCopyWith<_$ProfitLossPurchasingImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$ProfitLossPurchasingItem { + String get date => throw _privateConstructorUsedError; + String get item => throw _privateConstructorUsedError; + int get quantity => throw _privateConstructorUsedError; + int get nominal => throw _privateConstructorUsedError; + + /// Create a copy of ProfitLossPurchasingItem + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProfitLossPurchasingItemCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProfitLossPurchasingItemCopyWith<$Res> { + factory $ProfitLossPurchasingItemCopyWith( + ProfitLossPurchasingItem value, + $Res Function(ProfitLossPurchasingItem) then, + ) = _$ProfitLossPurchasingItemCopyWithImpl<$Res, ProfitLossPurchasingItem>; + @useResult + $Res call({String date, String item, int quantity, int nominal}); +} + +/// @nodoc +class _$ProfitLossPurchasingItemCopyWithImpl< + $Res, + $Val extends ProfitLossPurchasingItem +> + implements $ProfitLossPurchasingItemCopyWith<$Res> { + _$ProfitLossPurchasingItemCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProfitLossPurchasingItem + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? date = null, + Object? item = null, + Object? quantity = null, + Object? nominal = null, + }) { + return _then( + _value.copyWith( + date: null == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as String, + item: null == item + ? _value.item + : item // ignore: cast_nullable_to_non_nullable + as String, + quantity: null == quantity + ? _value.quantity + : quantity // ignore: cast_nullable_to_non_nullable + as int, + nominal: null == nominal + ? _value.nominal + : nominal // ignore: cast_nullable_to_non_nullable + as int, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProfitLossPurchasingItemImplCopyWith<$Res> + implements $ProfitLossPurchasingItemCopyWith<$Res> { + factory _$$ProfitLossPurchasingItemImplCopyWith( + _$ProfitLossPurchasingItemImpl value, + $Res Function(_$ProfitLossPurchasingItemImpl) then, + ) = __$$ProfitLossPurchasingItemImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String date, String item, int quantity, int nominal}); +} + +/// @nodoc +class __$$ProfitLossPurchasingItemImplCopyWithImpl<$Res> + extends + _$ProfitLossPurchasingItemCopyWithImpl< + $Res, + _$ProfitLossPurchasingItemImpl + > + implements _$$ProfitLossPurchasingItemImplCopyWith<$Res> { + __$$ProfitLossPurchasingItemImplCopyWithImpl( + _$ProfitLossPurchasingItemImpl _value, + $Res Function(_$ProfitLossPurchasingItemImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProfitLossPurchasingItem + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? date = null, + Object? item = null, + Object? quantity = null, + Object? nominal = null, + }) { + return _then( + _$ProfitLossPurchasingItemImpl( + date: null == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as String, + item: null == item + ? _value.item + : item // ignore: cast_nullable_to_non_nullable + as String, + quantity: null == quantity + ? _value.quantity + : quantity // ignore: cast_nullable_to_non_nullable + as int, + nominal: null == nominal + ? _value.nominal + : nominal // ignore: cast_nullable_to_non_nullable + as int, + ), + ); + } +} + +/// @nodoc + +class _$ProfitLossPurchasingItemImpl implements _ProfitLossPurchasingItem { + const _$ProfitLossPurchasingItemImpl({ + required this.date, + required this.item, + required this.quantity, + required this.nominal, + }); + + @override + final String date; + @override + final String item; + @override + final int quantity; + @override + final int nominal; + + @override + String toString() { + return 'ProfitLossPurchasingItem(date: $date, item: $item, quantity: $quantity, nominal: $nominal)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProfitLossPurchasingItemImpl && + (identical(other.date, date) || other.date == date) && + (identical(other.item, item) || other.item == item) && + (identical(other.quantity, quantity) || + other.quantity == quantity) && + (identical(other.nominal, nominal) || other.nominal == nominal)); + } + + @override + int get hashCode => Object.hash(runtimeType, date, item, quantity, nominal); + + /// Create a copy of ProfitLossPurchasingItem + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProfitLossPurchasingItemImplCopyWith<_$ProfitLossPurchasingItemImpl> + get copyWith => + __$$ProfitLossPurchasingItemImplCopyWithImpl< + _$ProfitLossPurchasingItemImpl + >(this, _$identity); +} + +abstract class _ProfitLossPurchasingItem implements ProfitLossPurchasingItem { + const factory _ProfitLossPurchasingItem({ + required final String date, + required final String item, + required final int quantity, + required final int nominal, + }) = _$ProfitLossPurchasingItemImpl; + + @override + String get date; + @override + String get item; + @override + int get quantity; + @override + int get nominal; + + /// Create a copy of ProfitLossPurchasingItem + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProfitLossPurchasingItemImplCopyWith<_$ProfitLossPurchasingItemImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$ProfitLossOperationalExpense { + String get item => throw _privateConstructorUsedError; + int get nominal => throw _privateConstructorUsedError; + + /// Create a copy of ProfitLossOperationalExpense + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProfitLossOperationalExpenseCopyWith + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProfitLossOperationalExpenseCopyWith<$Res> { + factory $ProfitLossOperationalExpenseCopyWith( + ProfitLossOperationalExpense value, + $Res Function(ProfitLossOperationalExpense) then, + ) = + _$ProfitLossOperationalExpenseCopyWithImpl< + $Res, + ProfitLossOperationalExpense + >; + @useResult + $Res call({String item, int nominal}); +} + +/// @nodoc +class _$ProfitLossOperationalExpenseCopyWithImpl< + $Res, + $Val extends ProfitLossOperationalExpense +> + implements $ProfitLossOperationalExpenseCopyWith<$Res> { + _$ProfitLossOperationalExpenseCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProfitLossOperationalExpense + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? item = null, Object? nominal = null}) { + return _then( + _value.copyWith( + item: null == item + ? _value.item + : item // ignore: cast_nullable_to_non_nullable + as String, + nominal: null == nominal + ? _value.nominal + : nominal // ignore: cast_nullable_to_non_nullable + as int, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProfitLossOperationalExpenseImplCopyWith<$Res> + implements $ProfitLossOperationalExpenseCopyWith<$Res> { + factory _$$ProfitLossOperationalExpenseImplCopyWith( + _$ProfitLossOperationalExpenseImpl value, + $Res Function(_$ProfitLossOperationalExpenseImpl) then, + ) = __$$ProfitLossOperationalExpenseImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String item, int nominal}); +} + +/// @nodoc +class __$$ProfitLossOperationalExpenseImplCopyWithImpl<$Res> + extends + _$ProfitLossOperationalExpenseCopyWithImpl< + $Res, + _$ProfitLossOperationalExpenseImpl + > + implements _$$ProfitLossOperationalExpenseImplCopyWith<$Res> { + __$$ProfitLossOperationalExpenseImplCopyWithImpl( + _$ProfitLossOperationalExpenseImpl _value, + $Res Function(_$ProfitLossOperationalExpenseImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProfitLossOperationalExpense + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? item = null, Object? nominal = null}) { + return _then( + _$ProfitLossOperationalExpenseImpl( + item: null == item + ? _value.item + : item // ignore: cast_nullable_to_non_nullable + as String, + nominal: null == nominal + ? _value.nominal + : nominal // ignore: cast_nullable_to_non_nullable + as int, + ), + ); + } +} + +/// @nodoc + +class _$ProfitLossOperationalExpenseImpl + implements _ProfitLossOperationalExpense { + const _$ProfitLossOperationalExpenseImpl({ + required this.item, + required this.nominal, + }); + + @override + final String item; + @override + final int nominal; + + @override + String toString() { + return 'ProfitLossOperationalExpense(item: $item, nominal: $nominal)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProfitLossOperationalExpenseImpl && + (identical(other.item, item) || other.item == item) && + (identical(other.nominal, nominal) || other.nominal == nominal)); + } + + @override + int get hashCode => Object.hash(runtimeType, item, nominal); + + /// Create a copy of ProfitLossOperationalExpense + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProfitLossOperationalExpenseImplCopyWith< + _$ProfitLossOperationalExpenseImpl + > + get copyWith => + __$$ProfitLossOperationalExpenseImplCopyWithImpl< + _$ProfitLossOperationalExpenseImpl + >(this, _$identity); +} + +abstract class _ProfitLossOperationalExpense + implements ProfitLossOperationalExpense { + const factory _ProfitLossOperationalExpense({ + required final String item, + required final int nominal, + }) = _$ProfitLossOperationalExpenseImpl; + + @override + String get item; + @override + int get nominal; + + /// Create a copy of ProfitLossOperationalExpense + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProfitLossOperationalExpenseImplCopyWith< + _$ProfitLossOperationalExpenseImpl + > + get copyWith => throw _privateConstructorUsedError; +} + /// @nodoc mixin _$CategoryAnalytic { String get organizationId => throw _privateConstructorUsedError; diff --git a/lib/domain/analytic/entities/profit_loss_analytic_entity.dart b/lib/domain/analytic/entities/profit_loss_analytic_entity.dart index 3828307..93a722f 100644 --- a/lib/domain/analytic/entities/profit_loss_analytic_entity.dart +++ b/lib/domain/analytic/entities/profit_loss_analytic_entity.dart @@ -4,22 +4,34 @@ part of '../analytic.dart'; class ProfitLossAnalytic with _$ProfitLossAnalytic { const factory ProfitLossAnalytic({ required String organizationId, + required String outletId, + required String outletName, required String dateFrom, required String dateTo, required String groupBy, required ProfitLossSummary summary, required List data, required List productData, + required List mainSummary, + required ProfitLossPurchasing purchasing, + required List operationalExpenses, + required int operationalExpensesTotal, }) = _ProfitLossAnalytic; factory ProfitLossAnalytic.empty() => ProfitLossAnalytic( organizationId: '', + outletId: '', + outletName: '', dateFrom: '', dateTo: '', groupBy: '', summary: ProfitLossSummary.empty(), data: [], productData: [], + mainSummary: [], + purchasing: ProfitLossPurchasing.empty(), + operationalExpenses: [], + operationalExpensesTotal: 0, ); } @@ -115,3 +127,79 @@ class ProfitLossProductData with _$ProfitLossProductData { profitPerUnit: 0, ); } + +@freezed +class ProfitLossMainSummaryItem with _$ProfitLossMainSummaryItem { + const factory ProfitLossMainSummaryItem({ + required String id, + required String label, + required bool isBold, + required int todayNominal, + required double todayPct, + required int mtdNominal, + required double mtdPct, + @Default([]) List subItems, + }) = _ProfitLossMainSummaryItem; + + factory ProfitLossMainSummaryItem.empty() => const ProfitLossMainSummaryItem( + id: '', + label: '', + isBold: false, + todayNominal: 0, + todayPct: 0, + mtdNominal: 0, + mtdPct: 0, + subItems: [], + ); +} + +@freezed +class ProfitLossPurchasing with _$ProfitLossPurchasing { + const factory ProfitLossPurchasing({ + required int todayTotal, + required int mtdTotal, + required int todayRawMaterial, + required int mtdRawMaterial, + required int todayExpense, + required int mtdExpense, + @Default([]) List items, + }) = _ProfitLossPurchasing; + + factory ProfitLossPurchasing.empty() => const ProfitLossPurchasing( + todayTotal: 0, + mtdTotal: 0, + todayRawMaterial: 0, + mtdRawMaterial: 0, + todayExpense: 0, + mtdExpense: 0, + items: [], + ); +} + +@freezed +class ProfitLossPurchasingItem with _$ProfitLossPurchasingItem { + const factory ProfitLossPurchasingItem({ + required String date, + required String item, + required int quantity, + required int nominal, + }) = _ProfitLossPurchasingItem; + + factory ProfitLossPurchasingItem.empty() => const ProfitLossPurchasingItem( + date: '', + item: '', + quantity: 0, + nominal: 0, + ); +} + +@freezed +class ProfitLossOperationalExpense with _$ProfitLossOperationalExpense { + const factory ProfitLossOperationalExpense({ + required String item, + required int nominal, + }) = _ProfitLossOperationalExpense; + + factory ProfitLossOperationalExpense.empty() => + const ProfitLossOperationalExpense(item: '', nominal: 0); +} diff --git a/lib/infrastructure/analytic/analytic_dtos.freezed.dart b/lib/infrastructure/analytic/analytic_dtos.freezed.dart index 0002fe9..9660b48 100644 --- a/lib/infrastructure/analytic/analytic_dtos.freezed.dart +++ b/lib/infrastructure/analytic/analytic_dtos.freezed.dart @@ -1053,6 +1053,10 @@ ProfitLossAnalyticDto _$ProfitLossAnalyticDtoFromJson( mixin _$ProfitLossAnalyticDto { @JsonKey(name: 'organization_id') String? get organizationId => throw _privateConstructorUsedError; + @JsonKey(name: 'outlet_id') + String? get outletId => throw _privateConstructorUsedError; + @JsonKey(name: 'outlet_name') + String? get outletName => throw _privateConstructorUsedError; @JsonKey(name: 'date_from') String? get dateFrom => throw _privateConstructorUsedError; @JsonKey(name: 'date_to') @@ -1066,6 +1070,16 @@ mixin _$ProfitLossAnalyticDto { @JsonKey(name: 'product_data') List? get productData => throw _privateConstructorUsedError; + @JsonKey(name: 'main_summary') + List? get mainSummary => + throw _privateConstructorUsedError; + @JsonKey(name: 'purchasing') + ProfitLossPurchasingDto? get purchasing => throw _privateConstructorUsedError; + @JsonKey(name: 'operational_expenses') + List? get operationalExpenses => + throw _privateConstructorUsedError; + @JsonKey(name: 'operational_expenses_total') + int? get operationalExpensesTotal => throw _privateConstructorUsedError; /// Serializes this ProfitLossAnalyticDto to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -1086,15 +1100,24 @@ abstract class $ProfitLossAnalyticDtoCopyWith<$Res> { @useResult $Res call({ @JsonKey(name: 'organization_id') String? organizationId, + @JsonKey(name: 'outlet_id') String? outletId, + @JsonKey(name: 'outlet_name') String? outletName, @JsonKey(name: 'date_from') String? dateFrom, @JsonKey(name: 'date_to') String? dateTo, @JsonKey(name: 'group_by') String? groupBy, @JsonKey(name: 'summary') ProfitLossSummaryDto? summary, @JsonKey(name: 'data') List? data, @JsonKey(name: 'product_data') List? productData, + @JsonKey(name: 'main_summary') + List? mainSummary, + @JsonKey(name: 'purchasing') ProfitLossPurchasingDto? purchasing, + @JsonKey(name: 'operational_expenses') + List? operationalExpenses, + @JsonKey(name: 'operational_expenses_total') int? operationalExpensesTotal, }); $ProfitLossSummaryDtoCopyWith<$Res>? get summary; + $ProfitLossPurchasingDtoCopyWith<$Res>? get purchasing; } /// @nodoc @@ -1116,12 +1139,18 @@ class _$ProfitLossAnalyticDtoCopyWithImpl< @override $Res call({ Object? organizationId = freezed, + Object? outletId = freezed, + Object? outletName = freezed, Object? dateFrom = freezed, Object? dateTo = freezed, Object? groupBy = freezed, Object? summary = freezed, Object? data = freezed, Object? productData = freezed, + Object? mainSummary = freezed, + Object? purchasing = freezed, + Object? operationalExpenses = freezed, + Object? operationalExpensesTotal = freezed, }) { return _then( _value.copyWith( @@ -1129,6 +1158,14 @@ class _$ProfitLossAnalyticDtoCopyWithImpl< ? _value.organizationId : organizationId // ignore: cast_nullable_to_non_nullable as String?, + outletId: freezed == outletId + ? _value.outletId + : outletId // ignore: cast_nullable_to_non_nullable + as String?, + outletName: freezed == outletName + ? _value.outletName + : outletName // ignore: cast_nullable_to_non_nullable + as String?, dateFrom: freezed == dateFrom ? _value.dateFrom : dateFrom // ignore: cast_nullable_to_non_nullable @@ -1153,6 +1190,22 @@ class _$ProfitLossAnalyticDtoCopyWithImpl< ? _value.productData : productData // ignore: cast_nullable_to_non_nullable as List?, + mainSummary: freezed == mainSummary + ? _value.mainSummary + : mainSummary // ignore: cast_nullable_to_non_nullable + as List?, + purchasing: freezed == purchasing + ? _value.purchasing + : purchasing // ignore: cast_nullable_to_non_nullable + as ProfitLossPurchasingDto?, + operationalExpenses: freezed == operationalExpenses + ? _value.operationalExpenses + : operationalExpenses // ignore: cast_nullable_to_non_nullable + as List?, + operationalExpensesTotal: freezed == operationalExpensesTotal + ? _value.operationalExpensesTotal + : operationalExpensesTotal // ignore: cast_nullable_to_non_nullable + as int?, ) as $Val, ); @@ -1171,6 +1224,20 @@ class _$ProfitLossAnalyticDtoCopyWithImpl< return _then(_value.copyWith(summary: value) as $Val); }); } + + /// Create a copy of ProfitLossAnalyticDto + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ProfitLossPurchasingDtoCopyWith<$Res>? get purchasing { + if (_value.purchasing == null) { + return null; + } + + return $ProfitLossPurchasingDtoCopyWith<$Res>(_value.purchasing!, (value) { + return _then(_value.copyWith(purchasing: value) as $Val); + }); + } } /// @nodoc @@ -1184,16 +1251,26 @@ abstract class _$$ProfitLossAnalyticDtoImplCopyWith<$Res> @useResult $Res call({ @JsonKey(name: 'organization_id') String? organizationId, + @JsonKey(name: 'outlet_id') String? outletId, + @JsonKey(name: 'outlet_name') String? outletName, @JsonKey(name: 'date_from') String? dateFrom, @JsonKey(name: 'date_to') String? dateTo, @JsonKey(name: 'group_by') String? groupBy, @JsonKey(name: 'summary') ProfitLossSummaryDto? summary, @JsonKey(name: 'data') List? data, @JsonKey(name: 'product_data') List? productData, + @JsonKey(name: 'main_summary') + List? mainSummary, + @JsonKey(name: 'purchasing') ProfitLossPurchasingDto? purchasing, + @JsonKey(name: 'operational_expenses') + List? operationalExpenses, + @JsonKey(name: 'operational_expenses_total') int? operationalExpensesTotal, }); @override $ProfitLossSummaryDtoCopyWith<$Res>? get summary; + @override + $ProfitLossPurchasingDtoCopyWith<$Res>? get purchasing; } /// @nodoc @@ -1212,12 +1289,18 @@ class __$$ProfitLossAnalyticDtoImplCopyWithImpl<$Res> @override $Res call({ Object? organizationId = freezed, + Object? outletId = freezed, + Object? outletName = freezed, Object? dateFrom = freezed, Object? dateTo = freezed, Object? groupBy = freezed, Object? summary = freezed, Object? data = freezed, Object? productData = freezed, + Object? mainSummary = freezed, + Object? purchasing = freezed, + Object? operationalExpenses = freezed, + Object? operationalExpensesTotal = freezed, }) { return _then( _$ProfitLossAnalyticDtoImpl( @@ -1225,6 +1308,14 @@ class __$$ProfitLossAnalyticDtoImplCopyWithImpl<$Res> ? _value.organizationId : organizationId // ignore: cast_nullable_to_non_nullable as String?, + outletId: freezed == outletId + ? _value.outletId + : outletId // ignore: cast_nullable_to_non_nullable + as String?, + outletName: freezed == outletName + ? _value.outletName + : outletName // ignore: cast_nullable_to_non_nullable + as String?, dateFrom: freezed == dateFrom ? _value.dateFrom : dateFrom // ignore: cast_nullable_to_non_nullable @@ -1249,6 +1340,22 @@ class __$$ProfitLossAnalyticDtoImplCopyWithImpl<$Res> ? _value._productData : productData // ignore: cast_nullable_to_non_nullable as List?, + mainSummary: freezed == mainSummary + ? _value._mainSummary + : mainSummary // ignore: cast_nullable_to_non_nullable + as List?, + purchasing: freezed == purchasing + ? _value.purchasing + : purchasing // ignore: cast_nullable_to_non_nullable + as ProfitLossPurchasingDto?, + operationalExpenses: freezed == operationalExpenses + ? _value._operationalExpenses + : operationalExpenses // ignore: cast_nullable_to_non_nullable + as List?, + operationalExpensesTotal: freezed == operationalExpensesTotal + ? _value.operationalExpensesTotal + : operationalExpensesTotal // ignore: cast_nullable_to_non_nullable + as int?, ), ); } @@ -1259,6 +1366,8 @@ class __$$ProfitLossAnalyticDtoImplCopyWithImpl<$Res> class _$ProfitLossAnalyticDtoImpl extends _ProfitLossAnalyticDto { const _$ProfitLossAnalyticDtoImpl({ @JsonKey(name: 'organization_id') this.organizationId, + @JsonKey(name: 'outlet_id') this.outletId, + @JsonKey(name: 'outlet_name') this.outletName, @JsonKey(name: 'date_from') this.dateFrom, @JsonKey(name: 'date_to') this.dateTo, @JsonKey(name: 'group_by') this.groupBy, @@ -1266,8 +1375,16 @@ class _$ProfitLossAnalyticDtoImpl extends _ProfitLossAnalyticDto { @JsonKey(name: 'data') final List? data, @JsonKey(name: 'product_data') final List? productData, + @JsonKey(name: 'main_summary') + final List? mainSummary, + @JsonKey(name: 'purchasing') this.purchasing, + @JsonKey(name: 'operational_expenses') + final List? operationalExpenses, + @JsonKey(name: 'operational_expenses_total') this.operationalExpensesTotal, }) : _data = data, _productData = productData, + _mainSummary = mainSummary, + _operationalExpenses = operationalExpenses, super._(); factory _$ProfitLossAnalyticDtoImpl.fromJson(Map json) => @@ -1277,6 +1394,12 @@ class _$ProfitLossAnalyticDtoImpl extends _ProfitLossAnalyticDto { @JsonKey(name: 'organization_id') final String? organizationId; @override + @JsonKey(name: 'outlet_id') + final String? outletId; + @override + @JsonKey(name: 'outlet_name') + final String? outletName; + @override @JsonKey(name: 'date_from') final String? dateFrom; @override @@ -1310,9 +1433,39 @@ class _$ProfitLossAnalyticDtoImpl extends _ProfitLossAnalyticDto { return EqualUnmodifiableListView(value); } + final List? _mainSummary; + @override + @JsonKey(name: 'main_summary') + List? get mainSummary { + final value = _mainSummary; + if (value == null) return null; + if (_mainSummary is EqualUnmodifiableListView) return _mainSummary; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + @JsonKey(name: 'purchasing') + final ProfitLossPurchasingDto? purchasing; + final List? _operationalExpenses; + @override + @JsonKey(name: 'operational_expenses') + List? get operationalExpenses { + final value = _operationalExpenses; + if (value == null) return null; + if (_operationalExpenses is EqualUnmodifiableListView) + return _operationalExpenses; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + @JsonKey(name: 'operational_expenses_total') + final int? operationalExpensesTotal; + @override String toString() { - return 'ProfitLossAnalyticDto(organizationId: $organizationId, dateFrom: $dateFrom, dateTo: $dateTo, groupBy: $groupBy, summary: $summary, data: $data, productData: $productData)'; + return 'ProfitLossAnalyticDto(organizationId: $organizationId, outletId: $outletId, outletName: $outletName, dateFrom: $dateFrom, dateTo: $dateTo, groupBy: $groupBy, summary: $summary, data: $data, productData: $productData, mainSummary: $mainSummary, purchasing: $purchasing, operationalExpenses: $operationalExpenses, operationalExpensesTotal: $operationalExpensesTotal)'; } @override @@ -1322,6 +1475,10 @@ class _$ProfitLossAnalyticDtoImpl extends _ProfitLossAnalyticDto { other is _$ProfitLossAnalyticDtoImpl && (identical(other.organizationId, organizationId) || other.organizationId == organizationId) && + (identical(other.outletId, outletId) || + other.outletId == outletId) && + (identical(other.outletName, outletName) || + other.outletName == outletName) && (identical(other.dateFrom, dateFrom) || other.dateFrom == dateFrom) && (identical(other.dateTo, dateTo) || other.dateTo == dateTo) && @@ -1331,7 +1488,22 @@ class _$ProfitLossAnalyticDtoImpl extends _ProfitLossAnalyticDto { const DeepCollectionEquality().equals( other._productData, _productData, - )); + ) && + const DeepCollectionEquality().equals( + other._mainSummary, + _mainSummary, + ) && + (identical(other.purchasing, purchasing) || + other.purchasing == purchasing) && + const DeepCollectionEquality().equals( + other._operationalExpenses, + _operationalExpenses, + ) && + (identical( + other.operationalExpensesTotal, + operationalExpensesTotal, + ) || + other.operationalExpensesTotal == operationalExpensesTotal)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -1339,12 +1511,18 @@ class _$ProfitLossAnalyticDtoImpl extends _ProfitLossAnalyticDto { int get hashCode => Object.hash( runtimeType, organizationId, + outletId, + outletName, dateFrom, dateTo, groupBy, summary, const DeepCollectionEquality().hash(_data), const DeepCollectionEquality().hash(_productData), + const DeepCollectionEquality().hash(_mainSummary), + purchasing, + const DeepCollectionEquality().hash(_operationalExpenses), + operationalExpensesTotal, ); /// Create a copy of ProfitLossAnalyticDto @@ -1368,6 +1546,8 @@ class _$ProfitLossAnalyticDtoImpl extends _ProfitLossAnalyticDto { abstract class _ProfitLossAnalyticDto extends ProfitLossAnalyticDto { const factory _ProfitLossAnalyticDto({ @JsonKey(name: 'organization_id') final String? organizationId, + @JsonKey(name: 'outlet_id') final String? outletId, + @JsonKey(name: 'outlet_name') final String? outletName, @JsonKey(name: 'date_from') final String? dateFrom, @JsonKey(name: 'date_to') final String? dateTo, @JsonKey(name: 'group_by') final String? groupBy, @@ -1375,6 +1555,13 @@ abstract class _ProfitLossAnalyticDto extends ProfitLossAnalyticDto { @JsonKey(name: 'data') final List? data, @JsonKey(name: 'product_data') final List? productData, + @JsonKey(name: 'main_summary') + final List? mainSummary, + @JsonKey(name: 'purchasing') final ProfitLossPurchasingDto? purchasing, + @JsonKey(name: 'operational_expenses') + final List? operationalExpenses, + @JsonKey(name: 'operational_expenses_total') + final int? operationalExpensesTotal, }) = _$ProfitLossAnalyticDtoImpl; const _ProfitLossAnalyticDto._() : super._(); @@ -1385,6 +1572,12 @@ abstract class _ProfitLossAnalyticDto extends ProfitLossAnalyticDto { @JsonKey(name: 'organization_id') String? get organizationId; @override + @JsonKey(name: 'outlet_id') + String? get outletId; + @override + @JsonKey(name: 'outlet_name') + String? get outletName; + @override @JsonKey(name: 'date_from') String? get dateFrom; @override @@ -1402,6 +1595,18 @@ abstract class _ProfitLossAnalyticDto extends ProfitLossAnalyticDto { @override @JsonKey(name: 'product_data') List? get productData; + @override + @JsonKey(name: 'main_summary') + List? get mainSummary; + @override + @JsonKey(name: 'purchasing') + ProfitLossPurchasingDto? get purchasing; + @override + @JsonKey(name: 'operational_expenses') + List? get operationalExpenses; + @override + @JsonKey(name: 'operational_expenses_total') + int? get operationalExpensesTotal; /// Create a copy of ProfitLossAnalyticDto /// with the given fields replaced by the non-null parameter values. @@ -2696,6 +2901,1183 @@ abstract class _ProfitLossProductDataDto extends ProfitLossProductDataDto { get copyWith => throw _privateConstructorUsedError; } +ProfitLossMainSummaryItemDto _$ProfitLossMainSummaryItemDtoFromJson( + Map json, +) { + return _ProfitLossMainSummaryItemDto.fromJson(json); +} + +/// @nodoc +mixin _$ProfitLossMainSummaryItemDto { + @JsonKey(name: 'id') + String? get id => throw _privateConstructorUsedError; + @JsonKey(name: 'label') + String? get label => throw _privateConstructorUsedError; + @JsonKey(name: 'is_bold') + bool? get isBold => throw _privateConstructorUsedError; + @JsonKey(name: 'today_nominal') + int? get todayNominal => throw _privateConstructorUsedError; + @JsonKey(name: 'today_pct') + double? get todayPct => throw _privateConstructorUsedError; + @JsonKey(name: 'mtd_nominal') + int? get mtdNominal => throw _privateConstructorUsedError; + @JsonKey(name: 'mtd_pct') + double? get mtdPct => throw _privateConstructorUsedError; + @JsonKey(name: 'sub_items') + List? get subItems => + throw _privateConstructorUsedError; + + /// Serializes this ProfitLossMainSummaryItemDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ProfitLossMainSummaryItemDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProfitLossMainSummaryItemDtoCopyWith + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProfitLossMainSummaryItemDtoCopyWith<$Res> { + factory $ProfitLossMainSummaryItemDtoCopyWith( + ProfitLossMainSummaryItemDto value, + $Res Function(ProfitLossMainSummaryItemDto) then, + ) = + _$ProfitLossMainSummaryItemDtoCopyWithImpl< + $Res, + ProfitLossMainSummaryItemDto + >; + @useResult + $Res call({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'label') String? label, + @JsonKey(name: 'is_bold') bool? isBold, + @JsonKey(name: 'today_nominal') int? todayNominal, + @JsonKey(name: 'today_pct') double? todayPct, + @JsonKey(name: 'mtd_nominal') int? mtdNominal, + @JsonKey(name: 'mtd_pct') double? mtdPct, + @JsonKey(name: 'sub_items') List? subItems, + }); +} + +/// @nodoc +class _$ProfitLossMainSummaryItemDtoCopyWithImpl< + $Res, + $Val extends ProfitLossMainSummaryItemDto +> + implements $ProfitLossMainSummaryItemDtoCopyWith<$Res> { + _$ProfitLossMainSummaryItemDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProfitLossMainSummaryItemDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? label = freezed, + Object? isBold = freezed, + Object? todayNominal = freezed, + Object? todayPct = freezed, + Object? mtdNominal = freezed, + Object? mtdPct = freezed, + Object? subItems = freezed, + }) { + return _then( + _value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + label: freezed == label + ? _value.label + : label // ignore: cast_nullable_to_non_nullable + as String?, + isBold: freezed == isBold + ? _value.isBold + : isBold // ignore: cast_nullable_to_non_nullable + as bool?, + todayNominal: freezed == todayNominal + ? _value.todayNominal + : todayNominal // ignore: cast_nullable_to_non_nullable + as int?, + todayPct: freezed == todayPct + ? _value.todayPct + : todayPct // ignore: cast_nullable_to_non_nullable + as double?, + mtdNominal: freezed == mtdNominal + ? _value.mtdNominal + : mtdNominal // ignore: cast_nullable_to_non_nullable + as int?, + mtdPct: freezed == mtdPct + ? _value.mtdPct + : mtdPct // ignore: cast_nullable_to_non_nullable + as double?, + subItems: freezed == subItems + ? _value.subItems + : subItems // ignore: cast_nullable_to_non_nullable + as List?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProfitLossMainSummaryItemDtoImplCopyWith<$Res> + implements $ProfitLossMainSummaryItemDtoCopyWith<$Res> { + factory _$$ProfitLossMainSummaryItemDtoImplCopyWith( + _$ProfitLossMainSummaryItemDtoImpl value, + $Res Function(_$ProfitLossMainSummaryItemDtoImpl) then, + ) = __$$ProfitLossMainSummaryItemDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'label') String? label, + @JsonKey(name: 'is_bold') bool? isBold, + @JsonKey(name: 'today_nominal') int? todayNominal, + @JsonKey(name: 'today_pct') double? todayPct, + @JsonKey(name: 'mtd_nominal') int? mtdNominal, + @JsonKey(name: 'mtd_pct') double? mtdPct, + @JsonKey(name: 'sub_items') List? subItems, + }); +} + +/// @nodoc +class __$$ProfitLossMainSummaryItemDtoImplCopyWithImpl<$Res> + extends + _$ProfitLossMainSummaryItemDtoCopyWithImpl< + $Res, + _$ProfitLossMainSummaryItemDtoImpl + > + implements _$$ProfitLossMainSummaryItemDtoImplCopyWith<$Res> { + __$$ProfitLossMainSummaryItemDtoImplCopyWithImpl( + _$ProfitLossMainSummaryItemDtoImpl _value, + $Res Function(_$ProfitLossMainSummaryItemDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProfitLossMainSummaryItemDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? label = freezed, + Object? isBold = freezed, + Object? todayNominal = freezed, + Object? todayPct = freezed, + Object? mtdNominal = freezed, + Object? mtdPct = freezed, + Object? subItems = freezed, + }) { + return _then( + _$ProfitLossMainSummaryItemDtoImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + label: freezed == label + ? _value.label + : label // ignore: cast_nullable_to_non_nullable + as String?, + isBold: freezed == isBold + ? _value.isBold + : isBold // ignore: cast_nullable_to_non_nullable + as bool?, + todayNominal: freezed == todayNominal + ? _value.todayNominal + : todayNominal // ignore: cast_nullable_to_non_nullable + as int?, + todayPct: freezed == todayPct + ? _value.todayPct + : todayPct // ignore: cast_nullable_to_non_nullable + as double?, + mtdNominal: freezed == mtdNominal + ? _value.mtdNominal + : mtdNominal // ignore: cast_nullable_to_non_nullable + as int?, + mtdPct: freezed == mtdPct + ? _value.mtdPct + : mtdPct // ignore: cast_nullable_to_non_nullable + as double?, + subItems: freezed == subItems + ? _value._subItems + : subItems // ignore: cast_nullable_to_non_nullable + as List?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProfitLossMainSummaryItemDtoImpl extends _ProfitLossMainSummaryItemDto { + const _$ProfitLossMainSummaryItemDtoImpl({ + @JsonKey(name: 'id') this.id, + @JsonKey(name: 'label') this.label, + @JsonKey(name: 'is_bold') this.isBold, + @JsonKey(name: 'today_nominal') this.todayNominal, + @JsonKey(name: 'today_pct') this.todayPct, + @JsonKey(name: 'mtd_nominal') this.mtdNominal, + @JsonKey(name: 'mtd_pct') this.mtdPct, + @JsonKey(name: 'sub_items') + final List? subItems, + }) : _subItems = subItems, + super._(); + + factory _$ProfitLossMainSummaryItemDtoImpl.fromJson( + Map json, + ) => _$$ProfitLossMainSummaryItemDtoImplFromJson(json); + + @override + @JsonKey(name: 'id') + final String? id; + @override + @JsonKey(name: 'label') + final String? label; + @override + @JsonKey(name: 'is_bold') + final bool? isBold; + @override + @JsonKey(name: 'today_nominal') + final int? todayNominal; + @override + @JsonKey(name: 'today_pct') + final double? todayPct; + @override + @JsonKey(name: 'mtd_nominal') + final int? mtdNominal; + @override + @JsonKey(name: 'mtd_pct') + final double? mtdPct; + final List? _subItems; + @override + @JsonKey(name: 'sub_items') + List? get subItems { + final value = _subItems; + if (value == null) return null; + if (_subItems is EqualUnmodifiableListView) return _subItems; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'ProfitLossMainSummaryItemDto(id: $id, label: $label, isBold: $isBold, todayNominal: $todayNominal, todayPct: $todayPct, mtdNominal: $mtdNominal, mtdPct: $mtdPct, subItems: $subItems)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProfitLossMainSummaryItemDtoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.label, label) || other.label == label) && + (identical(other.isBold, isBold) || other.isBold == isBold) && + (identical(other.todayNominal, todayNominal) || + other.todayNominal == todayNominal) && + (identical(other.todayPct, todayPct) || + other.todayPct == todayPct) && + (identical(other.mtdNominal, mtdNominal) || + other.mtdNominal == mtdNominal) && + (identical(other.mtdPct, mtdPct) || other.mtdPct == mtdPct) && + const DeepCollectionEquality().equals(other._subItems, _subItems)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + label, + isBold, + todayNominal, + todayPct, + mtdNominal, + mtdPct, + const DeepCollectionEquality().hash(_subItems), + ); + + /// Create a copy of ProfitLossMainSummaryItemDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProfitLossMainSummaryItemDtoImplCopyWith< + _$ProfitLossMainSummaryItemDtoImpl + > + get copyWith => + __$$ProfitLossMainSummaryItemDtoImplCopyWithImpl< + _$ProfitLossMainSummaryItemDtoImpl + >(this, _$identity); + + @override + Map toJson() { + return _$$ProfitLossMainSummaryItemDtoImplToJson(this); + } +} + +abstract class _ProfitLossMainSummaryItemDto + extends ProfitLossMainSummaryItemDto { + const factory _ProfitLossMainSummaryItemDto({ + @JsonKey(name: 'id') final String? id, + @JsonKey(name: 'label') final String? label, + @JsonKey(name: 'is_bold') final bool? isBold, + @JsonKey(name: 'today_nominal') final int? todayNominal, + @JsonKey(name: 'today_pct') final double? todayPct, + @JsonKey(name: 'mtd_nominal') final int? mtdNominal, + @JsonKey(name: 'mtd_pct') final double? mtdPct, + @JsonKey(name: 'sub_items') + final List? subItems, + }) = _$ProfitLossMainSummaryItemDtoImpl; + const _ProfitLossMainSummaryItemDto._() : super._(); + + factory _ProfitLossMainSummaryItemDto.fromJson(Map json) = + _$ProfitLossMainSummaryItemDtoImpl.fromJson; + + @override + @JsonKey(name: 'id') + String? get id; + @override + @JsonKey(name: 'label') + String? get label; + @override + @JsonKey(name: 'is_bold') + bool? get isBold; + @override + @JsonKey(name: 'today_nominal') + int? get todayNominal; + @override + @JsonKey(name: 'today_pct') + double? get todayPct; + @override + @JsonKey(name: 'mtd_nominal') + int? get mtdNominal; + @override + @JsonKey(name: 'mtd_pct') + double? get mtdPct; + @override + @JsonKey(name: 'sub_items') + List? get subItems; + + /// Create a copy of ProfitLossMainSummaryItemDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProfitLossMainSummaryItemDtoImplCopyWith< + _$ProfitLossMainSummaryItemDtoImpl + > + get copyWith => throw _privateConstructorUsedError; +} + +ProfitLossPurchasingDto _$ProfitLossPurchasingDtoFromJson( + Map json, +) { + return _ProfitLossPurchasingDto.fromJson(json); +} + +/// @nodoc +mixin _$ProfitLossPurchasingDto { + @JsonKey(name: 'today_total') + int? get todayTotal => throw _privateConstructorUsedError; + @JsonKey(name: 'mtd_total') + int? get mtdTotal => throw _privateConstructorUsedError; + @JsonKey(name: 'today_raw_material') + int? get todayRawMaterial => throw _privateConstructorUsedError; + @JsonKey(name: 'mtd_raw_material') + int? get mtdRawMaterial => throw _privateConstructorUsedError; + @JsonKey(name: 'today_expense') + int? get todayExpense => throw _privateConstructorUsedError; + @JsonKey(name: 'mtd_expense') + int? get mtdExpense => throw _privateConstructorUsedError; + @JsonKey(name: 'items') + List? get items => + throw _privateConstructorUsedError; + + /// Serializes this ProfitLossPurchasingDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ProfitLossPurchasingDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProfitLossPurchasingDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProfitLossPurchasingDtoCopyWith<$Res> { + factory $ProfitLossPurchasingDtoCopyWith( + ProfitLossPurchasingDto value, + $Res Function(ProfitLossPurchasingDto) then, + ) = _$ProfitLossPurchasingDtoCopyWithImpl<$Res, ProfitLossPurchasingDto>; + @useResult + $Res call({ + @JsonKey(name: 'today_total') int? todayTotal, + @JsonKey(name: 'mtd_total') int? mtdTotal, + @JsonKey(name: 'today_raw_material') int? todayRawMaterial, + @JsonKey(name: 'mtd_raw_material') int? mtdRawMaterial, + @JsonKey(name: 'today_expense') int? todayExpense, + @JsonKey(name: 'mtd_expense') int? mtdExpense, + @JsonKey(name: 'items') List? items, + }); +} + +/// @nodoc +class _$ProfitLossPurchasingDtoCopyWithImpl< + $Res, + $Val extends ProfitLossPurchasingDto +> + implements $ProfitLossPurchasingDtoCopyWith<$Res> { + _$ProfitLossPurchasingDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProfitLossPurchasingDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? todayTotal = freezed, + Object? mtdTotal = freezed, + Object? todayRawMaterial = freezed, + Object? mtdRawMaterial = freezed, + Object? todayExpense = freezed, + Object? mtdExpense = freezed, + Object? items = freezed, + }) { + return _then( + _value.copyWith( + todayTotal: freezed == todayTotal + ? _value.todayTotal + : todayTotal // ignore: cast_nullable_to_non_nullable + as int?, + mtdTotal: freezed == mtdTotal + ? _value.mtdTotal + : mtdTotal // ignore: cast_nullable_to_non_nullable + as int?, + todayRawMaterial: freezed == todayRawMaterial + ? _value.todayRawMaterial + : todayRawMaterial // ignore: cast_nullable_to_non_nullable + as int?, + mtdRawMaterial: freezed == mtdRawMaterial + ? _value.mtdRawMaterial + : mtdRawMaterial // ignore: cast_nullable_to_non_nullable + as int?, + todayExpense: freezed == todayExpense + ? _value.todayExpense + : todayExpense // ignore: cast_nullable_to_non_nullable + as int?, + mtdExpense: freezed == mtdExpense + ? _value.mtdExpense + : mtdExpense // ignore: cast_nullable_to_non_nullable + as int?, + items: freezed == items + ? _value.items + : items // ignore: cast_nullable_to_non_nullable + as List?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProfitLossPurchasingDtoImplCopyWith<$Res> + implements $ProfitLossPurchasingDtoCopyWith<$Res> { + factory _$$ProfitLossPurchasingDtoImplCopyWith( + _$ProfitLossPurchasingDtoImpl value, + $Res Function(_$ProfitLossPurchasingDtoImpl) then, + ) = __$$ProfitLossPurchasingDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'today_total') int? todayTotal, + @JsonKey(name: 'mtd_total') int? mtdTotal, + @JsonKey(name: 'today_raw_material') int? todayRawMaterial, + @JsonKey(name: 'mtd_raw_material') int? mtdRawMaterial, + @JsonKey(name: 'today_expense') int? todayExpense, + @JsonKey(name: 'mtd_expense') int? mtdExpense, + @JsonKey(name: 'items') List? items, + }); +} + +/// @nodoc +class __$$ProfitLossPurchasingDtoImplCopyWithImpl<$Res> + extends + _$ProfitLossPurchasingDtoCopyWithImpl< + $Res, + _$ProfitLossPurchasingDtoImpl + > + implements _$$ProfitLossPurchasingDtoImplCopyWith<$Res> { + __$$ProfitLossPurchasingDtoImplCopyWithImpl( + _$ProfitLossPurchasingDtoImpl _value, + $Res Function(_$ProfitLossPurchasingDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProfitLossPurchasingDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? todayTotal = freezed, + Object? mtdTotal = freezed, + Object? todayRawMaterial = freezed, + Object? mtdRawMaterial = freezed, + Object? todayExpense = freezed, + Object? mtdExpense = freezed, + Object? items = freezed, + }) { + return _then( + _$ProfitLossPurchasingDtoImpl( + todayTotal: freezed == todayTotal + ? _value.todayTotal + : todayTotal // ignore: cast_nullable_to_non_nullable + as int?, + mtdTotal: freezed == mtdTotal + ? _value.mtdTotal + : mtdTotal // ignore: cast_nullable_to_non_nullable + as int?, + todayRawMaterial: freezed == todayRawMaterial + ? _value.todayRawMaterial + : todayRawMaterial // ignore: cast_nullable_to_non_nullable + as int?, + mtdRawMaterial: freezed == mtdRawMaterial + ? _value.mtdRawMaterial + : mtdRawMaterial // ignore: cast_nullable_to_non_nullable + as int?, + todayExpense: freezed == todayExpense + ? _value.todayExpense + : todayExpense // ignore: cast_nullable_to_non_nullable + as int?, + mtdExpense: freezed == mtdExpense + ? _value.mtdExpense + : mtdExpense // ignore: cast_nullable_to_non_nullable + as int?, + items: freezed == items + ? _value._items + : items // ignore: cast_nullable_to_non_nullable + as List?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProfitLossPurchasingDtoImpl extends _ProfitLossPurchasingDto { + const _$ProfitLossPurchasingDtoImpl({ + @JsonKey(name: 'today_total') this.todayTotal, + @JsonKey(name: 'mtd_total') this.mtdTotal, + @JsonKey(name: 'today_raw_material') this.todayRawMaterial, + @JsonKey(name: 'mtd_raw_material') this.mtdRawMaterial, + @JsonKey(name: 'today_expense') this.todayExpense, + @JsonKey(name: 'mtd_expense') this.mtdExpense, + @JsonKey(name: 'items') final List? items, + }) : _items = items, + super._(); + + factory _$ProfitLossPurchasingDtoImpl.fromJson(Map json) => + _$$ProfitLossPurchasingDtoImplFromJson(json); + + @override + @JsonKey(name: 'today_total') + final int? todayTotal; + @override + @JsonKey(name: 'mtd_total') + final int? mtdTotal; + @override + @JsonKey(name: 'today_raw_material') + final int? todayRawMaterial; + @override + @JsonKey(name: 'mtd_raw_material') + final int? mtdRawMaterial; + @override + @JsonKey(name: 'today_expense') + final int? todayExpense; + @override + @JsonKey(name: 'mtd_expense') + final int? mtdExpense; + final List? _items; + @override + @JsonKey(name: 'items') + List? get items { + final value = _items; + if (value == null) return null; + if (_items is EqualUnmodifiableListView) return _items; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'ProfitLossPurchasingDto(todayTotal: $todayTotal, mtdTotal: $mtdTotal, todayRawMaterial: $todayRawMaterial, mtdRawMaterial: $mtdRawMaterial, todayExpense: $todayExpense, mtdExpense: $mtdExpense, items: $items)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProfitLossPurchasingDtoImpl && + (identical(other.todayTotal, todayTotal) || + other.todayTotal == todayTotal) && + (identical(other.mtdTotal, mtdTotal) || + other.mtdTotal == mtdTotal) && + (identical(other.todayRawMaterial, todayRawMaterial) || + other.todayRawMaterial == todayRawMaterial) && + (identical(other.mtdRawMaterial, mtdRawMaterial) || + other.mtdRawMaterial == mtdRawMaterial) && + (identical(other.todayExpense, todayExpense) || + other.todayExpense == todayExpense) && + (identical(other.mtdExpense, mtdExpense) || + other.mtdExpense == mtdExpense) && + const DeepCollectionEquality().equals(other._items, _items)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + todayTotal, + mtdTotal, + todayRawMaterial, + mtdRawMaterial, + todayExpense, + mtdExpense, + const DeepCollectionEquality().hash(_items), + ); + + /// Create a copy of ProfitLossPurchasingDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProfitLossPurchasingDtoImplCopyWith<_$ProfitLossPurchasingDtoImpl> + get copyWith => + __$$ProfitLossPurchasingDtoImplCopyWithImpl< + _$ProfitLossPurchasingDtoImpl + >(this, _$identity); + + @override + Map toJson() { + return _$$ProfitLossPurchasingDtoImplToJson(this); + } +} + +abstract class _ProfitLossPurchasingDto extends ProfitLossPurchasingDto { + const factory _ProfitLossPurchasingDto({ + @JsonKey(name: 'today_total') final int? todayTotal, + @JsonKey(name: 'mtd_total') final int? mtdTotal, + @JsonKey(name: 'today_raw_material') final int? todayRawMaterial, + @JsonKey(name: 'mtd_raw_material') final int? mtdRawMaterial, + @JsonKey(name: 'today_expense') final int? todayExpense, + @JsonKey(name: 'mtd_expense') final int? mtdExpense, + @JsonKey(name: 'items') final List? items, + }) = _$ProfitLossPurchasingDtoImpl; + const _ProfitLossPurchasingDto._() : super._(); + + factory _ProfitLossPurchasingDto.fromJson(Map json) = + _$ProfitLossPurchasingDtoImpl.fromJson; + + @override + @JsonKey(name: 'today_total') + int? get todayTotal; + @override + @JsonKey(name: 'mtd_total') + int? get mtdTotal; + @override + @JsonKey(name: 'today_raw_material') + int? get todayRawMaterial; + @override + @JsonKey(name: 'mtd_raw_material') + int? get mtdRawMaterial; + @override + @JsonKey(name: 'today_expense') + int? get todayExpense; + @override + @JsonKey(name: 'mtd_expense') + int? get mtdExpense; + @override + @JsonKey(name: 'items') + List? get items; + + /// Create a copy of ProfitLossPurchasingDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProfitLossPurchasingDtoImplCopyWith<_$ProfitLossPurchasingDtoImpl> + get copyWith => throw _privateConstructorUsedError; +} + +ProfitLossPurchasingItemDto _$ProfitLossPurchasingItemDtoFromJson( + Map json, +) { + return _ProfitLossPurchasingItemDto.fromJson(json); +} + +/// @nodoc +mixin _$ProfitLossPurchasingItemDto { + @JsonKey(name: 'date') + String? get date => throw _privateConstructorUsedError; + @JsonKey(name: 'item') + String? get item => throw _privateConstructorUsedError; + @JsonKey(name: 'quantity') + int? get quantity => throw _privateConstructorUsedError; + @JsonKey(name: 'nominal') + int? get nominal => throw _privateConstructorUsedError; + + /// Serializes this ProfitLossPurchasingItemDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ProfitLossPurchasingItemDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProfitLossPurchasingItemDtoCopyWith + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProfitLossPurchasingItemDtoCopyWith<$Res> { + factory $ProfitLossPurchasingItemDtoCopyWith( + ProfitLossPurchasingItemDto value, + $Res Function(ProfitLossPurchasingItemDto) then, + ) = + _$ProfitLossPurchasingItemDtoCopyWithImpl< + $Res, + ProfitLossPurchasingItemDto + >; + @useResult + $Res call({ + @JsonKey(name: 'date') String? date, + @JsonKey(name: 'item') String? item, + @JsonKey(name: 'quantity') int? quantity, + @JsonKey(name: 'nominal') int? nominal, + }); +} + +/// @nodoc +class _$ProfitLossPurchasingItemDtoCopyWithImpl< + $Res, + $Val extends ProfitLossPurchasingItemDto +> + implements $ProfitLossPurchasingItemDtoCopyWith<$Res> { + _$ProfitLossPurchasingItemDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProfitLossPurchasingItemDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? date = freezed, + Object? item = freezed, + Object? quantity = freezed, + Object? nominal = freezed, + }) { + return _then( + _value.copyWith( + date: freezed == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as String?, + item: freezed == item + ? _value.item + : item // ignore: cast_nullable_to_non_nullable + as String?, + quantity: freezed == quantity + ? _value.quantity + : quantity // ignore: cast_nullable_to_non_nullable + as int?, + nominal: freezed == nominal + ? _value.nominal + : nominal // ignore: cast_nullable_to_non_nullable + as int?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProfitLossPurchasingItemDtoImplCopyWith<$Res> + implements $ProfitLossPurchasingItemDtoCopyWith<$Res> { + factory _$$ProfitLossPurchasingItemDtoImplCopyWith( + _$ProfitLossPurchasingItemDtoImpl value, + $Res Function(_$ProfitLossPurchasingItemDtoImpl) then, + ) = __$$ProfitLossPurchasingItemDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'date') String? date, + @JsonKey(name: 'item') String? item, + @JsonKey(name: 'quantity') int? quantity, + @JsonKey(name: 'nominal') int? nominal, + }); +} + +/// @nodoc +class __$$ProfitLossPurchasingItemDtoImplCopyWithImpl<$Res> + extends + _$ProfitLossPurchasingItemDtoCopyWithImpl< + $Res, + _$ProfitLossPurchasingItemDtoImpl + > + implements _$$ProfitLossPurchasingItemDtoImplCopyWith<$Res> { + __$$ProfitLossPurchasingItemDtoImplCopyWithImpl( + _$ProfitLossPurchasingItemDtoImpl _value, + $Res Function(_$ProfitLossPurchasingItemDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProfitLossPurchasingItemDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? date = freezed, + Object? item = freezed, + Object? quantity = freezed, + Object? nominal = freezed, + }) { + return _then( + _$ProfitLossPurchasingItemDtoImpl( + date: freezed == date + ? _value.date + : date // ignore: cast_nullable_to_non_nullable + as String?, + item: freezed == item + ? _value.item + : item // ignore: cast_nullable_to_non_nullable + as String?, + quantity: freezed == quantity + ? _value.quantity + : quantity // ignore: cast_nullable_to_non_nullable + as int?, + nominal: freezed == nominal + ? _value.nominal + : nominal // ignore: cast_nullable_to_non_nullable + as int?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProfitLossPurchasingItemDtoImpl extends _ProfitLossPurchasingItemDto { + const _$ProfitLossPurchasingItemDtoImpl({ + @JsonKey(name: 'date') this.date, + @JsonKey(name: 'item') this.item, + @JsonKey(name: 'quantity') this.quantity, + @JsonKey(name: 'nominal') this.nominal, + }) : super._(); + + factory _$ProfitLossPurchasingItemDtoImpl.fromJson( + Map json, + ) => _$$ProfitLossPurchasingItemDtoImplFromJson(json); + + @override + @JsonKey(name: 'date') + final String? date; + @override + @JsonKey(name: 'item') + final String? item; + @override + @JsonKey(name: 'quantity') + final int? quantity; + @override + @JsonKey(name: 'nominal') + final int? nominal; + + @override + String toString() { + return 'ProfitLossPurchasingItemDto(date: $date, item: $item, quantity: $quantity, nominal: $nominal)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProfitLossPurchasingItemDtoImpl && + (identical(other.date, date) || other.date == date) && + (identical(other.item, item) || other.item == item) && + (identical(other.quantity, quantity) || + other.quantity == quantity) && + (identical(other.nominal, nominal) || other.nominal == nominal)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, date, item, quantity, nominal); + + /// Create a copy of ProfitLossPurchasingItemDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProfitLossPurchasingItemDtoImplCopyWith<_$ProfitLossPurchasingItemDtoImpl> + get copyWith => + __$$ProfitLossPurchasingItemDtoImplCopyWithImpl< + _$ProfitLossPurchasingItemDtoImpl + >(this, _$identity); + + @override + Map toJson() { + return _$$ProfitLossPurchasingItemDtoImplToJson(this); + } +} + +abstract class _ProfitLossPurchasingItemDto + extends ProfitLossPurchasingItemDto { + const factory _ProfitLossPurchasingItemDto({ + @JsonKey(name: 'date') final String? date, + @JsonKey(name: 'item') final String? item, + @JsonKey(name: 'quantity') final int? quantity, + @JsonKey(name: 'nominal') final int? nominal, + }) = _$ProfitLossPurchasingItemDtoImpl; + const _ProfitLossPurchasingItemDto._() : super._(); + + factory _ProfitLossPurchasingItemDto.fromJson(Map json) = + _$ProfitLossPurchasingItemDtoImpl.fromJson; + + @override + @JsonKey(name: 'date') + String? get date; + @override + @JsonKey(name: 'item') + String? get item; + @override + @JsonKey(name: 'quantity') + int? get quantity; + @override + @JsonKey(name: 'nominal') + int? get nominal; + + /// Create a copy of ProfitLossPurchasingItemDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProfitLossPurchasingItemDtoImplCopyWith<_$ProfitLossPurchasingItemDtoImpl> + get copyWith => throw _privateConstructorUsedError; +} + +ProfitLossOperationalExpenseDto _$ProfitLossOperationalExpenseDtoFromJson( + Map json, +) { + return _ProfitLossOperationalExpenseDto.fromJson(json); +} + +/// @nodoc +mixin _$ProfitLossOperationalExpenseDto { + @JsonKey(name: 'item') + String? get item => throw _privateConstructorUsedError; + @JsonKey(name: 'nominal') + int? get nominal => throw _privateConstructorUsedError; + + /// Serializes this ProfitLossOperationalExpenseDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ProfitLossOperationalExpenseDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProfitLossOperationalExpenseDtoCopyWith + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProfitLossOperationalExpenseDtoCopyWith<$Res> { + factory $ProfitLossOperationalExpenseDtoCopyWith( + ProfitLossOperationalExpenseDto value, + $Res Function(ProfitLossOperationalExpenseDto) then, + ) = + _$ProfitLossOperationalExpenseDtoCopyWithImpl< + $Res, + ProfitLossOperationalExpenseDto + >; + @useResult + $Res call({ + @JsonKey(name: 'item') String? item, + @JsonKey(name: 'nominal') int? nominal, + }); +} + +/// @nodoc +class _$ProfitLossOperationalExpenseDtoCopyWithImpl< + $Res, + $Val extends ProfitLossOperationalExpenseDto +> + implements $ProfitLossOperationalExpenseDtoCopyWith<$Res> { + _$ProfitLossOperationalExpenseDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProfitLossOperationalExpenseDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? item = freezed, Object? nominal = freezed}) { + return _then( + _value.copyWith( + item: freezed == item + ? _value.item + : item // ignore: cast_nullable_to_non_nullable + as String?, + nominal: freezed == nominal + ? _value.nominal + : nominal // ignore: cast_nullable_to_non_nullable + as int?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProfitLossOperationalExpenseDtoImplCopyWith<$Res> + implements $ProfitLossOperationalExpenseDtoCopyWith<$Res> { + factory _$$ProfitLossOperationalExpenseDtoImplCopyWith( + _$ProfitLossOperationalExpenseDtoImpl value, + $Res Function(_$ProfitLossOperationalExpenseDtoImpl) then, + ) = __$$ProfitLossOperationalExpenseDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'item') String? item, + @JsonKey(name: 'nominal') int? nominal, + }); +} + +/// @nodoc +class __$$ProfitLossOperationalExpenseDtoImplCopyWithImpl<$Res> + extends + _$ProfitLossOperationalExpenseDtoCopyWithImpl< + $Res, + _$ProfitLossOperationalExpenseDtoImpl + > + implements _$$ProfitLossOperationalExpenseDtoImplCopyWith<$Res> { + __$$ProfitLossOperationalExpenseDtoImplCopyWithImpl( + _$ProfitLossOperationalExpenseDtoImpl _value, + $Res Function(_$ProfitLossOperationalExpenseDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProfitLossOperationalExpenseDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? item = freezed, Object? nominal = freezed}) { + return _then( + _$ProfitLossOperationalExpenseDtoImpl( + item: freezed == item + ? _value.item + : item // ignore: cast_nullable_to_non_nullable + as String?, + nominal: freezed == nominal + ? _value.nominal + : nominal // ignore: cast_nullable_to_non_nullable + as int?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProfitLossOperationalExpenseDtoImpl + extends _ProfitLossOperationalExpenseDto { + const _$ProfitLossOperationalExpenseDtoImpl({ + @JsonKey(name: 'item') this.item, + @JsonKey(name: 'nominal') this.nominal, + }) : super._(); + + factory _$ProfitLossOperationalExpenseDtoImpl.fromJson( + Map json, + ) => _$$ProfitLossOperationalExpenseDtoImplFromJson(json); + + @override + @JsonKey(name: 'item') + final String? item; + @override + @JsonKey(name: 'nominal') + final int? nominal; + + @override + String toString() { + return 'ProfitLossOperationalExpenseDto(item: $item, nominal: $nominal)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProfitLossOperationalExpenseDtoImpl && + (identical(other.item, item) || other.item == item) && + (identical(other.nominal, nominal) || other.nominal == nominal)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, item, nominal); + + /// Create a copy of ProfitLossOperationalExpenseDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProfitLossOperationalExpenseDtoImplCopyWith< + _$ProfitLossOperationalExpenseDtoImpl + > + get copyWith => + __$$ProfitLossOperationalExpenseDtoImplCopyWithImpl< + _$ProfitLossOperationalExpenseDtoImpl + >(this, _$identity); + + @override + Map toJson() { + return _$$ProfitLossOperationalExpenseDtoImplToJson(this); + } +} + +abstract class _ProfitLossOperationalExpenseDto + extends ProfitLossOperationalExpenseDto { + const factory _ProfitLossOperationalExpenseDto({ + @JsonKey(name: 'item') final String? item, + @JsonKey(name: 'nominal') final int? nominal, + }) = _$ProfitLossOperationalExpenseDtoImpl; + const _ProfitLossOperationalExpenseDto._() : super._(); + + factory _ProfitLossOperationalExpenseDto.fromJson(Map json) = + _$ProfitLossOperationalExpenseDtoImpl.fromJson; + + @override + @JsonKey(name: 'item') + String? get item; + @override + @JsonKey(name: 'nominal') + int? get nominal; + + /// Create a copy of ProfitLossOperationalExpenseDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProfitLossOperationalExpenseDtoImplCopyWith< + _$ProfitLossOperationalExpenseDtoImpl + > + get copyWith => throw _privateConstructorUsedError; +} + CategoryAnalyticDto _$CategoryAnalyticDtoFromJson(Map json) { return _CategoryAnalyticDto.fromJson(json); } diff --git a/lib/infrastructure/analytic/analytic_dtos.g.dart b/lib/infrastructure/analytic/analytic_dtos.g.dart index 8dad8b2..8e63a26 100644 --- a/lib/infrastructure/analytic/analytic_dtos.g.dart +++ b/lib/infrastructure/analytic/analytic_dtos.g.dart @@ -94,6 +94,8 @@ _$ProfitLossAnalyticDtoImpl _$$ProfitLossAnalyticDtoImplFromJson( Map json, ) => _$ProfitLossAnalyticDtoImpl( organizationId: json['organization_id'] as String?, + outletId: json['outlet_id'] as String?, + outletName: json['outlet_name'] as String?, dateFrom: json['date_from'] as String?, dateTo: json['date_to'] as String?, groupBy: json['group_by'] as String?, @@ -106,18 +108,42 @@ _$ProfitLossAnalyticDtoImpl _$$ProfitLossAnalyticDtoImplFromJson( productData: (json['product_data'] as List?) ?.map((e) => ProfitLossProductDataDto.fromJson(e as Map)) .toList(), + mainSummary: (json['main_summary'] as List?) + ?.map( + (e) => ProfitLossMainSummaryItemDto.fromJson(e as Map), + ) + .toList(), + purchasing: json['purchasing'] == null + ? null + : ProfitLossPurchasingDto.fromJson( + json['purchasing'] as Map, + ), + operationalExpenses: (json['operational_expenses'] as List?) + ?.map( + (e) => + ProfitLossOperationalExpenseDto.fromJson(e as Map), + ) + .toList(), + operationalExpensesTotal: (json['operational_expenses_total'] as num?) + ?.toInt(), ); Map _$$ProfitLossAnalyticDtoImplToJson( _$ProfitLossAnalyticDtoImpl instance, ) => { 'organization_id': instance.organizationId, + 'outlet_id': instance.outletId, + 'outlet_name': instance.outletName, 'date_from': instance.dateFrom, 'date_to': instance.dateTo, 'group_by': instance.groupBy, 'summary': instance.summary, 'data': instance.data, 'product_data': instance.productData, + 'main_summary': instance.mainSummary, + 'purchasing': instance.purchasing, + 'operational_expenses': instance.operationalExpenses, + 'operational_expenses_total': instance.operationalExpensesTotal, }; _$ProfitLossSummaryDtoImpl _$$ProfitLossSummaryDtoImplFromJson( @@ -216,6 +242,93 @@ Map _$$ProfitLossProductDataDtoImplToJson( 'profit_per_unit': instance.profitPerUnit, }; +_$ProfitLossMainSummaryItemDtoImpl _$$ProfitLossMainSummaryItemDtoImplFromJson( + Map json, +) => _$ProfitLossMainSummaryItemDtoImpl( + id: json['id'] as String?, + label: json['label'] as String?, + isBold: json['is_bold'] as bool?, + todayNominal: (json['today_nominal'] as num?)?.toInt(), + todayPct: (json['today_pct'] as num?)?.toDouble(), + mtdNominal: (json['mtd_nominal'] as num?)?.toInt(), + mtdPct: (json['mtd_pct'] as num?)?.toDouble(), + subItems: (json['sub_items'] as List?) + ?.map( + (e) => ProfitLossMainSummaryItemDto.fromJson(e as Map), + ) + .toList(), +); + +Map _$$ProfitLossMainSummaryItemDtoImplToJson( + _$ProfitLossMainSummaryItemDtoImpl instance, +) => { + 'id': instance.id, + 'label': instance.label, + 'is_bold': instance.isBold, + 'today_nominal': instance.todayNominal, + 'today_pct': instance.todayPct, + 'mtd_nominal': instance.mtdNominal, + 'mtd_pct': instance.mtdPct, + 'sub_items': instance.subItems, +}; + +_$ProfitLossPurchasingDtoImpl _$$ProfitLossPurchasingDtoImplFromJson( + Map json, +) => _$ProfitLossPurchasingDtoImpl( + todayTotal: (json['today_total'] as num?)?.toInt(), + mtdTotal: (json['mtd_total'] as num?)?.toInt(), + todayRawMaterial: (json['today_raw_material'] as num?)?.toInt(), + mtdRawMaterial: (json['mtd_raw_material'] as num?)?.toInt(), + todayExpense: (json['today_expense'] as num?)?.toInt(), + mtdExpense: (json['mtd_expense'] as num?)?.toInt(), + items: (json['items'] as List?) + ?.map( + (e) => ProfitLossPurchasingItemDto.fromJson(e as Map), + ) + .toList(), +); + +Map _$$ProfitLossPurchasingDtoImplToJson( + _$ProfitLossPurchasingDtoImpl instance, +) => { + 'today_total': instance.todayTotal, + 'mtd_total': instance.mtdTotal, + 'today_raw_material': instance.todayRawMaterial, + 'mtd_raw_material': instance.mtdRawMaterial, + 'today_expense': instance.todayExpense, + 'mtd_expense': instance.mtdExpense, + 'items': instance.items, +}; + +_$ProfitLossPurchasingItemDtoImpl _$$ProfitLossPurchasingItemDtoImplFromJson( + Map json, +) => _$ProfitLossPurchasingItemDtoImpl( + date: json['date'] as String?, + item: json['item'] as String?, + quantity: (json['quantity'] as num?)?.toInt(), + nominal: (json['nominal'] as num?)?.toInt(), +); + +Map _$$ProfitLossPurchasingItemDtoImplToJson( + _$ProfitLossPurchasingItemDtoImpl instance, +) => { + 'date': instance.date, + 'item': instance.item, + 'quantity': instance.quantity, + 'nominal': instance.nominal, +}; + +_$ProfitLossOperationalExpenseDtoImpl +_$$ProfitLossOperationalExpenseDtoImplFromJson(Map json) => + _$ProfitLossOperationalExpenseDtoImpl( + item: json['item'] as String?, + nominal: (json['nominal'] as num?)?.toInt(), + ); + +Map _$$ProfitLossOperationalExpenseDtoImplToJson( + _$ProfitLossOperationalExpenseDtoImpl instance, +) => {'item': instance.item, 'nominal': instance.nominal}; + _$CategoryAnalyticDtoImpl _$$CategoryAnalyticDtoImplFromJson( Map json, ) => _$CategoryAnalyticDtoImpl( diff --git a/lib/infrastructure/analytic/dto/profit_loss_analytic_dto.dart b/lib/infrastructure/analytic/dto/profit_loss_analytic_dto.dart index 2741495..62f4860 100644 --- a/lib/infrastructure/analytic/dto/profit_loss_analytic_dto.dart +++ b/lib/infrastructure/analytic/dto/profit_loss_analytic_dto.dart @@ -6,12 +6,20 @@ class ProfitLossAnalyticDto with _$ProfitLossAnalyticDto { const factory ProfitLossAnalyticDto({ @JsonKey(name: 'organization_id') String? organizationId, + @JsonKey(name: 'outlet_id') String? outletId, + @JsonKey(name: 'outlet_name') String? outletName, @JsonKey(name: 'date_from') String? dateFrom, @JsonKey(name: 'date_to') String? dateTo, @JsonKey(name: 'group_by') String? groupBy, @JsonKey(name: 'summary') ProfitLossSummaryDto? summary, @JsonKey(name: 'data') List? data, @JsonKey(name: 'product_data') List? productData, + @JsonKey(name: 'main_summary') + List? mainSummary, + @JsonKey(name: 'purchasing') ProfitLossPurchasingDto? purchasing, + @JsonKey(name: 'operational_expenses') + List? operationalExpenses, + @JsonKey(name: 'operational_expenses_total') int? operationalExpensesTotal, }) = _ProfitLossAnalyticDto; factory ProfitLossAnalyticDto.fromJson(Map json) => @@ -19,12 +27,20 @@ class ProfitLossAnalyticDto with _$ProfitLossAnalyticDto { ProfitLossAnalytic toDomain() => ProfitLossAnalytic( organizationId: organizationId ?? '', + outletId: outletId ?? '', + outletName: outletName ?? '', dateFrom: dateFrom ?? '', dateTo: dateTo ?? '', groupBy: groupBy ?? '', summary: summary?.toDomain() ?? ProfitLossSummary.empty(), data: (data ?? []).map((e) => e.toDomain()).toList(), productData: (productData ?? []).map((e) => e.toDomain()).toList(), + mainSummary: (mainSummary ?? []).map((e) => e.toDomain()).toList(), + purchasing: purchasing?.toDomain() ?? ProfitLossPurchasing.empty(), + operationalExpenses: (operationalExpenses ?? []) + .map((e) => e.toDomain()) + .toList(), + operationalExpensesTotal: operationalExpensesTotal ?? 0, ); } @@ -135,3 +151,99 @@ class ProfitLossProductDataDto with _$ProfitLossProductDataDto { profitPerUnit: profitPerUnit ?? 0, ); } + +@freezed +class ProfitLossMainSummaryItemDto with _$ProfitLossMainSummaryItemDto { + const ProfitLossMainSummaryItemDto._(); + + const factory ProfitLossMainSummaryItemDto({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'label') String? label, + @JsonKey(name: 'is_bold') bool? isBold, + @JsonKey(name: 'today_nominal') int? todayNominal, + @JsonKey(name: 'today_pct') double? todayPct, + @JsonKey(name: 'mtd_nominal') int? mtdNominal, + @JsonKey(name: 'mtd_pct') double? mtdPct, + @JsonKey(name: 'sub_items') List? subItems, + }) = _ProfitLossMainSummaryItemDto; + + factory ProfitLossMainSummaryItemDto.fromJson(Map json) => + _$ProfitLossMainSummaryItemDtoFromJson(json); + + ProfitLossMainSummaryItem toDomain() => ProfitLossMainSummaryItem( + id: id ?? '', + label: label ?? '', + isBold: isBold ?? false, + todayNominal: todayNominal ?? 0, + todayPct: todayPct ?? 0.0, + mtdNominal: mtdNominal ?? 0, + mtdPct: mtdPct ?? 0.0, + subItems: (subItems ?? []).map((e) => e.toDomain()).toList(), + ); +} + +@freezed +class ProfitLossPurchasingDto with _$ProfitLossPurchasingDto { + const ProfitLossPurchasingDto._(); + + const factory ProfitLossPurchasingDto({ + @JsonKey(name: 'today_total') int? todayTotal, + @JsonKey(name: 'mtd_total') int? mtdTotal, + @JsonKey(name: 'today_raw_material') int? todayRawMaterial, + @JsonKey(name: 'mtd_raw_material') int? mtdRawMaterial, + @JsonKey(name: 'today_expense') int? todayExpense, + @JsonKey(name: 'mtd_expense') int? mtdExpense, + @JsonKey(name: 'items') List? items, + }) = _ProfitLossPurchasingDto; + + factory ProfitLossPurchasingDto.fromJson(Map json) => + _$ProfitLossPurchasingDtoFromJson(json); + + ProfitLossPurchasing toDomain() => ProfitLossPurchasing( + todayTotal: todayTotal ?? 0, + mtdTotal: mtdTotal ?? 0, + todayRawMaterial: todayRawMaterial ?? 0, + mtdRawMaterial: mtdRawMaterial ?? 0, + todayExpense: todayExpense ?? 0, + mtdExpense: mtdExpense ?? 0, + items: (items ?? []).map((e) => e.toDomain()).toList(), + ); +} + +@freezed +class ProfitLossPurchasingItemDto with _$ProfitLossPurchasingItemDto { + const ProfitLossPurchasingItemDto._(); + + const factory ProfitLossPurchasingItemDto({ + @JsonKey(name: 'date') String? date, + @JsonKey(name: 'item') String? item, + @JsonKey(name: 'quantity') int? quantity, + @JsonKey(name: 'nominal') int? nominal, + }) = _ProfitLossPurchasingItemDto; + + factory ProfitLossPurchasingItemDto.fromJson(Map json) => + _$ProfitLossPurchasingItemDtoFromJson(json); + + ProfitLossPurchasingItem toDomain() => ProfitLossPurchasingItem( + date: date ?? '', + item: item ?? '', + quantity: quantity ?? 0, + nominal: nominal ?? 0, + ); +} + +@freezed +class ProfitLossOperationalExpenseDto with _$ProfitLossOperationalExpenseDto { + const ProfitLossOperationalExpenseDto._(); + + const factory ProfitLossOperationalExpenseDto({ + @JsonKey(name: 'item') String? item, + @JsonKey(name: 'nominal') int? nominal, + }) = _ProfitLossOperationalExpenseDto; + + factory ProfitLossOperationalExpenseDto.fromJson(Map json) => + _$ProfitLossOperationalExpenseDtoFromJson(json); + + ProfitLossOperationalExpense toDomain() => + ProfitLossOperationalExpense(item: item ?? '', nominal: nominal ?? 0); +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 49c3d5f..ea96978 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -496,5 +496,29 @@ "example": "48" } } - } + }, + "mtd_month": "MTD ({month})", + "@mtd_month": { + "placeholders": { + "month": { + "type": "String", + "example": "June" + } + } + }, + "profit_loss_date": "Profit / Loss · {date}", + "@profit_loss_date": { + "placeholders": { + "date": { + "type": "String", + "example": "22 Jun 2026" + } + } + }, + "profit_loss_report": "Profit & Loss Report", + "@profit_loss_report": {}, + "net_profit_loss": "Net Profit/Loss", + "@net_profit_loss": {}, + "cost_breakdown": "Cost Breakdown", + "@cost_breakdown": {} } diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index 6c91c48..a5febf2 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -496,5 +496,29 @@ "example": "48" } } - } + }, + "mtd_month": "MTD ({month})", + "@mtd_month": { + "placeholders": { + "month": { + "type": "String", + "example": "Juni" + } + } + }, + "profit_loss_date": "Laba / Rugi · {date}", + "@profit_loss_date": { + "placeholders": { + "date": { + "type": "String", + "example": "22 Jun 2026" + } + } + }, + "profit_loss_report": "Laporan Laba Rugi", + "@profit_loss_report": {}, + "net_profit_loss": "Laba/Rugi Bersih", + "@net_profit_loss": {}, + "cost_breakdown": "Rincian Biaya", + "@cost_breakdown": {} } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 45104a9..2ec256f 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1480,6 +1480,36 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'{count} portions sold'** String portion_sold(int count); + + /// No description provided for @mtd_month. + /// + /// In en, this message translates to: + /// **'MTD ({month})'** + String mtd_month(String month); + + /// No description provided for @profit_loss_date. + /// + /// In en, this message translates to: + /// **'Profit / Loss · {date}'** + String profit_loss_date(String date); + + /// No description provided for @profit_loss_report. + /// + /// In en, this message translates to: + /// **'Profit & Loss Report'** + String get profit_loss_report; + + /// No description provided for @net_profit_loss. + /// + /// In en, this message translates to: + /// **'Net Profit/Loss'** + String get net_profit_loss; + + /// No description provided for @cost_breakdown. + /// + /// In en, this message translates to: + /// **'Cost Breakdown'** + String get cost_breakdown; } class _AppLocalizationsDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index bd9cf39..60d1516 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -710,4 +710,23 @@ class AppLocalizationsEn extends AppLocalizations { String portion_sold(int count) { return '$count portions sold'; } + + @override + String mtd_month(String month) { + return 'MTD ($month)'; + } + + @override + String profit_loss_date(String date) { + return 'Profit / Loss · $date'; + } + + @override + String get profit_loss_report => 'Profit & Loss Report'; + + @override + String get net_profit_loss => 'Net Profit/Loss'; + + @override + String get cost_breakdown => 'Cost Breakdown'; } diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index 7c9b2de..f573dd0 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -710,4 +710,23 @@ class AppLocalizationsId extends AppLocalizations { String portion_sold(int count) { return '$count porsi terjual'; } + + @override + String mtd_month(String month) { + return 'MTD ($month)'; + } + + @override + String profit_loss_date(String date) { + return 'Laba / Rugi · $date'; + } + + @override + String get profit_loss_report => 'Laporan Laba Rugi'; + + @override + String get net_profit_loss => 'Laba/Rugi Bersih'; + + @override + String get cost_breakdown => 'Rincian Biaya'; } diff --git a/lib/presentation/pages/finance/finance_page.dart b/lib/presentation/pages/finance/finance_page.dart index 77e9ae7..6bbb20f 100644 --- a/lib/presentation/pages/finance/finance_page.dart +++ b/lib/presentation/pages/finance/finance_page.dart @@ -1,21 +1,13 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:line_icons/line_icons.dart'; -import '../../../application/analytic/category_analytic_loader/category_analytic_loader_bloc.dart'; import '../../../application/analytic/profit_loss_loader/profit_loss_loader_bloc.dart'; -import '../../../common/extension/extension.dart'; import '../../../common/theme/theme.dart'; -import '../../../domain/analytic/analytic.dart'; import '../../../injection.dart'; -import '../../components/appbar/appbar.dart'; -import '../../components/field/date_range_picker_field.dart'; -import 'widgets/cash_flow.dart'; -import 'widgets/category.dart'; -import 'widgets/product.dart'; -import 'widgets/profit_loss.dart'; -import 'widgets/summary_card.dart'; +import 'widgets/cost_breakdown.dart'; +import 'widgets/profit_loss_header.dart'; +import 'widgets/profit_loss_report.dart'; @RoutePage() class FinancePage extends StatefulWidget implements AutoRouteWrapper { @@ -25,80 +17,40 @@ class FinancePage extends StatefulWidget implements AutoRouteWrapper { State createState() => _FinancePageState(); @override - Widget wrappedRoute(BuildContext context) => MultiBlocProvider( - providers: [ - BlocProvider( - create: (_) => - getIt()..add(ProfitLossLoaderEvent.fetched()), - ), - BlocProvider( - create: (context) => - getIt() - ..add(CategoryAnalyticLoaderEvent.fetched()), - ), - ], + Widget wrappedRoute(BuildContext context) => BlocProvider( + create: (_) => + getIt()..add(ProfitLossLoaderEvent.fetched()), child: this, ); } class _FinancePageState extends State - with TickerProviderStateMixin { - late AnimationController _slideController; + with SingleTickerProviderStateMixin { late AnimationController _fadeController; - late AnimationController _scaleController; - - late Animation _slideAnimation; late Animation _fadeAnimation; - late Animation _scaleAnimation; + + int _selectedTabIndex = 0; @override void initState() { super.initState(); - _slideController = AnimationController( - duration: const Duration(milliseconds: 800), - vsync: this, - ); - _fadeController = AnimationController( duration: const Duration(milliseconds: 1000), vsync: this, ); - _scaleController = AnimationController( - duration: const Duration(milliseconds: 600), - vsync: this, - ); - - _slideAnimation = - Tween(begin: const Offset(0, 0.3), end: Offset.zero).animate( - CurvedAnimation(parent: _slideController, curve: Curves.easeOutCubic), - ); - _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeIn)); - _scaleAnimation = Tween(begin: 0.8, end: 1.0).animate( - CurvedAnimation(parent: _scaleController, curve: Curves.elasticOut), - ); - - // Start animations _fadeController.forward(); - Future.delayed(const Duration(milliseconds: 200), () { - _slideController.forward(); - }); - Future.delayed(const Duration(milliseconds: 400), () { - _scaleController.forward(); - }); } @override void dispose() { - _slideController.dispose(); _fadeController.dispose(); - _scaleController.dispose(); super.dispose(); } @@ -119,89 +71,48 @@ class _FinancePageState extends State builder: (context, state) { return CustomScrollView( slivers: [ - // SliverAppBar with animated background - SliverAppBar( - expandedHeight: 120, - floating: false, - pinned: true, - backgroundColor: AppColor.primary, - elevation: 0, - flexibleSpace: CustomAppBar(title: context.lang.profit_loss), - ), - - // Header dengan filter periode + // Header with gradient background, tabs, and summary SliverToBoxAdapter( child: FadeTransition( opacity: _fadeAnimation, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: DateRangePickerField( - maxDate: DateTime.now(), - startDate: state.dateFrom, - endDate: state.dateTo, - onChanged: (startDate, endDate) { - context.read().add( - ProfitLossLoaderEvent.rangeDateChanged( - startDate!, - endDate!, - ), - ); - }, - ), + child: ProfitLossHeader( + state: state, + selectedTabIndex: _selectedTabIndex, + onTabChanged: (index) { + setState(() { + _selectedTabIndex = index; + }); + _onTabChanged(context, index); + }, ), ), ), - // Summary Cards - SliverToBoxAdapter( - child: SlideTransition( - position: _slideAnimation, - child: _buildSummaryCards(state.profitLoss.summary), - ), - ), - - // Cash Flow Analysis - SliverToBoxAdapter( - child: ScaleTransition( - scale: _scaleAnimation, - child: FinanceCashFlow(dailyData: state.profitLoss.data), - ), - ), - - // Profit Loss Detail + // Profit Loss Report Table SliverToBoxAdapter( child: FadeTransition( opacity: _fadeAnimation, - child: FinanceProfitLoss(data: state.profitLoss.summary), + child: ProfitLossReport( + mainSummary: state.profitLoss.mainSummary, + summary: state.profitLoss.summary, + selectedTabIndex: _selectedTabIndex, + ), ), ), - BlocBuilder< - CategoryAnalyticLoaderBloc, - CategoryAnalyticLoaderState - >( - builder: (context, stateCategory) { - return SliverToBoxAdapter( - child: SlideTransition( - position: _slideAnimation, - child: FinanceCategory( - categories: stateCategory.categoryAnalytic.data, - ), - ), - ); - }, - ), - - // Product Analysis Section + // Cost Breakdown SliverToBoxAdapter( - child: SlideTransition( - position: _slideAnimation, - child: _buildProductAnalysis(state.profitLoss.productData), + child: FadeTransition( + opacity: _fadeAnimation, + child: CostBreakdown( + purchasing: state.profitLoss.purchasing, + selectedTabIndex: _selectedTabIndex, + dateFrom: state.dateFrom, + dateTo: state.dateTo, + ), ), ), - // Transaction Categories - // Bottom spacing const SliverToBoxAdapter(child: SizedBox(height: 100)), ], @@ -212,125 +123,23 @@ class _FinancePageState extends State ); } - Widget _buildSummaryCards(ProfitLossSummary summary) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Column( - children: [ - Row( - children: [ - Expanded( - child: FinanceSummaryCard( - title: context.lang.total_revenue, - amount: summary.totalRevenue.currencyFormatRp, - icon: LineIcons.arrowUp, - color: AppColor.success, - isPositive: true, - ), - ), - const SizedBox(width: 12), - Expanded( - child: FinanceSummaryCard( - title: context.lang.total_expenditures, - amount: summary.totalCost.currencyFormatRp, - icon: LineIcons.arrowDown, - color: AppColor.error, - isPositive: false, - ), - ), - ], - ), - const SizedBox(height: 12), - Row( - children: [ - Expanded( - child: FinanceSummaryCard( - title: context.lang.net_profit, - amount: summary.netProfit.currencyFormatRp, - icon: LineIcons.lineChart, - color: AppColor.info, - isPositive: true, - ), - ), - const SizedBox(width: 12), - Expanded( - child: FinanceSummaryCard( - title: context.lang.margin_profit, - amount: '${summary.profitabilityRatio.round()}%', - icon: LineIcons.percent, - color: AppColor.warning, - isPositive: true, - ), - ), - ], - ), - ], - ), - ); - } + void _onTabChanged(BuildContext context, int index) { + final now = DateTime.now(); + DateTime dateFrom; + DateTime dateTo; - Widget _buildProductAnalysis(List products) { - return Container( - margin: const EdgeInsets.all(16), - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: AppColor.white, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: AppColor.textLight.withOpacity(0.1), - spreadRadius: 1, - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: AppColor.info.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon( - LineIcons.shoppingBag, - color: AppColor.info, - size: 20, - ), - ), - const SizedBox(width: 12), - Text( - context.lang.product_analytic, - style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold), - ), - const Spacer(), - TextButton( - onPressed: () {}, - child: Text( - context.lang.view_all, - style: AppStyle.sm.copyWith(color: AppColor.primary), - ), - ), - ], - ), - // Product list - ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.only(top: 12), - itemCount: products.length, - separatorBuilder: (context, index) => const SizedBox(height: 12), - itemBuilder: (context, index) { - final product = products[index]; - return ProfitLossProduct(product: product); - }, - ), - ], - ), + if (index == 0) { + // Today + dateFrom = DateTime(now.year, now.month, now.day); + dateTo = DateTime(now.year, now.month, now.day, 23, 59, 59); + } else { + // MTD (Month-to-Date) + dateFrom = DateTime(now.year, now.month, 1); + dateTo = now; + } + + context.read().add( + ProfitLossLoaderEvent.rangeDateChanged(dateFrom, dateTo), ); } } diff --git a/lib/presentation/pages/finance/widgets/cash_flow.dart b/lib/presentation/pages/finance/widgets/cash_flow.dart deleted file mode 100644 index fa6e0cd..0000000 --- a/lib/presentation/pages/finance/widgets/cash_flow.dart +++ /dev/null @@ -1,476 +0,0 @@ -import 'package:fl_chart/fl_chart.dart'; -import 'package:flutter/material.dart'; -import 'package:line_icons/line_icons.dart'; -import 'package:intl/intl.dart'; - -import '../../../../common/extension/extension.dart'; -import '../../../../common/theme/theme.dart'; -import '../../../../domain/analytic/analytic.dart'; - -class FinanceCashFlow extends StatelessWidget { - final List dailyData; - - const FinanceCashFlow({super.key, required this.dailyData}); - - @override - Widget build(BuildContext context) { - // Calculate totals from daily data - final totalCashIn = _calculateTotalCashIn(); - final totalCashOut = _calculateTotalCashOut(); - final netFlow = totalCashIn - totalCashOut; - - return Container( - margin: const EdgeInsets.all(16), - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: AppColor.white, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: AppColor.textLight.withOpacity(0.1), - spreadRadius: 1, - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: AppColor.primaryGradient, - ), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon( - LineIcons.areaChart, - color: AppColor.white, - size: 20, - ), - ), - const SizedBox(width: 12), - Text( - context.lang.cash_flow_analysis, - style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold), - ), - ], - ), - IconButton( - onPressed: () {}, - icon: const Icon( - LineIcons.alternateExternalLink, - color: AppColor.primary, - ), - ), - ], - ), - const SizedBox(height: 20), - - // Cash Flow Indicators - Row( - children: [ - Expanded( - child: _buildCashFlowIndicator( - context.lang.cash_in, - _formatCurrency(totalCashIn), - LineIcons.arrowUp, - AppColor.success, - ), - ), - const SizedBox(width: 16), - Expanded( - child: _buildCashFlowIndicator( - context.lang.cash_out, - _formatCurrency(totalCashOut), - LineIcons.arrowDown, - AppColor.error, - ), - ), - const SizedBox(width: 16), - Expanded( - child: _buildCashFlowIndicator( - context.lang.net_flow, - _formatCurrency(netFlow), - LineIcons.equals, - AppColor.info, - ), - ), - ], - ), - const SizedBox(height: 20), - - // FL Chart Implementation - Container( - height: 200, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColor.background, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: AppColor.borderLight), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.lang.cash_flow_chart(dailyData.length), - style: AppStyle.sm.copyWith( - color: AppColor.textSecondary, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 16), - Expanded( - child: dailyData.isEmpty - ? _buildEmptyChart() - : LineChart(_buildLineChartData()), - ), - const SizedBox(height: 12), - // Legend - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _buildChartLegend(context.lang.cash_in, AppColor.success), - const SizedBox(width: 20), - _buildChartLegend(context.lang.cash_out, AppColor.error), - const SizedBox(width: 20), - _buildChartLegend(context.lang.net_flow, AppColor.info), - ], - ), - ], - ), - ), - ], - ), - ); - } - - LineChartData _buildLineChartData() { - final maxValue = _getMaxChartValue(); - final minValue = _getMinChartValue(); - - return LineChartData( - gridData: FlGridData( - show: true, - drawVerticalLine: false, - horizontalInterval: (maxValue / 5).roundToDouble(), - getDrawingHorizontalLine: (value) { - return FlLine(color: AppColor.borderLight, strokeWidth: 1); - }, - ), - titlesData: FlTitlesData( - show: true, - rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), - topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - reservedSize: 30, - interval: 1, - getTitlesWidget: (double value, TitleMeta meta) { - final index = value.toInt(); - if (index >= 0 && index < dailyData.length) { - final date = DateTime.parse(dailyData[index].date); - final dayName = _getDayName(date.weekday); - return SideTitleWidget( - meta: meta, - child: Text( - dayName, - style: const TextStyle( - color: AppColor.textSecondary, - fontWeight: FontWeight.w500, - fontSize: 10, - ), - ), - ); - } - return SideTitleWidget(meta: meta, child: Text('')); - }, - ), - ), - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - interval: (maxValue / 3).roundToDouble(), - reservedSize: 42, - getTitlesWidget: (double value, TitleMeta meta) { - return Text( - _formatChartValue(value), - style: const TextStyle( - color: AppColor.textSecondary, - fontWeight: FontWeight.w500, - fontSize: 10, - ), - textAlign: TextAlign.left, - ); - }, - ), - ), - ), - borderData: FlBorderData( - show: true, - border: Border.all(color: AppColor.borderLight), - ), - minX: 0, - maxX: (dailyData.length - 1).toDouble(), - minY: minValue, - maxY: maxValue, - lineBarsData: [ - // Cash In Line (Revenue) - LineChartBarData( - spots: _buildCashInSpots(), - isCurved: true, - gradient: LinearGradient( - colors: [AppColor.success.withOpacity(0.8), AppColor.success], - ), - barWidth: 3, - isStrokeCapRound: true, - dotData: FlDotData( - show: true, - getDotPainter: (spot, percent, barData, index) { - return FlDotCirclePainter( - radius: 4, - color: AppColor.success, - strokeWidth: 2, - strokeColor: AppColor.white, - ); - }, - ), - belowBarData: BarAreaData( - show: true, - gradient: LinearGradient( - colors: [ - AppColor.success.withOpacity(0.1), - AppColor.success.withOpacity(0.0), - ], - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - ), - ), - ), - // Cash Out Line (Total Cost) - LineChartBarData( - spots: _buildCashOutSpots(), - isCurved: true, - gradient: LinearGradient( - colors: [AppColor.error.withOpacity(0.8), AppColor.error], - ), - barWidth: 3, - isStrokeCapRound: true, - dotData: FlDotData( - show: true, - getDotPainter: (spot, percent, barData, index) { - return FlDotCirclePainter( - radius: 4, - color: AppColor.error, - strokeWidth: 2, - strokeColor: AppColor.white, - ); - }, - ), - ), - // Net Flow Line (Net Profit) - LineChartBarData( - spots: _buildNetFlowSpots(), - isCurved: true, - gradient: LinearGradient( - colors: [AppColor.info.withOpacity(0.8), AppColor.info], - ), - barWidth: 3, - isStrokeCapRound: true, - dotData: FlDotData( - show: true, - getDotPainter: (spot, percent, barData, index) { - return FlDotCirclePainter( - radius: 4, - color: AppColor.info, - strokeWidth: 2, - strokeColor: AppColor.white, - ); - }, - ), - ), - ], - ); - } - - Widget _buildEmptyChart() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - LineIcons.lineChart, - size: 48, - color: AppColor.textSecondary.withOpacity(0.3), - ), - const SizedBox(height: 12), - Text( - 'Tidak ada data untuk ditampilkan', - style: AppStyle.sm.copyWith(color: AppColor.textSecondary), - ), - ], - ), - ); - } - - // Helper methods for calculating data - int _calculateTotalCashIn() { - return dailyData.fold(0, (sum, data) => sum + data.revenue); - } - - int _calculateTotalCashOut() { - return dailyData.fold( - 0, - (sum, data) => sum + data.cost + data.tax + data.discount, - ); - } - - double _getMaxChartValue() { - if (dailyData.isEmpty) return 30000000; - - final maxRevenue = dailyData - .map((e) => e.revenue) - .reduce((a, b) => a > b ? a : b); - final maxCost = dailyData - .map((e) => e.cost + e.tax + e.discount) - .reduce((a, b) => a > b ? a : b); - final maxValue = maxRevenue > maxCost ? maxRevenue : maxCost; - - return (maxValue * 1.2).toDouble(); // Add 20% padding - } - - double _getMinChartValue() { - if (dailyData.isEmpty) return -5000000; - - final minNetProfit = dailyData - .map((e) => e.netProfit) - .reduce((a, b) => a < b ? a : b); - return minNetProfit < 0 ? (minNetProfit * 1.2).toDouble() : 0; - } - - List _buildCashInSpots() { - return dailyData.asMap().entries.map((entry) { - return FlSpot(entry.key.toDouble(), entry.value.revenue.toDouble()); - }).toList(); - } - - List _buildCashOutSpots() { - return dailyData.asMap().entries.map((entry) { - final totalCost = - entry.value.cost + entry.value.tax + entry.value.discount; - return FlSpot(entry.key.toDouble(), totalCost.toDouble()); - }).toList(); - } - - List _buildNetFlowSpots() { - return dailyData.asMap().entries.map((entry) { - return FlSpot(entry.key.toDouble(), entry.value.netProfit.toDouble()); - }).toList(); - } - - String _getDayName(int weekday) { - switch (weekday) { - case 1: - return 'Sen'; - case 2: - return 'Sel'; - case 3: - return 'Rab'; - case 4: - return 'Kam'; - case 5: - return 'Jum'; - case 6: - return 'Sab'; - case 7: - return 'Min'; - default: - return ''; - } - } - - String _formatChartValue(double value) { - if (value.abs() >= 1000000) { - return '${(value / 1000000).toStringAsFixed(0)}M'; - } else if (value.abs() >= 1000) { - return '${(value / 1000).toStringAsFixed(0)}K'; - } else { - return value.toStringAsFixed(0); - } - } - - String _formatCurrency(int amount) { - if (amount.abs() >= 1000000000) { - return 'Rp ${(amount / 1000000000).toStringAsFixed(1)}B'; - } else if (amount.abs() >= 1000000) { - return 'Rp ${(amount / 1000000).toStringAsFixed(1)}M'; - } else if (amount.abs() >= 1000) { - return 'Rp ${(amount / 1000).toStringAsFixed(1)}K'; - } else { - return 'Rp ${NumberFormat('#,###', 'id_ID').format(amount)}'; - } - } - - Widget _buildChartLegend(String label, Color color) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 12, - height: 12, - decoration: BoxDecoration(color: color, shape: BoxShape.circle), - ), - const SizedBox(width: 6), - Text( - label, - style: AppStyle.xs.copyWith( - color: AppColor.textSecondary, - fontWeight: FontWeight.w500, - ), - ), - ], - ); - } - - Widget _buildCashFlowIndicator( - String label, - String amount, - IconData icon, - Color color, - ) { - return Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: color.withOpacity(0.05), - borderRadius: BorderRadius.circular(12), - border: Border.all(color: color.withOpacity(0.2)), - ), - child: Column( - children: [ - Icon(icon, color: color, size: 20), - const SizedBox(height: 8), - Text( - label, - style: AppStyle.xs.copyWith(color: AppColor.textSecondary), - ), - const SizedBox(height: 4), - Text( - amount, - style: AppStyle.md.copyWith( - fontWeight: FontWeight.bold, - color: color, - ), - ), - ], - ), - ); - } -} diff --git a/lib/presentation/pages/finance/widgets/category.dart b/lib/presentation/pages/finance/widgets/category.dart deleted file mode 100644 index df21ac6..0000000 --- a/lib/presentation/pages/finance/widgets/category.dart +++ /dev/null @@ -1,209 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:line_icons/line_icons.dart'; -import 'package:intl/intl.dart'; - -import '../../../../common/extension/extension.dart'; -import '../../../../common/theme/theme.dart'; -import '../../../../domain/analytic/analytic.dart'; -import '../../../components/widgets/empty_widget.dart'; - -class FinanceCategory extends StatelessWidget { - final List categories; - - const FinanceCategory({super.key, required this.categories}); - - @override - Widget build(BuildContext context) { - final totalRevenue = _calculateTotalRevenue(); - final sortedCategories = _sortCategoriesByRevenue(); - - return Container( - margin: const EdgeInsets.all(16), - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: AppColor.white, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: AppColor.textLight.withOpacity(0.1), - spreadRadius: 1, - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: AppColor.secondary.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon( - LineIcons.pieChart, - color: AppColor.secondary, - size: 20, - ), - ), - const SizedBox(width: 12), - Text( - context.lang.sales_category, - style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold), - ), - ], - ), - const SizedBox(height: 20), - - // Show empty state if no categories - if (categories.isEmpty) - _buildEmptyState(context) - else - ...sortedCategories.asMap().entries.map( - (entry) => _buildCategoryItem( - context, - entry.value, - _calculatePercentage(entry.value.totalRevenue, totalRevenue), - _getCategoryColor(entry.key), - ), - ), - ], - ), - ); - } - - Widget _buildCategoryItem( - BuildContext context, - CategoryAnalyticItem category, - double percentage, - Color color, - ) { - return Container( - margin: const EdgeInsets.only(bottom: 16), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Row( - children: [ - Container( - width: 12, - height: 12, - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular(6), - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - category.categoryName, - style: AppStyle.md.copyWith( - fontWeight: FontWeight.w600, - ), - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 2), - Text( - '${category.productCount} ${context.lang.product} • ${category.orderCount} ${context.lang.orders}', - style: AppStyle.xs.copyWith( - color: AppColor.textSecondary, - ), - ), - ], - ), - ), - ], - ), - ), - const SizedBox(width: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - category.totalRevenue.currencyFormatRp, - style: AppStyle.md.copyWith( - fontWeight: FontWeight.bold, - color: color, - ), - ), - Text( - '${NumberFormat('#,###', 'id_ID').format(category.totalQuantity)} ${context.lang.unit}', - style: AppStyle.xs.copyWith(color: AppColor.textSecondary), - ), - ], - ), - ], - ), - const SizedBox(height: 8), - LinearProgressIndicator( - value: percentage / 100, - backgroundColor: AppColor.borderLight, - valueColor: AlwaysStoppedAnimation(color), - minHeight: 6, - ), - const SizedBox(height: 4), - Align( - alignment: Alignment.centerRight, - child: Text( - '${percentage.toStringAsFixed(1)}%', - style: AppStyle.xs.copyWith(color: AppColor.textSecondary), - ), - ), - ], - ), - ); - } - - Widget _buildEmptyState(BuildContext context) { - return EmptyWidget( - title: context.lang.category_no_data, - message: context.lang.category_no_data_desc, - ); - } - - // Helper methods - int _calculateTotalRevenue() { - return categories.fold(0, (sum, category) => sum + category.totalRevenue); - } - - List _sortCategoriesByRevenue() { - final sorted = List.from(categories); - sorted.sort((a, b) => b.totalRevenue.compareTo(a.totalRevenue)); - return sorted; - } - - double _calculatePercentage(int categoryRevenue, int totalRevenue) { - if (totalRevenue == 0) return 0; - return (categoryRevenue / totalRevenue) * 100; - } - - Color _getCategoryColor(int index) { - // Predefined color palette for categories - const colors = [ - AppColor.primary, - AppColor.secondary, - AppColor.success, - AppColor.warning, - AppColor.error, - AppColor.info, - ]; - - // Generate additional colors if needed - if (index < colors.length) { - return colors[index]; - } else { - // Generate colors based on index for unlimited categories - final hue = (index * 137.5) % 360; // Golden angle approximation - return HSLColor.fromAHSL(1.0, hue, 0.7, 0.5).toColor(); - } - } -} diff --git a/lib/presentation/pages/finance/widgets/cost_breakdown.dart b/lib/presentation/pages/finance/widgets/cost_breakdown.dart new file mode 100644 index 0000000..264111b --- /dev/null +++ b/lib/presentation/pages/finance/widgets/cost_breakdown.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/extension/extension.dart'; +import '../../../../common/theme/theme.dart'; +import '../../../../domain/analytic/analytic.dart'; + +class CostBreakdown extends StatelessWidget { + final ProfitLossPurchasing purchasing; + final int selectedTabIndex; + final DateTime dateFrom; + final DateTime dateTo; + + const CostBreakdown({ + super.key, + required this.purchasing, + required this.selectedTabIndex, + required this.dateFrom, + required this.dateTo, + }); + + @override + Widget build(BuildContext context) { + final isToday = selectedTabIndex == 0; + final total = isToday ? purchasing.todayTotal : purchasing.mtdTotal; + + return Container( + margin: const EdgeInsets.fromLTRB(16, 0, 16, 16), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColor.textLight.withOpacity(0.08), + spreadRadius: 1, + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Title row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + context.lang.cost_breakdown, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w700, + color: AppColor.textPrimary, + ), + ), + Text( + _formatDateLabel(dateFrom, dateTo), + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + + const SizedBox(height: 16), + + // Item list + ...purchasing.items.map((item) => _buildItemRow(item)), + + // Total row + const Divider(height: 24, color: AppColor.borderLight), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + context.lang.total_cost, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w700, + color: AppColor.textPrimary, + ), + ), + Text( + total.currencyFormatRp, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w700, + color: AppColor.textPrimary, + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildItemRow(ProfitLossPurchasingItem item) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + children: [ + Expanded( + child: Text( + item.item, + style: AppStyle.md.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w400, + ), + ), + ), + Text( + item.nominal.currencyFormatRp, + style: AppStyle.md.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ); + } + + String _formatDateLabel(DateTime from, DateTime to) { + const months = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'Mei', + 'Jun', + 'Jul', + 'Agu', + 'Sep', + 'Okt', + 'Nov', + 'Des', + ]; + + if (from.year == to.year && from.month == to.month && from.day == to.day) { + return '${from.day} ${months[from.month - 1]} ${from.year}'; + } + return '${from.day} ${months[from.month - 1]} - ${to.day} ${months[to.month - 1]} ${to.year}'; + } +} diff --git a/lib/presentation/pages/finance/widgets/product.dart b/lib/presentation/pages/finance/widgets/product.dart deleted file mode 100644 index 7f41843..0000000 --- a/lib/presentation/pages/finance/widgets/product.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../../common/extension/extension.dart'; -import '../../../../common/theme/theme.dart'; -import '../../../../domain/analytic/analytic.dart'; - -class ProfitLossProduct extends StatelessWidget { - final ProfitLossProductData product; - const ProfitLossProduct({super.key, required this.product}); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColor.background, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: AppColor.border.withOpacity(0.5)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Product header - Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - product.productName, - style: AppStyle.md.copyWith(fontWeight: FontWeight.bold), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 2), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, - vertical: 2, - ), - decoration: BoxDecoration( - color: AppColor.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Text( - product.categoryName, - style: AppStyle.xs.copyWith(color: AppColor.primary), - ), - ), - ], - ), - ), - const SizedBox(width: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - '${product.quantitySold} terjual', - style: AppStyle.sm.copyWith(color: AppColor.textSecondary), - ), - Text( - '${product.grossProfitMargin.toStringAsFixed(1)}%', - style: AppStyle.md.copyWith( - fontWeight: FontWeight.bold, - color: product.grossProfitMargin > 25 - ? AppColor.success - : product.grossProfitMargin > 15 - ? AppColor.warning - : AppColor.error, - ), - ), - ], - ), - ], - ), - const SizedBox(height: 16), - - // Financial metrics - Row( - children: [ - Expanded( - child: _buildMetricColumn( - context.lang.revenue, - product.revenue.currencyFormatRp, - AppColor.success, - ), - ), - Expanded( - child: _buildMetricColumn( - context.lang.cost, - product.cost.currencyFormatRp, - AppColor.error, - ), - ), - Expanded( - child: _buildMetricColumn( - context.lang.gross_profit, - product.grossProfit.currencyFormatRp, - AppColor.info, - ), - ), - ], - ), - const SizedBox(height: 12), - - // Average metrics - Row( - children: [ - Expanded( - child: _buildMetricColumn( - context.lang.average_price, - product.averagePrice.currencyFormatRp, - AppColor.textSecondary, - ), - ), - Expanded( - child: _buildMetricColumn( - context.lang.profit_per_unit, - product.profitPerUnit.currencyFormatRp, - AppColor.primary, - ), - ), - const Expanded(child: SizedBox()), - ], - ), - ], - ), - ); - } - - Widget _buildMetricColumn(String label, String value, Color color) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(label, style: AppStyle.xs.copyWith(color: AppColor.textSecondary)), - const SizedBox(height: 2), - Text( - value, - style: AppStyle.sm.copyWith( - fontWeight: FontWeight.w600, - color: color, - ), - ), - ], - ); - } -} diff --git a/lib/presentation/pages/finance/widgets/profit_loss.dart b/lib/presentation/pages/finance/widgets/profit_loss.dart deleted file mode 100644 index 929c04e..0000000 --- a/lib/presentation/pages/finance/widgets/profit_loss.dart +++ /dev/null @@ -1,192 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:line_icons/line_icons.dart'; - -import '../../../../common/extension/extension.dart'; -import '../../../../common/theme/theme.dart'; -import '../../../../domain/analytic/analytic.dart'; - -class FinanceProfitLoss extends StatelessWidget { - final ProfitLossSummary data; - - const FinanceProfitLoss({super.key, required this.data}); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.all(16), - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: AppColor.white, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: AppColor.textLight.withOpacity(0.1), - spreadRadius: 1, - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: AppColor.info.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon( - LineIcons.calculator, - color: AppColor.info, - size: 20, - ), - ), - const SizedBox(width: 12), - Text( - context.lang.profit_loss_detail, - style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold), - ), - ], - ), - const SizedBox(height: 20), - - // Total Revenue (Penjualan Kotor) - _buildPLItem( - context.lang.gross_sales, - data.totalRevenue.currencyFormatRp, - AppColor.success, - true, - ), - - // Discount (Diskon & Retur) - _buildPLItem( - '${context.lang.discount} & ${context.lang.return_text}', - '- ${data.totalDiscount.currencyFormatRp}', - AppColor.error, - false, - ), - - const Divider(height: 24), - - // Net Sales (Penjualan Bersih = Total Revenue - Discount) - _buildPLItem( - context.lang.net_sales, - (data.totalRevenue - data.totalDiscount).currencyFormatRp, - AppColor.textPrimary, - true, - isHeader: true, - ), - - const SizedBox(height: 12), - - // Cost of Goods Sold (HPP) - _buildPLItem( - '${context.lang.cogs} (${context.lang.cost_of_goods_sold})', - '- ${data.totalCost.currencyFormatRp}', - AppColor.error, - false, - ), - - const Divider(height: 24), - - // Gross Profit (Laba Kotor) - _buildPLItem( - context.lang.gross_profit, - data.grossProfit.currencyFormatRp, - AppColor.success, - true, - isHeader: true, - showPercentage: true, - percentage: '${data.grossProfitMargin.toStringAsFixed(1)}%', - ), - - const SizedBox(height: 12), - - // Operational Cost (Biaya Operasional) - calculated as difference - _buildPLItem( - context.lang.operating_costs, - '- ${_calculateOperationalCost().currencyFormatRp}', - AppColor.error, - false, - ), - - const Divider(height: 24), - - // Net Profit (Laba Bersih) - _buildPLItem( - context.lang.net_profit, - data.netProfit.currencyFormatRp, - AppColor.primary, - true, - isHeader: true, - showPercentage: true, - percentage: '${data.netProfitMargin.round()}%', - ), - ], - ), - ); - } - - Widget _buildPLItem( - String title, - String amount, - Color color, - bool isPositive, { - bool isHeader = false, - bool showPercentage = false, - String? percentage, - }) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - title, - style: isHeader - ? AppStyle.md.copyWith( - fontWeight: FontWeight.bold, - color: color, - ) - : AppStyle.md.copyWith(color: AppColor.textSecondary), - ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - amount, - style: isHeader - ? AppStyle.lg.copyWith( - fontWeight: FontWeight.bold, - color: color, - ) - : AppStyle.md.copyWith( - color: color, - fontWeight: FontWeight.w600, - ), - ), - if (showPercentage && percentage != null) - Text( - percentage, - style: AppStyle.xs.copyWith( - color: AppColor.textSecondary, - fontStyle: FontStyle.italic, - ), - ), - ], - ), - ], - ), - ); - } - - // Calculate operational cost as the difference between gross profit and net profit - int _calculateOperationalCost() { - return data.grossProfit - data.netProfit - data.totalTax; - } -} diff --git a/lib/presentation/pages/finance/widgets/profit_loss_header.dart b/lib/presentation/pages/finance/widgets/profit_loss_header.dart new file mode 100644 index 0000000..f74deb1 --- /dev/null +++ b/lib/presentation/pages/finance/widgets/profit_loss_header.dart @@ -0,0 +1,389 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + +import '../../../../application/analytic/profit_loss_loader/profit_loss_loader_bloc.dart'; +import '../../../../common/extension/extension.dart'; +import '../../../../common/painter/wave_painter.dart'; +import '../../../../common/theme/theme.dart'; +import '../../../components/spacer/spacer.dart'; + +class ProfitLossHeader extends StatelessWidget { + final ProfitLossLoaderState state; + final int selectedTabIndex; + final ValueChanged onTabChanged; + + const ProfitLossHeader({ + super.key, + required this.state, + required this.selectedTabIndex, + required this.onTabChanged, + }); + + @override + Widget build(BuildContext context) { + final outletLabel = state.profitLoss.outletName.isNotEmpty + ? state.profitLoss.outletName + : 'Semua Outlet'; + + return Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(24), + bottomRight: Radius.circular(24), + ), + ), + child: Stack( + children: [ + // Decorative circles + Positioned( + top: -20, + right: -30, + child: Container( + width: 120, + height: 120, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.textWhite.withOpacity(0.08), + ), + ), + ), + Positioned( + top: 30, + right: 20, + child: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.textWhite.withOpacity(0.05), + ), + ), + ), + Positioned( + top: 10, + left: -20, + child: Container( + width: 80, + height: 80, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.textWhite.withOpacity(0.04), + ), + ), + ), + + // Wave pattern + Positioned.fill( + child: CustomPaint( + painter: WavePainter( + animation: 0.0, + color: AppColor.textWhite.withOpacity(0.1), + ), + ), + ), + + // Content + SafeArea( + bottom: false, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Back button + Title row + Row( + children: [ + GestureDetector( + onTap: () => context.router.maybePop(), + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: AppColor.textWhite.withOpacity(0.15), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.chevron_left_rounded, + color: AppColor.textWhite, + size: 24, + ), + ), + ), + const SpaceWidth(12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.lang.profit_loss, + style: AppStyle.xl.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w700, + fontSize: 20, + ), + ), + const SizedBox(height: 2), + Text( + outletLabel, + style: AppStyle.sm.copyWith( + color: AppColor.textWhite.withOpacity(0.75), + fontWeight: FontWeight.w400, + fontSize: 12, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + + const SpaceHeight(20), + + // Tab selector (Today / MTD) + _buildTabSelector(context), + + const SpaceHeight(24), + + // Profit / Loss label with date + Text( + context.lang.profit_loss_date( + _formatDateLabel(state.dateFrom, state.dateTo), + ), + style: AppStyle.sm.copyWith( + color: AppColor.textWhite.withOpacity(0.75), + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + + const SpaceHeight(4), + + // Big profit/loss value + state.isFetching + ? _buildHeaderValueShimmer() + : Text( + state.profitLoss.summary.netProfit.currencyFormatRp, + style: AppStyle.h1.copyWith( + color: state.profitLoss.summary.netProfit >= 0 + ? AppColor.textWhite + : AppColor.textWhite.withOpacity(0.7), + fontWeight: FontWeight.w900, + fontSize: 32, + ), + ), + + const SpaceHeight(16), + + // Chips row (Omset + Total Biaya) + state.isFetching + ? _buildHeaderChipsShimmer() + : _buildHeaderChips(context), + ], + ), + ), + ), + ], + ), + ); + } + + Widget _buildTabSelector(BuildContext context) { + final todayLabel = _formatTodayTabLabel(DateTime.now()); + final mtdLabel = context.lang.mtd_month( + _getMonthName(DateTime.now().month), + ); + + return Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: AppColor.textWhite.withOpacity(0.15), + borderRadius: BorderRadius.circular(30), + border: Border.all(color: AppColor.textWhite.withOpacity(0.2)), + ), + child: Row( + children: [ + Expanded( + child: _buildTab( + label: todayLabel, + isSelected: selectedTabIndex == 0, + onTap: () => onTabChanged(0), + ), + ), + Expanded( + child: _buildTab( + label: mtdLabel, + isSelected: selectedTabIndex == 1, + onTap: () => onTabChanged(1), + ), + ), + ], + ), + ); + } + + Widget _buildTab({ + required String label, + required bool isSelected, + required VoidCallback onTap, + }) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 10), + decoration: BoxDecoration( + color: isSelected ? AppColor.white : Colors.transparent, + borderRadius: BorderRadius.circular(26), + ), + child: Center( + child: Text( + label, + style: AppStyle.md.copyWith( + color: isSelected ? AppColor.textPrimary : AppColor.textWhite, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ); + } + + Widget _buildHeaderValueShimmer() { + return Shimmer.fromColors( + baseColor: AppColor.textWhite.withOpacity(0.3), + highlightColor: AppColor.textWhite.withOpacity(0.6), + child: Container( + width: 200, + height: 36, + decoration: BoxDecoration( + color: AppColor.textWhite.withOpacity(0.3), + borderRadius: BorderRadius.circular(8), + ), + ), + ); + } + + Widget _buildHeaderChipsShimmer() { + return Row( + children: List.generate( + 2, + (index) => Padding( + padding: const EdgeInsets.only(right: 8), + child: Shimmer.fromColors( + baseColor: AppColor.textWhite.withOpacity(0.15), + highlightColor: AppColor.textWhite.withOpacity(0.3), + child: Container( + width: 130, + height: 32, + decoration: BoxDecoration( + color: AppColor.textWhite.withOpacity(0.15), + borderRadius: BorderRadius.circular(20), + ), + ), + ), + ), + ), + ); + } + + Widget _buildHeaderChips(BuildContext context) { + final summary = state.profitLoss.summary; + + return Wrap( + spacing: 8, + runSpacing: 8, + children: [ + _buildChip( + '${context.lang.sales} ${summary.totalRevenue.currencyFormatRp}', + ), + _buildChip( + '${context.lang.total_cost} ${summary.totalCost.currencyFormatRp}', + ), + ], + ); + } + + Widget _buildChip(String label) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + decoration: BoxDecoration( + color: AppColor.textWhite.withOpacity(0.15), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: AppColor.textWhite.withOpacity(0.25)), + ), + child: Text( + label, + style: AppStyle.sm.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w600, + fontSize: 12, + ), + ), + ); + } + + String _formatTodayTabLabel(DateTime date) { + const months = [ + 'Januari', + 'Februari', + 'Maret', + 'April', + 'Mei', + 'Juni', + 'Juli', + 'Agustus', + 'September', + 'Oktober', + 'November', + 'Desember', + ]; + return '${date.day} ${months[date.month - 1]}'; + } + + String _formatDateLabel(DateTime from, DateTime to) { + const months = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'Mei', + 'Jun', + 'Jul', + 'Agu', + 'Sep', + 'Okt', + 'Nov', + 'Des', + ]; + + if (from.year == to.year && from.month == to.month && from.day == to.day) { + return '${from.day} ${months[from.month - 1]} ${from.year}'; + } + return '${from.day} ${months[from.month - 1]} - ${to.day} ${months[to.month - 1]} ${to.year}'; + } + + String _getMonthName(int month) { + const months = [ + 'Januari', + 'Februari', + 'Maret', + 'April', + 'Mei', + 'Juni', + 'Juli', + 'Agustus', + 'September', + 'Oktober', + 'November', + 'Desember', + ]; + return months[month - 1]; + } +} diff --git a/lib/presentation/pages/finance/widgets/profit_loss_report.dart b/lib/presentation/pages/finance/widgets/profit_loss_report.dart new file mode 100644 index 0000000..15e4a9f --- /dev/null +++ b/lib/presentation/pages/finance/widgets/profit_loss_report.dart @@ -0,0 +1,238 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/extension/extension.dart'; +import '../../../../common/theme/theme.dart'; +import '../../../../domain/analytic/analytic.dart'; + +class ProfitLossReport extends StatelessWidget { + final List mainSummary; + final ProfitLossSummary summary; + final int selectedTabIndex; + + const ProfitLossReport({ + super.key, + required this.mainSummary, + required this.summary, + required this.selectedTabIndex, + }); + + @override + Widget build(BuildContext context) { + final isToday = selectedTabIndex == 0; + final marginPct = isToday + ? summary.netProfitMargin.round() + : summary.netProfitMargin.round(); + + return Container( + margin: const EdgeInsets.all(16), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: AppColor.textLight.withOpacity(0.08), + spreadRadius: 1, + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Title row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + context.lang.profit_loss_report, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w700, + color: AppColor.textPrimary, + ), + ), + Text( + 'margin $marginPct%', + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + + const SizedBox(height: 20), + + // Main summary items + ...mainSummary.map((item) => _buildSummarySection(item, isToday)), + + const SizedBox(height: 12), + + // Net Profit/Loss footer + _buildNetProfitFooter(context, isToday), + ], + ), + ); + } + + Widget _buildSummarySection(ProfitLossMainSummaryItem item, bool isToday) { + final nominal = isToday ? item.todayNominal : item.mtdNominal; + final pct = isToday ? item.todayPct : item.mtdPct; + + return Column( + children: [ + // Main item row + _buildItemRow( + label: item.label, + nominal: nominal, + pct: pct, + isBold: item.isBold, + isSubItem: false, + ), + + // Sub items + ...item.subItems.map((subItem) { + final subNominal = isToday + ? subItem.todayNominal + : subItem.mtdNominal; + final subPct = isToday ? subItem.todayPct : subItem.mtdPct; + return _buildItemRow( + label: subItem.label, + nominal: subNominal, + pct: subPct, + isBold: subItem.isBold, + isSubItem: true, + ); + }), + + // Divider after section (except for sub-items only sections) + if (item.isBold) + const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Divider(height: 1, color: AppColor.borderLight), + ), + ], + ); + } + + Widget _buildItemRow({ + required String label, + required int nominal, + required double pct, + required bool isBold, + required bool isSubItem, + }) { + final isNegative = nominal < 0; + final displayNominal = isNegative + ? '-${nominal.abs().currencyFormatRp}' + : nominal.currencyFormatRp; + final pctText = '${pct.round()}%'; + + // Determine color based on context + Color nominalColor; + if (isBold && isNegative) { + nominalColor = AppColor.error; + } else if (isBold) { + nominalColor = AppColor.textPrimary; + } else { + nominalColor = AppColor.textPrimary; + } + + return Padding( + padding: EdgeInsets.only(left: isSubItem ? 16 : 0, top: 6, bottom: 6), + child: Row( + children: [ + // Label + Expanded( + flex: 5, + child: Text( + label, + style: isBold + ? AppStyle.md.copyWith( + fontWeight: FontWeight.w700, + color: AppColor.textPrimary, + ) + : AppStyle.md.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w400, + ), + ), + ), + + // Nominal + Expanded( + flex: 3, + child: Text( + displayNominal, + textAlign: TextAlign.right, + style: isBold + ? AppStyle.md.copyWith( + fontWeight: FontWeight.w700, + color: nominalColor, + ) + : AppStyle.md.copyWith( + color: AppColor.textPrimary, + fontWeight: FontWeight.w500, + ), + ), + ), + + // Percentage + SizedBox( + width: 48, + child: Text( + pctText, + textAlign: TextAlign.right, + style: AppStyle.sm.copyWith( + color: AppColor.textSecondary, + fontWeight: FontWeight.w400, + ), + ), + ), + ], + ), + ); + } + + Widget _buildNetProfitFooter(BuildContext context, bool isToday) { + final netProfit = summary.netProfit; + final isNegative = netProfit < 0; + final displayValue = isNegative + ? '-${netProfit.abs().currencyFormatRp}' + : netProfit.currencyFormatRp; + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + decoration: BoxDecoration( + color: isNegative + ? AppColor.error.withOpacity(0.08) + : AppColor.success.withOpacity(0.08), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Flexible( + child: Text( + context.lang.net_profit_loss, + style: AppStyle.md.copyWith( + fontWeight: FontWeight.w700, + color: isNegative ? AppColor.error : AppColor.success, + ), + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: 12), + Text( + displayValue, + style: AppStyle.lg.copyWith( + fontWeight: FontWeight.w900, + color: isNegative ? AppColor.error : AppColor.success, + fontSize: 20, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/finance/widgets/summary_card.dart b/lib/presentation/pages/finance/widgets/summary_card.dart deleted file mode 100644 index 26daaf6..0000000 --- a/lib/presentation/pages/finance/widgets/summary_card.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../../common/theme/theme.dart'; - -class FinanceSummaryCard extends StatelessWidget { - const FinanceSummaryCard({ - super.key, - required this.title, - required this.amount, - required this.icon, - required this.color, - required this.isPositive, - }); - - final String title; - final String amount; - final IconData icon; - final Color color; - final bool isPositive; - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColor.white, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: AppColor.textLight.withOpacity(0.1), - spreadRadius: 1, - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: color.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Icon(icon, color: color, size: 20), - ), - ], - ), - const SizedBox(height: 12), - Text( - title, - style: AppStyle.sm.copyWith(color: AppColor.textSecondary), - ), - const SizedBox(height: 4), - Text( - amount, - style: AppStyle.lg.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textPrimary, - ), - ), - ], - ), - ); - } -} diff --git a/lib/presentation/pages/purchase/purchase_page.dart b/lib/presentation/pages/purchase/purchase_page.dart index 75f0eb8..513f733 100644 --- a/lib/presentation/pages/purchase/purchase_page.dart +++ b/lib/presentation/pages/purchase/purchase_page.dart @@ -1,7 +1,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:line_icons/line_icons.dart'; import 'package:shimmer/shimmer.dart'; import '../../../application/analytic/purchasing_analytic_loader/purchasing_analytic_loader_bloc.dart'; @@ -9,13 +8,12 @@ import '../../../application/outlet/outlet_list_loader/outlet_list_loader_bloc.d import '../../../common/extension/extension.dart'; import '../../../common/theme/theme.dart'; import '../../../injection.dart'; -import '../../components/appbar/appbar.dart'; -import '../../components/field/date_range_picker_field.dart'; import '../../components/spacer/spacer.dart'; import 'widgets/ingredient_card.dart'; import 'widgets/outlet_selector_field.dart'; import 'widgets/purchase_daily_tile.dart'; -import 'widgets/stat_card.dart'; +import 'widgets/purchase_header.dart'; +import 'widgets/purchase_rincian_card.dart'; import 'widgets/vendor_card.dart'; @RoutePage() @@ -29,12 +27,14 @@ class PurchasePage extends StatefulWidget implements AutoRouteWrapper { Widget wrappedRoute(BuildContext context) => MultiBlocProvider( providers: [ BlocProvider( - create: (context) => getIt() - ..add(const PurchasingAnalyticLoaderEvent.fetched()), + create: (context) => + getIt() + ..add(const PurchasingAnalyticLoaderEvent.fetched()), ), BlocProvider( - create: (context) => getIt() - ..add(const OutletListLoaderEvent.fetched()), + create: (context) => + getIt() + ..add(const OutletListLoaderEvent.fetched()), ), ], child: this, @@ -74,19 +74,20 @@ class _PurchasePageState extends State backgroundColor: AppColor.background, body: MultiBlocListener( listeners: [ - // Re-fetch when date range changes - BlocListener( + BlocListener< + PurchasingAnalyticLoaderBloc, + PurchasingAnalyticLoaderState + >( listenWhen: (prev, curr) => - prev.dateFrom != curr.dateFrom || - prev.dateTo != curr.dateTo, + prev.dateFrom != curr.dateFrom || prev.dateTo != curr.dateTo, listener: (context, _) => context .read() .add(const PurchasingAnalyticLoaderEvent.fetched()), ), - // Re-fetch when outlet changes - BlocListener( + BlocListener< + PurchasingAnalyticLoaderBloc, + PurchasingAnalyticLoaderState + >( listenWhen: (prev, curr) => prev.outletId != curr.outletId, listener: (context, _) => context .read() @@ -95,108 +96,61 @@ class _PurchasePageState extends State ], child: BlocBuilder( builder: (context, outletListState) { - return BlocBuilder( + return BlocBuilder< + PurchasingAnalyticLoaderBloc, + PurchasingAnalyticLoaderState + >( builder: (context, state) { return CustomScrollView( slivers: [ - // App Bar - SliverAppBar( - expandedHeight: 120.0, - floating: false, - pinned: true, - elevation: 0, - backgroundColor: AppColor.primary, - flexibleSpace: - CustomAppBar(title: context.lang.purchase), + // Header (same style as Sales) + SliverToBoxAdapter( + child: PurchaseHeader( + state: state, + onDateRangeChanged: (startDate, endDate) { + context.read().add( + PurchasingAnalyticLoaderEvent.rangeDateChanged( + startDate, + endDate, + ), + ); + }, + ), ), - // Date Range + Outlet Picker + // Outlet Selector SliverToBoxAdapter( child: FadeTransition( opacity: _fadeAnimation, child: Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), - child: Column( - children: [ - DateRangePickerField( - maxDate: DateTime.now(), - startDate: state.dateFrom, - endDate: state.dateTo, - onChanged: (startDate, endDate) { - context - .read() - .add( - PurchasingAnalyticLoaderEvent - .rangeDateChanged( - startDate!, - endDate!, - ), - ); - }, - ), - const SpaceHeight(8), - PurchaseOutletSelectorField( - selectedOutletId: state.outletId, - outlets: outletListState.outlets, - isLoading: outletListState.isFetching, - onOutletChanged: (outletId) { - context - .read() - .add( - PurchasingAnalyticLoaderEvent - .outletChanged(outletId), - ); - }, - ), - ], + child: PurchaseOutletSelectorField( + selectedOutletId: state.outletId, + outlets: outletListState.outlets, + isLoading: outletListState.isFetching, + onOutletChanged: (outletId) { + context.read().add( + PurchasingAnalyticLoaderEvent.outletChanged( + outletId, + ), + ); + }, ), ), ), ), - const SliverToBoxAdapter(child: SpaceHeight(16)), - - // Summary Section + // Rincian Pembelian (same style as Sales) SliverToBoxAdapter( child: FadeTransition( opacity: _fadeAnimation, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.lang.summary, - style: AppStyle.xxl.copyWith( - fontWeight: FontWeight.bold, - color: AppColor.textPrimary, - ), - ), - const SpaceHeight(16), - state.isFetching - ? _buildSummaryShimmer() - : _buildSummaryCards(state), - ], - ), + padding: const EdgeInsets.all(16), + child: PurchaseRincianCard(state: state), ), ), ), - const SliverToBoxAdapter(child: SpaceHeight(24)), - - // Total Purchases Highlight Card - SliverToBoxAdapter( - child: FadeTransition( - opacity: _fadeAnimation, - child: state.isFetching - ? _buildHighlightShimmer() - : _buildTotalPurchasesCard(state), - ), - ), - - const SliverToBoxAdapter(child: SpaceHeight(24)), - // Daily Breakdown Header SliverToBoxAdapter( child: FadeTransition( @@ -278,211 +232,13 @@ class _PurchasePageState extends State ); } - // ─── Summary Cards ──────────────────────────────────────────────────────── - - Widget _buildSummaryCards(PurchasingAnalyticLoaderState state) { - final s = state.purchasing.summary; - return Column( - children: [ - Row( - children: [ - Expanded( - child: PurchaseStatCard( - title: 'Total Pembelian', - value: s.totalPurchases.currencyFormatRp, - icon: LineIcons.shoppingCart, - iconColor: AppColor.primary, - cardAnimation: _fadeAnimation, - ), - ), - const SpaceWidth(12), - Expanded( - child: PurchaseStatCard( - title: 'Total PO', - value: '${s.totalPurchaseOrders} PO', - icon: LineIcons.fileAlt, - iconColor: AppColor.info, - cardAnimation: _fadeAnimation, - ), - ), - ], - ), - const SpaceHeight(12), - Row( - children: [ - Expanded( - child: PurchaseStatCard( - title: 'Bahan Baku', - value: s.rawMaterialPurchases.currencyFormatRp, - icon: LineIcons.leaf, - iconColor: AppColor.secondary, - cardAnimation: _fadeAnimation, - ), - ), - const SpaceWidth(12), - Expanded( - child: PurchaseStatCard( - title: 'Pengeluaran', - value: s.expensePurchases.currencyFormatRp, - icon: LineIcons.receipt, - iconColor: AppColor.warning, - cardAnimation: _fadeAnimation, - ), - ), - ], - ), - const SpaceHeight(12), - Row( - children: [ - Expanded( - child: PurchaseStatCard( - title: 'Total Qty', - value: '${s.totalQuantity} pcs', - icon: LineIcons.boxes, - iconColor: AppColor.warning, - cardAnimation: _fadeAnimation, - ), - ), - const SpaceWidth(12), - Expanded( - child: PurchaseStatCard( - title: 'Rata-rata PO', - value: s.averagePurchaseOrderValue.round().currencyFormatRp, - icon: LineIcons.dollarSign, - iconColor: AppColor.secondaryDark, - cardAnimation: _fadeAnimation, - ), - ), - ], - ), - const SpaceHeight(12), - Row( - children: [ - Expanded( - child: PurchaseStatCard( - title: 'Item Bahan Baku', - value: '${s.totalIngredients} item', - icon: LineIcons.leaf, - iconColor: AppColor.secondaryDark, - cardAnimation: _fadeAnimation, - ), - ), - const SpaceWidth(12), - Expanded( - child: PurchaseStatCard( - title: 'Vendor', - value: '${s.totalVendors} vendor', - icon: LineIcons.truck, - iconColor: AppColor.primaryDark, - cardAnimation: _fadeAnimation, - ), - ), - ], - ), - ], - ); - } - - Widget _buildTotalPurchasesCard(PurchasingAnalyticLoaderState state) { - return TweenAnimationBuilder( - tween: Tween(begin: 0.0, end: 1.0), - duration: const Duration(milliseconds: 900), - curve: Curves.bounceOut, - builder: (context, value, _) { - return Transform.scale( - scale: value, - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 16), - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - gradient: const LinearGradient( - colors: AppColor.primaryGradient, - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: AppColor.primary.withOpacity(0.35), - blurRadius: 16, - offset: const Offset(0, 6), - ), - ], - ), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular(12), - ), - child: const Icon( - LineIcons.shoppingBag, - color: AppColor.textWhite, - size: 28, - ), - ), - const SpaceWidth(16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.lang.total_purchase, - style: TextStyle( - color: AppColor.textWhite.withOpacity(0.9), - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - const SpaceHeight(4), - Text( - state.purchasing.summary.totalPurchases - .currencyFormatRp, - style: const TextStyle( - color: AppColor.textWhite, - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - '${state.purchasing.summary.totalPurchaseOrders} PO', - style: const TextStyle( - color: AppColor.textWhite, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - Text( - 'purchase order', - style: TextStyle( - color: AppColor.textWhite.withOpacity(0.8), - fontSize: 12, - ), - ), - ], - ), - ], - ), - ), - ); - }, - ); - } - // ─── Lists ──────────────────────────────────────────────────────────────── Widget _buildDailyList(PurchasingAnalyticLoaderState state) { if (state.purchasing.data.isEmpty) { return SliverToBoxAdapter( - child: _buildEmptyState('Tidak ada data harian')); + child: _buildEmptyState('Tidak ada data harian'), + ); } return SliverList( delegate: SliverChildBuilderDelegate( @@ -499,7 +255,8 @@ class _PurchasePageState extends State Widget _buildIngredientList(PurchasingAnalyticLoaderState state) { if (state.purchasing.ingredientData.isEmpty) { return SliverToBoxAdapter( - child: _buildEmptyState('Tidak ada data bahan baku')); + child: _buildEmptyState('Tidak ada data bahan baku'), + ); } return SliverList( delegate: SliverChildBuilderDelegate( @@ -516,7 +273,8 @@ class _PurchasePageState extends State Widget _buildVendorList(PurchasingAnalyticLoaderState state) { if (state.purchasing.vendorData.isEmpty) { return SliverToBoxAdapter( - child: _buildEmptyState('Tidak ada data vendor')); + child: _buildEmptyState('Tidak ada data vendor'), + ); } return SliverList( delegate: SliverChildBuilderDelegate( @@ -552,51 +310,6 @@ class _PurchasePageState extends State // ─── Shimmer Loaders ────────────────────────────────────────────────────── - Widget _buildSummaryShimmer() { - return Column( - children: [ - Row( - children: [ - Expanded(child: _shimmerCard(height: 100)), - const SpaceWidth(12), - Expanded(child: _shimmerCard(height: 100)), - ], - ), - const SpaceHeight(12), - Row( - children: [ - Expanded(child: _shimmerCard(height: 100)), - const SpaceWidth(12), - Expanded(child: _shimmerCard(height: 100)), - ], - ), - const SpaceHeight(12), - Row( - children: [ - Expanded(child: _shimmerCard(height: 100)), - const SpaceWidth(12), - Expanded(child: _shimmerCard(height: 100)), - ], - ), - const SpaceHeight(12), - Row( - children: [ - Expanded(child: _shimmerCard(height: 100)), - const SpaceWidth(12), - Expanded(child: _shimmerCard(height: 100)), - ], - ), - ], - ); - } - - Widget _buildHighlightShimmer() { - return Container( - margin: const EdgeInsets.symmetric(horizontal: 16), - child: _shimmerCard(height: 88), - ); - } - Widget _buildListShimmer() { return SliverList( delegate: SliverChildBuilderDelegate( diff --git a/lib/presentation/pages/purchase/widgets/purchase_header.dart b/lib/presentation/pages/purchase/widgets/purchase_header.dart new file mode 100644 index 0000000..9ec8170 --- /dev/null +++ b/lib/presentation/pages/purchase/widgets/purchase_header.dart @@ -0,0 +1,318 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + +import '../../../../application/analytic/purchasing_analytic_loader/purchasing_analytic_loader_bloc.dart'; +import '../../../../common/extension/extension.dart'; +import '../../../../common/painter/wave_painter.dart'; +import '../../../../common/theme/theme.dart'; +import '../../../components/bottom_sheet/date_range_bottom_sheet.dart'; +import '../../../components/spacer/spacer.dart'; + +class PurchaseHeader extends StatelessWidget { + final PurchasingAnalyticLoaderState state; + final void Function(DateTime startDate, DateTime endDate)? onDateRangeChanged; + + const PurchaseHeader({ + super.key, + required this.state, + this.onDateRangeChanged, + }); + + @override + Widget build(BuildContext context) { + final dateLabel = _formatDateRange(state.dateFrom, state.dateTo, context); + final outletLabel = state.purchasing.outletName.isNotEmpty + ? state.purchasing.outletName + : 'Semua Outlet'; + + return Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(24), + bottomRight: Radius.circular(24), + ), + ), + child: Stack( + children: [ + // Decorative circles + Positioned( + top: -20, + right: -30, + child: Container( + width: 120, + height: 120, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.textWhite.withOpacity(0.08), + ), + ), + ), + Positioned( + top: 30, + right: 20, + child: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.textWhite.withOpacity(0.05), + ), + ), + ), + Positioned( + top: 10, + left: -20, + child: Container( + width: 80, + height: 80, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.textWhite.withOpacity(0.04), + ), + ), + ), + + // Wave pattern + Positioned.fill( + child: CustomPaint( + painter: WavePainter( + animation: 0.0, + color: AppColor.textWhite.withOpacity(0.1), + ), + ), + ), + + // Content + SafeArea( + bottom: false, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Back button + Title row + Calendar button + Row( + children: [ + GestureDetector( + onTap: () => context.router.maybePop(), + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: AppColor.textWhite.withOpacity(0.15), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.chevron_left_rounded, + color: AppColor.textWhite, + size: 24, + ), + ), + ), + const SpaceWidth(12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.lang.purchase, + style: AppStyle.xl.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w700, + fontSize: 20, + ), + ), + const SizedBox(height: 2), + Text( + '$dateLabel · $outletLabel', + style: AppStyle.sm.copyWith( + color: AppColor.textWhite.withOpacity(0.75), + fontWeight: FontWeight.w400, + fontSize: 12, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + const SpaceWidth(8), + // Date filter button + GestureDetector( + onTap: () => _showDatePicker(context), + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: AppColor.textWhite.withOpacity(0.15), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.calendar_month_rounded, + color: AppColor.textWhite, + size: 20, + ), + ), + ), + ], + ), + + const SpaceHeight(24), + + // Total Pembelian label + Text( + context.lang.total_purchase, + style: AppStyle.sm.copyWith( + color: AppColor.textWhite.withOpacity(0.75), + fontWeight: FontWeight.w400, + fontSize: 13, + ), + ), + + const SpaceHeight(4), + + // Big value + state.isFetching + ? _buildHeaderValueShimmer() + : Text( + state + .purchasing + .summary + .totalPurchases + .currencyFormatRp, + style: AppStyle.h1.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w900, + fontSize: 32, + ), + ), + + const SpaceHeight(16), + + // Chips row + state.isFetching + ? _buildHeaderChipsShimmer() + : _buildHeaderChips(context), + ], + ), + ), + ), + ], + ), + ); + } + + void _showDatePicker(BuildContext context) { + DateRangePickerBottomSheet.show( + context: context, + primaryColor: AppColor.primary, + initialStartDate: state.dateFrom, + initialEndDate: state.dateTo, + maxDate: DateTime.now(), + onChanged: (startDate, endDate) { + if (startDate != null && endDate != null) { + onDateRangeChanged?.call(startDate, endDate); + } + }, + ); + } + + Widget _buildHeaderValueShimmer() { + return Shimmer.fromColors( + baseColor: AppColor.textWhite.withOpacity(0.3), + highlightColor: AppColor.textWhite.withOpacity(0.6), + child: Container( + width: 200, + height: 36, + decoration: BoxDecoration( + color: AppColor.textWhite.withOpacity(0.3), + borderRadius: BorderRadius.circular(8), + ), + ), + ); + } + + Widget _buildHeaderChipsShimmer() { + return Row( + children: List.generate( + 3, + (index) => Padding( + padding: const EdgeInsets.only(right: 8), + child: Shimmer.fromColors( + baseColor: AppColor.textWhite.withOpacity(0.15), + highlightColor: AppColor.textWhite.withOpacity(0.3), + child: Container( + width: 90, + height: 32, + decoration: BoxDecoration( + color: AppColor.textWhite.withOpacity(0.15), + borderRadius: BorderRadius.circular(20), + ), + ), + ), + ), + ), + ); + } + + Widget _buildHeaderChips(BuildContext context) { + final summary = state.purchasing.summary; + + return Wrap( + spacing: 8, + runSpacing: 8, + children: [ + _buildChip('${summary.totalPurchaseOrders} PO'), + _buildChip('${summary.totalQuantity} pcs'), + _buildChip('${summary.totalVendors} vendor'), + ], + ); + } + + Widget _buildChip(String label) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + decoration: BoxDecoration( + color: AppColor.textWhite.withOpacity(0.15), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: AppColor.textWhite.withOpacity(0.25)), + ), + child: Text( + label, + style: AppStyle.sm.copyWith( + color: AppColor.textWhite, + fontWeight: FontWeight.w600, + fontSize: 12, + ), + ), + ); + } + + String _formatDateRange(DateTime from, DateTime to, BuildContext context) { + const months = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'Mei', + 'Jun', + 'Jul', + 'Agu', + 'Sep', + 'Okt', + 'Nov', + 'Des', + ]; + + if (from.year == to.year && from.month == to.month && from.day == to.day) { + return '${context.lang.report} ${from.day} ${months[from.month - 1]} ${from.year}'; + } + return '${context.lang.report} ${from.day} ${months[from.month - 1]} - ${to.day} ${months[to.month - 1]} ${to.year}'; + } +} diff --git a/lib/presentation/pages/purchase/widgets/purchase_rincian_card.dart b/lib/presentation/pages/purchase/widgets/purchase_rincian_card.dart new file mode 100644 index 0000000..8f30fe7 --- /dev/null +++ b/lib/presentation/pages/purchase/widgets/purchase_rincian_card.dart @@ -0,0 +1,167 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + +import '../../../../application/analytic/purchasing_analytic_loader/purchasing_analytic_loader_bloc.dart'; +import '../../../../common/extension/extension.dart'; +import '../../../../common/theme/theme.dart'; +import '../../../components/spacer/spacer.dart'; + +class PurchaseRincianCard extends StatelessWidget { + final PurchasingAnalyticLoaderState state; + + const PurchaseRincianCard({super.key, required this.state}); + + @override + Widget build(BuildContext context) { + if (state.isFetching) return _buildShimmer(); + + final summary = state.purchasing.summary; + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.lang.daily_breakdown, + style: AppStyle.xl.copyWith( + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + fontSize: 18, + ), + ), + const SpaceHeight(16), + _buildRow('Total Pembelian', summary.totalPurchases.currencyFormatRp), + _buildDivider(), + _buildRow( + 'Bahan Baku', + summary.rawMaterialPurchases.currencyFormatRp, + ), + _buildDivider(), + _buildRow('Pengeluaran', summary.expensePurchases.currencyFormatRp), + _buildDivider(), + _buildRow('Total PO', '${summary.totalPurchaseOrders} PO'), + _buildDivider(), + _buildRow('Total Qty', '${summary.totalQuantity} pcs'), + _buildDivider(), + _buildRow( + 'Rata-rata PO', + summary.averagePurchaseOrderValue.round().currencyFormatRp, + ), + _buildDivider(), + _buildRow('Item Bahan Baku', '${summary.totalIngredients} item'), + _buildDivider(), + _buildRow('Vendor', '${summary.totalVendors} vendor'), + ], + ), + ); + } + + Widget _buildRow(String label, String value, {bool isBold = false}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 14), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: AppStyle.md.copyWith( + color: isBold ? AppColor.textPrimary : AppColor.textSecondary, + fontWeight: isBold ? FontWeight.w700 : FontWeight.w500, + fontSize: 14, + ), + ), + Text( + value, + style: AppStyle.md.copyWith( + color: AppColor.textPrimary, + fontWeight: isBold ? FontWeight.w700 : FontWeight.w600, + fontSize: 14, + ), + ), + ], + ), + ); + } + + Widget _buildDivider() { + return Divider( + height: 1, + thickness: 1, + color: AppColor.border.withOpacity(0.5), + ); + } + + Widget _buildShimmer() { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 140, + height: 20, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(4), + ), + ), + const SpaceHeight(20), + ...List.generate( + 8, + (index) => Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: 100, + height: 14, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(4), + ), + ), + Container( + width: 80, + height: 14, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(4), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +}