feat: update profit shareing
Build & Deploy iOS to TestFlight / build-and-deploy (push) Canceled after 0s

This commit is contained in:
efrilm
2026-10-04 22:31:01 +07:00
parent 288d0d75a0
commit 243b7e02aa
23 changed files with 1349 additions and 451 deletions
@@ -9,8 +9,10 @@ import '../../../common/theme/theme.dart';
import '../../../domain/analytic/analytic.dart';
import '../../../injection.dart';
import '../../components/spacer/spacer.dart';
import 'widgets/period_label.dart';
import 'widgets/profit_sharing_allocation.dart';
import 'widgets/profit_sharing_detail_header.dart';
import 'widgets/profit_sharing_sdl.dart';
import 'widgets/profit_sharing_status.dart';
import 'widgets/profit_sharing_subcategories.dart';
@@ -48,75 +50,116 @@ class ProfitSharingDetailPage extends StatelessWidget
return Scaffold(
backgroundColor: AppColor.background,
body:
BlocBuilder<
BlocListener<
ProfitSharingDetailLoaderBloc,
ProfitSharingDetailLoaderState
>(
builder: (context, state) {
return RefreshIndicator(
backgroundColor: AppColor.white,
color: AppColor.primary,
onRefresh: () async {
context.read<ProfitSharingDetailLoaderBloc>().add(
_fetchEvent(),
);
await context
.read<ProfitSharingDetailLoaderBloc>()
.stream
.firstWhere((s) => !s.isFetching);
},
child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: ProfitSharingDetailHeader(
state: state,
fallbackTitle: parentCategoryName,
),
),
const SliverToBoxAdapter(child: SpaceHeight(16)),
if (state.isFetching)
SliverToBoxAdapter(child: _buildShimmer())
else ...[
SliverToBoxAdapter(
child: _SummaryCard(summary: state.detail.summary),
),
const SliverToBoxAdapter(child: SpaceHeight(16)),
SliverToBoxAdapter(
child: ProfitSharingAllocation(
budget: state.detail.budget,
),
),
const SliverToBoxAdapter(child: SpaceHeight(16)),
SliverToBoxAdapter(
child: ProfitSharingSubCategories(
categories: state.detail.categories,
expandedCategoryId: state.expandedCategoryId,
onToggle: (categoryId) =>
context.read<ProfitSharingDetailLoaderBloc>().add(
ProfitSharingDetailLoaderEvent.expandedCategoryChanged(
categoryId,
),
),
),
),
],
const SliverToBoxAdapter(child: SizedBox(height: 100)),
],
),
listenWhen: (previous, current) =>
previous.failureOption != current.failureOption,
listener: (context, state) {
final isNotTeam = state.failureOption.fold(
() => false,
(f) => f.maybeMap(notTeam: (_) => true, orElse: () => false),
);
// Kategori induk dimatikan dari team setelah list dimuat:
// kembali ke list dengan hasil `true` supaya list memuat ulang.
if (isNotTeam) context.router.maybePop(true);
},
child: _buildContent(),
),
);
}
Widget _buildContent() {
return BlocBuilder<
ProfitSharingDetailLoaderBloc,
ProfitSharingDetailLoaderState
>(
builder: (context, state) {
return RefreshIndicator(
backgroundColor: AppColor.white,
color: AppColor.primary,
onRefresh: () async {
context.read<ProfitSharingDetailLoaderBloc>().add(_fetchEvent());
await context
.read<ProfitSharingDetailLoaderBloc>()
.stream
.firstWhere((s) => !s.isFetching);
},
child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: ProfitSharingDetailHeader(
state: state,
fallbackTitle: parentCategoryName,
),
),
const SliverToBoxAdapter(child: SpaceHeight(16)),
if (state.isFetching)
SliverToBoxAdapter(child: _buildShimmer())
else ...[
SliverToBoxAdapter(
child: _SummaryCard(summary: state.detail.summary),
),
const SliverToBoxAdapter(child: SpaceHeight(16)),
SliverToBoxAdapter(
child: _buildSdlSection(context, state.detail),
),
const SliverToBoxAdapter(child: SpaceHeight(16)),
SliverToBoxAdapter(
child: ProfitSharingAllocation(budget: state.detail.budget),
),
const SliverToBoxAdapter(child: SpaceHeight(16)),
SliverToBoxAdapter(
child: ProfitSharingSubCategories(
categories: state.detail.categories,
expandedCategoryId: state.expandedCategoryId,
onToggle: (categoryId) =>
context.read<ProfitSharingDetailLoaderBloc>().add(
ProfitSharingDetailLoaderEvent.expandedCategoryChanged(
categoryId,
),
),
),
),
],
const SliverToBoxAdapter(child: SizedBox(height: 100)),
],
),
);
},
);
}
Widget _buildSdlSection(BuildContext context, ProfitSharingDetail detail) {
final summary = detail.summary;
return ProfitSharingSdlSection(
sdl: summary.sdl,
revenue: summary.totalRevenue,
percent: summary.ownerFeePercent,
breakdownTitle: context.lang.sdl_per_week,
items: [
for (final period in detail.budget.weekly.where((e) => e.hasRevenue))
ProfitSharingSdlItem(
label: formatPeriodLabel(period.periodStart, period.periodEnd),
sdl: period.sdl,
revenue: period.revenue,
),
],
);
}
Widget _buildShimmer() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
@@ -187,6 +230,7 @@ class _SummaryCard extends StatelessWidget {
icon: Icons.category_rounded,
label: context.lang.sub_category,
value: '${summary.categoryCount}',
color: ProfitSharingColor.subCategory,
),
),
const SpaceWidth(10),
@@ -195,7 +239,7 @@ class _SummaryCard extends StatelessWidget {
icon: Icons.inventory_2_rounded,
label: context.lang.products,
value: '${summary.productCount}',
color: AppColor.info,
color: ProfitSharingColor.product,
),
),
const SpaceWidth(10),
@@ -204,20 +248,13 @@ class _SummaryCard extends StatelessWidget {
icon: Icons.receipt_long_rounded,
label: context.lang.orders,
value: summary.orderCount.thousandFormat,
color: AppColor.warning,
color: ProfitSharingColor.order,
),
),
],
),
const SpaceHeight(12),
ProfitSharingSdlStrip(
amount: summary.sdl,
feePercent: summary.ownerFeePercent,
),
const SpaceHeight(12),
const SpaceHeight(16),
Row(
children: [