This commit is contained in:
@@ -16,6 +16,7 @@ import 'widgets/feature.dart';
|
||||
import 'widgets/header.dart';
|
||||
import 'widgets/home_top_products.dart';
|
||||
import 'widgets/home_warnings.dart';
|
||||
import 'widgets/profit_sharing_banner.dart';
|
||||
import 'widgets/stats.dart';
|
||||
|
||||
@RoutePage()
|
||||
@@ -201,6 +202,7 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
HomeFeature(),
|
||||
HomeProfitSharingBanner(),
|
||||
HomeWarnings(),
|
||||
HomeStats(overview: state.dashboard.overview),
|
||||
HomeTopProducts(
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hugeicons/hugeicons.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
import '../../../router/app_router.gr.dart';
|
||||
|
||||
/// Pintu masuk ke halaman bagi hasil dari beranda.
|
||||
class HomeProfitSharingBanner extends StatelessWidget {
|
||||
const HomeProfitSharingBanner({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(AppValue.radius),
|
||||
onTap: () => context.router.push(const ProfitSharingRoute()),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: AppColor.primaryGradient,
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(AppValue.radius),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.primary.withOpacity(0.25),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.18),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const HugeIcon(
|
||||
icon: HugeIcons.strokeRoundedPieChart,
|
||||
color: AppColor.textWhite,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.profit_sharing,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
context.lang.profit_sharing_desc,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.8),
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
const Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: AppColor.textWhite,
|
||||
size: 24,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
import '../../../application/analytic/profit_sharing_detail_loader/profit_sharing_detail_loader_bloc.dart';
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../../domain/analytic/analytic.dart';
|
||||
import '../../../injection.dart';
|
||||
import '../../components/spacer/spacer.dart';
|
||||
import 'widgets/profit_sharing_allocation.dart';
|
||||
import 'widgets/profit_sharing_detail_header.dart';
|
||||
import 'widgets/profit_sharing_status.dart';
|
||||
import 'widgets/profit_sharing_subcategories.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ProfitSharingDetailPage extends StatelessWidget
|
||||
implements AutoRouteWrapper {
|
||||
final String parentCategoryId;
|
||||
final String parentCategoryName;
|
||||
final DateTime dateFrom;
|
||||
final DateTime dateTo;
|
||||
|
||||
const ProfitSharingDetailPage({
|
||||
super.key,
|
||||
required this.parentCategoryId,
|
||||
required this.parentCategoryName,
|
||||
required this.dateFrom,
|
||||
required this.dateTo,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(BuildContext context) => BlocProvider(
|
||||
create: (_) => getIt<ProfitSharingDetailLoaderBloc>()..add(_fetchEvent()),
|
||||
child: this,
|
||||
);
|
||||
|
||||
ProfitSharingDetailLoaderEvent _fetchEvent() =>
|
||||
ProfitSharingDetailLoaderEvent.fetched(
|
||||
parentCategoryId: parentCategoryId,
|
||||
dateFrom: dateFrom,
|
||||
dateTo: dateTo,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColor.background,
|
||||
body:
|
||||
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: 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 _buildShimmer() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
_shimmerBox(height: 160),
|
||||
const SpaceHeight(16),
|
||||
_shimmerBox(height: 240),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _shimmerBox({required double height}) {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: Colors.grey[300]!,
|
||||
highlightColor: Colors.grey[100]!,
|
||||
child: Container(
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SummaryCard extends StatelessWidget {
|
||||
final ProfitSharingCategory summary;
|
||||
|
||||
const _SummaryCard({required this.summary});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 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: [
|
||||
Text(
|
||||
context.lang.summary,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.sub_category,
|
||||
value: '${summary.categoryCount}',
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.products,
|
||||
value: '${summary.productCount}',
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.orders,
|
||||
value: summary.orderCount.thousandFormat,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 12),
|
||||
child: Divider(height: 1, color: AppColor.borderLight),
|
||||
),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.std_hpp,
|
||||
value: formatPercent(summary.standardHppPercentage),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.real_hpp,
|
||||
value: formatPercent(summary.realHppPercentage),
|
||||
valueColor: statusColor(summary.realHppPercentage),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.gross_profit,
|
||||
value: summary.grossProfit.currencyFormatRp,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
import '../../../application/analytic/profit_sharing_loader/profit_sharing_loader_bloc.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../../injection.dart';
|
||||
import '../../components/field/date_range_picker_field.dart';
|
||||
import '../../components/spacer/spacer.dart';
|
||||
import '../../router/app_router.gr.dart';
|
||||
import 'widgets/profit_sharing_allocation.dart';
|
||||
import 'widgets/profit_sharing_categories.dart';
|
||||
import 'widgets/profit_sharing_header.dart';
|
||||
import 'widgets/profit_sharing_periods.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ProfitSharingPage extends StatefulWidget implements AutoRouteWrapper {
|
||||
const ProfitSharingPage({super.key});
|
||||
|
||||
@override
|
||||
State<ProfitSharingPage> createState() => _ProfitSharingPageState();
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(BuildContext context) => BlocProvider(
|
||||
create: (_) =>
|
||||
getIt<ProfitSharingLoaderBloc>()
|
||||
..add(ProfitSharingLoaderEvent.fetched()),
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
|
||||
class _ProfitSharingPageState extends State<ProfitSharingPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _fadeController;
|
||||
late Animation<double> _fadeAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_fadeController = AnimationController(
|
||||
duration: const Duration(milliseconds: 1000),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_fadeAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeIn));
|
||||
|
||||
_fadeController.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_fadeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColor.background,
|
||||
body: BlocListener<ProfitSharingLoaderBloc, ProfitSharingLoaderState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.dateFrom != current.dateFrom ||
|
||||
previous.dateTo != current.dateTo,
|
||||
listener: (context, state) {
|
||||
context.read<ProfitSharingLoaderBloc>().add(
|
||||
const ProfitSharingLoaderEvent.fetched(),
|
||||
);
|
||||
},
|
||||
child: BlocBuilder<ProfitSharingLoaderBloc, ProfitSharingLoaderState>(
|
||||
builder: (context, state) {
|
||||
return RefreshIndicator(
|
||||
backgroundColor: AppColor.white,
|
||||
color: AppColor.primary,
|
||||
onRefresh: () async {
|
||||
context.read<ProfitSharingLoaderBloc>().add(
|
||||
const ProfitSharingLoaderEvent.fetched(),
|
||||
);
|
||||
await context.read<ProfitSharingLoaderBloc>().stream.firstWhere(
|
||||
(s) => !s.isFetching,
|
||||
);
|
||||
},
|
||||
child: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: ProfitSharingHeader(state: state),
|
||||
),
|
||||
),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: DateRangePickerField(
|
||||
startDate: state.dateFrom,
|
||||
endDate: state.dateTo,
|
||||
onChanged: (startDate, endDate) {
|
||||
if (startDate == null || endDate == null) return;
|
||||
context.read<ProfitSharingLoaderBloc>().add(
|
||||
ProfitSharingLoaderEvent.rangeDateChanged(
|
||||
startDate,
|
||||
endDate,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
if (state.isFetching)
|
||||
SliverToBoxAdapter(child: _buildShimmer())
|
||||
else ...[
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: ProfitSharingCategories(
|
||||
categories: state.profitSharing.categories,
|
||||
onCategoryTap: (category) => context.router.push(
|
||||
ProfitSharingDetailRoute(
|
||||
parentCategoryId: category.parentCategoryId,
|
||||
parentCategoryName: category.parentCategoryName,
|
||||
dateFrom: state.dateFrom,
|
||||
dateTo: state.dateTo,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: ProfitSharingAllocation(
|
||||
budget: state.profitSharing.budget,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: ProfitSharingPeriods(
|
||||
state: state,
|
||||
onPeriodTypeChanged: (type) {
|
||||
context.read<ProfitSharingLoaderBloc>().add(
|
||||
ProfitSharingLoaderEvent.periodTypeChanged(type),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const SliverToBoxAdapter(child: SizedBox(height: 100)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShimmer() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
_shimmerBox(height: 240),
|
||||
const SpaceHeight(16),
|
||||
_shimmerBox(height: 300),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _shimmerBox({required double height}) {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: Colors.grey[300]!,
|
||||
highlightColor: Colors.grey[100]!,
|
||||
child: Container(
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
/// Label rentang periode ringkas, contoh: "27 Jul - 2 Agu 2026".
|
||||
String formatPeriodLabel(DateTime from, DateTime to) {
|
||||
final dayMonth = DateFormat('d MMM', 'id_ID');
|
||||
final dayMonthYear = DateFormat('d MMM yyyy', 'id_ID');
|
||||
|
||||
if (from.year == to.year && from.month == to.month && from.day == to.day) {
|
||||
return dayMonthYear.format(from);
|
||||
}
|
||||
if (from.year == to.year) {
|
||||
return '${dayMonth.format(from)} - ${dayMonthYear.format(to)}';
|
||||
}
|
||||
return '${dayMonthYear.format(from)} - ${dayMonthYear.format(to)}';
|
||||
}
|
||||
|
||||
/// Label bulan dari format API "2026-08" menjadi "Agustus 2026".
|
||||
String formatMonthLabel(String month, DateTime fallback) {
|
||||
final parts = month.split('-');
|
||||
if (parts.length == 2) {
|
||||
final year = int.tryParse(parts[0]);
|
||||
final monthNumber = int.tryParse(parts[1]);
|
||||
if (year != null && monthNumber != null) {
|
||||
return DateFormat(
|
||||
'MMMM yyyy',
|
||||
'id_ID',
|
||||
).format(DateTime(year, monthNumber));
|
||||
}
|
||||
}
|
||||
return DateFormat('MMMM yyyy', 'id_ID').format(fallback);
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
|
||||
/// Warna tetap untuk tiap pos bagi hasil supaya konsisten di semua bagian.
|
||||
class ProfitSharingColor {
|
||||
static const Color purchase = Color(0xFF2196F3);
|
||||
static const Color owner = Color(0xFF4CAF50);
|
||||
static const Color team = Color(0xFFFF9800);
|
||||
}
|
||||
|
||||
class ProfitSharingAllocation extends StatelessWidget {
|
||||
final ProfitSharingBudget budget;
|
||||
|
||||
const ProfitSharingAllocation({super.key, required this.budget});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final total = budget.total;
|
||||
final percentages = budget.percentages;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 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: [
|
||||
Text(
|
||||
context.lang.profit_sharing_allocation,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
ProfitSharingBar(
|
||||
purchase: percentages.purchase,
|
||||
owner: percentages.owner,
|
||||
team: percentages.team,
|
||||
),
|
||||
|
||||
const SpaceHeight(20),
|
||||
|
||||
_row(
|
||||
context: context,
|
||||
color: ProfitSharingColor.purchase,
|
||||
label: context.lang.share_purchase,
|
||||
percentage: percentages.purchase,
|
||||
amount: total.limitPurchase,
|
||||
),
|
||||
const Divider(height: 24, color: AppColor.borderLight),
|
||||
_row(
|
||||
context: context,
|
||||
color: ProfitSharingColor.owner,
|
||||
label: context.lang.share_owner,
|
||||
percentage: percentages.owner,
|
||||
amount: total.limitOwner,
|
||||
),
|
||||
const Divider(height: 24, color: AppColor.borderLight),
|
||||
_row(
|
||||
context: context,
|
||||
color: ProfitSharingColor.team,
|
||||
label: context.lang.share_team,
|
||||
percentage: percentages.team,
|
||||
amount: total.limitTeam,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _row({
|
||||
required BuildContext context,
|
||||
required Color color,
|
||||
required String label,
|
||||
required double percentage,
|
||||
required int amount,
|
||||
}) {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
),
|
||||
const SpaceWidth(10),
|
||||
Text(
|
||||
label,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.12),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
'${percentage.toStringAsFixed(percentage % 1 == 0 ? 0 : 1)}%',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Flexible(
|
||||
child: Text(
|
||||
amount.currencyFormatRp,
|
||||
textAlign: TextAlign.right,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Bar proporsi belanja / owner / tim.
|
||||
class ProfitSharingBar extends StatelessWidget {
|
||||
final double purchase;
|
||||
final double owner;
|
||||
final double team;
|
||||
final double height;
|
||||
|
||||
const ProfitSharingBar({
|
||||
super.key,
|
||||
required this.purchase,
|
||||
required this.owner,
|
||||
required this.team,
|
||||
this.height = 12,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final total = purchase + owner + team;
|
||||
if (total <= 0) {
|
||||
return Container(
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.borderLight,
|
||||
borderRadius: BorderRadius.circular(height),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(height),
|
||||
child: SizedBox(
|
||||
height: height,
|
||||
child: Row(
|
||||
children: [
|
||||
_segment(purchase, ProfitSharingColor.purchase),
|
||||
_segment(owner, ProfitSharingColor.owner),
|
||||
_segment(team, ProfitSharingColor.team),
|
||||
].whereType<Widget>().toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Segmen dilewati saat porsinya 0 supaya Expanded tidak dipakai dengan flex 0.
|
||||
Widget? _segment(double value, Color color) {
|
||||
final flex = (value * 100).round();
|
||||
if (flex <= 0) return null;
|
||||
return Expanded(
|
||||
flex: flex,
|
||||
child: Container(color: color),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
import 'profit_sharing_status.dart';
|
||||
|
||||
/// Ringkasan omzet & HPP per kategori induk (parent category).
|
||||
/// Tiap kartu bisa diketuk untuk melihat rincian sub kategori & produknya.
|
||||
class ProfitSharingCategories extends StatelessWidget {
|
||||
final List<ProfitSharingCategory> categories;
|
||||
final ValueChanged<ProfitSharingCategory> onCategoryTap;
|
||||
|
||||
const ProfitSharingCategories({
|
||||
super.key,
|
||||
required this.categories,
|
||||
required this.onCategoryTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sorted = [...categories]
|
||||
..sort((a, b) => b.totalRevenue.compareTo(a.totalRevenue));
|
||||
|
||||
final totalRevenue = sorted.fold<int>(0, (sum, e) => sum + e.totalRevenue);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 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: [
|
||||
Text(
|
||||
context.lang.parent_category_summary,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
context.lang.tap_row_for_detail,
|
||||
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
if (sorted.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.lang.no_data_yet,
|
||||
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
),
|
||||
)
|
||||
else ...[
|
||||
...sorted.map(
|
||||
(category) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: _CategoryCard(
|
||||
category: category,
|
||||
share: totalRevenue == 0
|
||||
? 0
|
||||
: category.totalRevenue / totalRevenue,
|
||||
onTap: () => onCategoryTap(category),
|
||||
),
|
||||
),
|
||||
),
|
||||
_TotalCard(categories: sorted),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CategoryCard extends StatelessWidget {
|
||||
final ProfitSharingCategory category;
|
||||
final double share;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _CategoryCard({
|
||||
required this.category,
|
||||
required this.share,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.background,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColor.borderLight),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
category.parentCategoryName,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
ProfitSharingStatusBadge(
|
||||
realHppPercentage: category.realHppPercentage,
|
||||
),
|
||||
const Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: AppColor.textSecondary,
|
||||
size: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(8),
|
||||
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
category.totalRevenue.currencyFormatRp,
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
formatPercent(share * 100),
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(8),
|
||||
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: LinearProgressIndicator(
|
||||
value: share.clamp(0.0, 1.0),
|
||||
minHeight: 6,
|
||||
backgroundColor: AppColor.borderLight,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||
AppColor.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(12),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.qty,
|
||||
value: category.totalQuantity.thousandFormat,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.products,
|
||||
value: '${category.productCount}',
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.sub_category,
|
||||
value: '${category.categoryCount}',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 10),
|
||||
child: Divider(height: 1, color: AppColor.border),
|
||||
),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.std_hpp,
|
||||
value: formatPercent(category.standardHppPercentage),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.real_hpp,
|
||||
value: formatPercent(category.realHppPercentage),
|
||||
valueColor: statusColor(category.realHppPercentage),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TotalCard extends StatelessWidget {
|
||||
final List<ProfitSharingCategory> categories;
|
||||
|
||||
const _TotalCard({required this.categories});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final totalQuantity = categories.fold<int>(
|
||||
0,
|
||||
(sum, e) => sum + e.totalQuantity,
|
||||
);
|
||||
final totalRevenue = categories.fold<int>(
|
||||
0,
|
||||
(sum, e) => sum + e.totalRevenue,
|
||||
);
|
||||
final totalStandardHpp = categories.fold<int>(
|
||||
0,
|
||||
(sum, e) => sum + e.totalStandardHpp,
|
||||
);
|
||||
final totalFifoHpp = categories.fold<int>(
|
||||
0,
|
||||
(sum, e) => sum + e.totalFifoHpp,
|
||||
);
|
||||
|
||||
double percentOf(int hpp) =>
|
||||
totalRevenue == 0 ? 0 : (hpp / totalRevenue) * 100;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.06),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
context.lang.grand_total,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
totalRevenue.currencyFormatRp,
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(12),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.qty,
|
||||
value: totalQuantity.thousandFormat,
|
||||
isBold: true,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.std_hpp,
|
||||
value: formatPercent(percentOf(totalStandardHpp)),
|
||||
isBold: true,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.real_hpp,
|
||||
value: formatPercent(percentOf(totalFifoHpp)),
|
||||
isBold: true,
|
||||
valueColor: statusColor(percentOf(totalFifoHpp)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
import '../../../../application/analytic/profit_sharing_detail_loader/profit_sharing_detail_loader_bloc.dart';
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
import 'period_label.dart';
|
||||
import 'profit_sharing_status.dart';
|
||||
|
||||
class ProfitSharingDetailHeader extends StatelessWidget {
|
||||
final ProfitSharingDetailLoaderState state;
|
||||
final String fallbackTitle;
|
||||
|
||||
const ProfitSharingDetailHeader({
|
||||
super.key,
|
||||
required this.state,
|
||||
required this.fallbackTitle,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final detail = state.detail;
|
||||
final summary = detail.summary;
|
||||
final title = detail.parentCategoryName.isNotEmpty
|
||||
? detail.parentCategoryName
|
||||
: fallbackTitle;
|
||||
|
||||
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: SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
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(
|
||||
title,
|
||||
style: AppStyle.xl.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 20,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
formatPeriodLabel(state.dateFrom, state.dateTo),
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.75),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(24),
|
||||
|
||||
if (state.isFetching)
|
||||
_shimmerBox(width: 220, height: 36)
|
||||
else
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
summary.totalRevenue.currencyFormatRp,
|
||||
style: AppStyle.h1.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 30,
|
||||
),
|
||||
),
|
||||
),
|
||||
ProfitSharingStatusBadge(
|
||||
realHppPercentage: summary.realHppPercentage,
|
||||
onDarkBackground: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(4),
|
||||
Text(
|
||||
context.lang.total_revenue,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.75),
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
if (state.isFetching)
|
||||
Row(
|
||||
children: [
|
||||
_shimmerBox(width: 120, height: 32, radius: 20),
|
||||
const SpaceWidth(8),
|
||||
_shimmerBox(width: 120, height: 32, radius: 20),
|
||||
],
|
||||
)
|
||||
else
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_chip(
|
||||
'${context.lang.qty} ${summary.totalQuantity.thousandFormat}',
|
||||
),
|
||||
_chip(context.lang.order_count_label(summary.orderCount)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _chip(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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _shimmerBox({
|
||||
required double width,
|
||||
required double height,
|
||||
double radius = 8,
|
||||
}) {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: AppColor.textWhite.withOpacity(0.3),
|
||||
highlightColor: AppColor.textWhite.withOpacity(0.6),
|
||||
child: Container(
|
||||
width: width,
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.3),
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
import '../../../../application/analytic/profit_sharing_loader/profit_sharing_loader_bloc.dart';
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
|
||||
class ProfitSharingHeader extends StatelessWidget {
|
||||
final ProfitSharingLoaderState state;
|
||||
|
||||
const ProfitSharingHeader({super.key, required this.state});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final budget = state.profitSharing.budget;
|
||||
final outletLabel = state.profitSharing.outletName.isNotEmpty
|
||||
? state.profitSharing.outletName
|
||||
: context.lang.all_outlets;
|
||||
|
||||
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: SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
if (context.router.canPop()) ...[
|
||||
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_sharing,
|
||||
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),
|
||||
fontSize: 12,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(24),
|
||||
|
||||
state.isFetching
|
||||
? _shimmerBox(width: 220, height: 36)
|
||||
: Text(
|
||||
budget.total.revenue.currencyFormatRp,
|
||||
style: AppStyle.h1.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 32,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(4),
|
||||
Text(
|
||||
context.lang.total_revenue,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.75),
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
state.isFetching
|
||||
? _shimmerBox(width: 140, height: 32, radius: 20)
|
||||
: _chip(
|
||||
context.lang.order_count_label(budget.total.orderCount),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _chip(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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _shimmerBox({
|
||||
required double width,
|
||||
required double height,
|
||||
double radius = 8,
|
||||
}) {
|
||||
return Shimmer.fromColors(
|
||||
baseColor: AppColor.textWhite.withOpacity(0.3),
|
||||
highlightColor: AppColor.textWhite.withOpacity(0.6),
|
||||
child: Container(
|
||||
width: width,
|
||||
height: height,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.3),
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../application/analytic/profit_sharing_loader/profit_sharing_loader_bloc.dart';
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
import 'period_label.dart';
|
||||
import 'profit_sharing_allocation.dart';
|
||||
|
||||
class ProfitSharingPeriods extends StatelessWidget {
|
||||
final ProfitSharingLoaderState state;
|
||||
final ValueChanged<ProfitSharingPeriodType> onPeriodTypeChanged;
|
||||
|
||||
const ProfitSharingPeriods({
|
||||
super.key,
|
||||
required this.state,
|
||||
required this.onPeriodTypeChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final periods = state.periods;
|
||||
final percentages = state.profitSharing.budget.percentages;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 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: [
|
||||
Text(
|
||||
context.lang.period_breakdown,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
_tabs(context),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
if (periods.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.lang.no_data_yet,
|
||||
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
...periods.map(
|
||||
(period) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: _PeriodCard(period: period, percentages: percentages),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _tabs(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.background,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_tab(
|
||||
context: context,
|
||||
label: context.lang.weekly,
|
||||
type: ProfitSharingPeriodType.weekly,
|
||||
),
|
||||
_tab(
|
||||
context: context,
|
||||
label: context.lang.monthly,
|
||||
type: ProfitSharingPeriodType.monthly,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _tab({
|
||||
required BuildContext context,
|
||||
required String label,
|
||||
required ProfitSharingPeriodType type,
|
||||
}) {
|
||||
final isSelected = state.periodType == type;
|
||||
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () => onPeriodTypeChanged(type),
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? AppColor.primary : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
textAlign: TextAlign.center,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: isSelected ? AppColor.textWhite : AppColor.textSecondary,
|
||||
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PeriodCard extends StatelessWidget {
|
||||
final ProfitSharingPeriod period;
|
||||
final ProfitSharingPercentage percentages;
|
||||
|
||||
const _PeriodCard({required this.period, required this.percentages});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isMonthly = period.month.isNotEmpty;
|
||||
|
||||
final title = isMonthly
|
||||
? formatMonthLabel(period.month, period.periodStart)
|
||||
: formatPeriodLabel(period.periodStart, period.periodEnd);
|
||||
|
||||
final subtitle = isMonthly
|
||||
? '${formatPeriodLabel(period.periodStart, period.periodEnd)} - ${context.lang.week_count(period.weekCount)}'
|
||||
: context.lang.order_count_label(period.orderCount);
|
||||
|
||||
return Container(
|
||||
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: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
subtitle,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
Text(
|
||||
period.revenue.currencyFormatRp,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(12),
|
||||
|
||||
ProfitSharingBar(
|
||||
purchase: percentages.purchase,
|
||||
owner: percentages.owner,
|
||||
team: percentages.team,
|
||||
height: 6,
|
||||
),
|
||||
|
||||
const SpaceHeight(12),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
_share(
|
||||
context.lang.share_purchase,
|
||||
period.limitPurchase,
|
||||
ProfitSharingColor.purchase,
|
||||
),
|
||||
_share(
|
||||
context.lang.share_owner,
|
||||
period.limitOwner,
|
||||
ProfitSharingColor.owner,
|
||||
),
|
||||
_share(
|
||||
context.lang.share_team,
|
||||
period.limitTeam,
|
||||
ProfitSharingColor.team,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _share(String label, int amount, Color color) {
|
||||
return Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
),
|
||||
const SpaceWidth(6),
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
amount.currencyFormatRp,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
|
||||
/// Ambang kesehatan berdasarkan % HPP riil terhadap omzet.
|
||||
/// Ubah di sini kalau target margin bisnis berubah.
|
||||
class HppThreshold {
|
||||
static const double healthy = 70;
|
||||
static const double watch = 80;
|
||||
}
|
||||
|
||||
String formatPercent(double value) => '${value.toStringAsFixed(1)}%';
|
||||
|
||||
Color statusColor(double realHppPercentage) {
|
||||
if (realHppPercentage <= HppThreshold.healthy) return AppColor.success;
|
||||
if (realHppPercentage <= HppThreshold.watch) return AppColor.warning;
|
||||
return AppColor.error;
|
||||
}
|
||||
|
||||
String statusLabel(BuildContext context, double realHppPercentage) {
|
||||
if (realHppPercentage <= HppThreshold.healthy) {
|
||||
return context.lang.status_healthy;
|
||||
}
|
||||
if (realHppPercentage <= HppThreshold.watch) return context.lang.status_watch;
|
||||
return context.lang.status_critical;
|
||||
}
|
||||
|
||||
/// Pill status margin (Sehat / Waspada / Tidak Sehat).
|
||||
class ProfitSharingStatusBadge extends StatelessWidget {
|
||||
final double realHppPercentage;
|
||||
final bool onDarkBackground;
|
||||
|
||||
const ProfitSharingStatusBadge({
|
||||
super.key,
|
||||
required this.realHppPercentage,
|
||||
this.onDarkBackground = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = statusColor(realHppPercentage);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: onDarkBackground
|
||||
? AppColor.textWhite.withOpacity(0.18)
|
||||
: color.withOpacity(0.12),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: onDarkBackground
|
||||
? Border.all(color: AppColor.textWhite.withOpacity(0.3))
|
||||
: null,
|
||||
),
|
||||
child: Text(
|
||||
statusLabel(context, realHppPercentage),
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: onDarkBackground ? AppColor.textWhite : color,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Label kecil dua baris: judul di atas, nilai di bawah.
|
||||
class ProfitSharingMetric extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
final Color? valueColor;
|
||||
final bool isBold;
|
||||
|
||||
const ProfitSharingMetric({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.value,
|
||||
this.valueColor,
|
||||
this.isBold = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
value,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: valueColor ?? AppColor.textPrimary,
|
||||
fontWeight: isBold ? FontWeight.w800 : FontWeight.w600,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
import 'profit_sharing_status.dart';
|
||||
|
||||
/// Daftar sub kategori; tiap kartu bisa dibuka untuk melihat produknya.
|
||||
class ProfitSharingSubCategories extends StatelessWidget {
|
||||
final List<ProfitSharingSubCategory> categories;
|
||||
final String expandedCategoryId;
|
||||
final ValueChanged<String> onToggle;
|
||||
|
||||
const ProfitSharingSubCategories({
|
||||
super.key,
|
||||
required this.categories,
|
||||
required this.expandedCategoryId,
|
||||
required this.onToggle,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sorted = [...categories]
|
||||
..sort((a, b) => b.totalRevenue.compareTo(a.totalRevenue));
|
||||
|
||||
final totalRevenue = sorted.fold<int>(0, (sum, e) => sum + e.totalRevenue);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 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: [
|
||||
Text(
|
||||
context.lang.sub_category,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
if (sorted.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.lang.no_data_yet,
|
||||
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
...sorted.map(
|
||||
(category) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: _SubCategoryCard(
|
||||
category: category,
|
||||
share: totalRevenue == 0
|
||||
? 0
|
||||
: category.totalRevenue / totalRevenue,
|
||||
isExpanded: expandedCategoryId == category.categoryId,
|
||||
onToggle: () => onToggle(category.categoryId),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SubCategoryCard extends StatelessWidget {
|
||||
final ProfitSharingSubCategory category;
|
||||
final double share;
|
||||
final bool isExpanded;
|
||||
final VoidCallback onToggle;
|
||||
|
||||
const _SubCategoryCard({
|
||||
required this.category,
|
||||
required this.share,
|
||||
required this.isExpanded,
|
||||
required this.onToggle,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.background,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColor.borderLight),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: onToggle,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
category.categoryName,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
Text(
|
||||
category.totalRevenue.currencyFormatRp,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
AnimatedRotation(
|
||||
turns: isExpanded ? 0.5 : 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: const Icon(
|
||||
Icons.keyboard_arrow_down_rounded,
|
||||
color: AppColor.textSecondary,
|
||||
size: 22,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(8),
|
||||
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: LinearProgressIndicator(
|
||||
value: share.clamp(0.0, 1.0),
|
||||
minHeight: 5,
|
||||
backgroundColor: AppColor.borderLight,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||
AppColor.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(12),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.qty,
|
||||
value: category.totalQuantity.thousandFormat,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.products,
|
||||
value: '${category.productCount}',
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ProfitSharingMetric(
|
||||
label: context.lang.real_hpp,
|
||||
value: formatPercent(category.realHppPercentage),
|
||||
valueColor: statusColor(category.realHppPercentage),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
AnimatedCrossFade(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
crossFadeState: isExpanded
|
||||
? CrossFadeState.showFirst
|
||||
: CrossFadeState.showSecond,
|
||||
firstChild: _ProductList(products: category.products),
|
||||
secondChild: const SizedBox(width: double.infinity),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ProductList extends StatelessWidget {
|
||||
final List<ProfitSharingProduct> products;
|
||||
|
||||
const _ProductList({required this.products});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sorted = [...products]
|
||||
..sort((a, b) => b.revenue.compareTo(a.revenue));
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(14, 0, 14, 14),
|
||||
child: Column(
|
||||
children: [
|
||||
const Divider(height: 1, color: AppColor.border),
|
||||
const SpaceHeight(10),
|
||||
...sorted.map((product) => _ProductRow(product: product)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ProductRow extends StatelessWidget {
|
||||
final ProfitSharingProduct product;
|
||||
|
||||
const _ProductRow({required this.product});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = statusColor(product.realHppPercentage);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.08),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
'${product.quantitySold}x',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SpaceWidth(10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
product.productName,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'${product.productSku} · @${product.averagePrice.round().currencyFormatRp}',
|
||||
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
product.revenue.currencyFormatRp,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'${context.lang.real_hpp} ${formatPercent(product.realHppPercentage)}',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,10 @@ class AppRouter extends RootStackRouter {
|
||||
// Finance page
|
||||
AutoRoute(page: FinanceRoute.page),
|
||||
|
||||
// Profit Sharing
|
||||
AutoRoute(page: ProfitSharingRoute.page),
|
||||
AutoRoute(page: ProfitSharingDetailRoute.page),
|
||||
|
||||
// Exclusive Summary
|
||||
AutoRoute(page: ExclusiveSummaryRoute.page),
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
// coverage:ignore-file
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:apskel_owner_flutter/domain/order/order.dart' as _i29;
|
||||
import 'package:apskel_owner_flutter/domain/user/user.dart' as _i30;
|
||||
import 'package:apskel_owner_flutter/domain/order/order.dart' as _i31;
|
||||
import 'package:apskel_owner_flutter/domain/user/user.dart' as _i32;
|
||||
import 'package:apskel_owner_flutter/presentation/pages/about_app/about_app_page.dart'
|
||||
as _i1;
|
||||
import 'package:apskel_owner_flutter/presentation/pages/auth/login/login_page.dart'
|
||||
@@ -53,28 +53,32 @@ import 'package:apskel_owner_flutter/presentation/pages/profile/pages/profile_ed
|
||||
as _i20;
|
||||
import 'package:apskel_owner_flutter/presentation/pages/profile/profile_page.dart'
|
||||
as _i21;
|
||||
import 'package:apskel_owner_flutter/presentation/pages/purchase/purchase_page.dart'
|
||||
import 'package:apskel_owner_flutter/presentation/pages/profit_sharing/profit_sharing_detail_page.dart'
|
||||
as _i22;
|
||||
import 'package:apskel_owner_flutter/presentation/pages/report/report_page.dart'
|
||||
import 'package:apskel_owner_flutter/presentation/pages/profit_sharing/profit_sharing_page.dart'
|
||||
as _i23;
|
||||
import 'package:apskel_owner_flutter/presentation/pages/sales/sales_page.dart'
|
||||
import 'package:apskel_owner_flutter/presentation/pages/purchase/purchase_page.dart'
|
||||
as _i24;
|
||||
import 'package:apskel_owner_flutter/presentation/pages/schedule/schedule_page.dart'
|
||||
import 'package:apskel_owner_flutter/presentation/pages/report/report_page.dart'
|
||||
as _i25;
|
||||
import 'package:apskel_owner_flutter/presentation/pages/splash/splash_page.dart'
|
||||
import 'package:apskel_owner_flutter/presentation/pages/sales/sales_page.dart'
|
||||
as _i26;
|
||||
import 'package:auto_route/auto_route.dart' as _i27;
|
||||
import 'package:flutter/material.dart' as _i28;
|
||||
import 'package:apskel_owner_flutter/presentation/pages/schedule/schedule_page.dart'
|
||||
as _i27;
|
||||
import 'package:apskel_owner_flutter/presentation/pages/splash/splash_page.dart'
|
||||
as _i28;
|
||||
import 'package:auto_route/auto_route.dart' as _i29;
|
||||
import 'package:flutter/material.dart' as _i30;
|
||||
|
||||
/// generated route for
|
||||
/// [_i1.AboutAppPage]
|
||||
class AboutAppRoute extends _i27.PageRouteInfo<void> {
|
||||
const AboutAppRoute({List<_i27.PageRouteInfo>? children})
|
||||
class AboutAppRoute extends _i29.PageRouteInfo<void> {
|
||||
const AboutAppRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(AboutAppRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'AboutAppRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i1.AboutAppPage();
|
||||
@@ -84,13 +88,13 @@ class AboutAppRoute extends _i27.PageRouteInfo<void> {
|
||||
|
||||
/// generated route for
|
||||
/// [_i2.ComingSoonPage]
|
||||
class ComingSoonRoute extends _i27.PageRouteInfo<void> {
|
||||
const ComingSoonRoute({List<_i27.PageRouteInfo>? children})
|
||||
class ComingSoonRoute extends _i29.PageRouteInfo<void> {
|
||||
const ComingSoonRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(ComingSoonRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'ComingSoonRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i2.ComingSoonPage();
|
||||
@@ -100,29 +104,29 @@ class ComingSoonRoute extends _i27.PageRouteInfo<void> {
|
||||
|
||||
/// generated route for
|
||||
/// [_i3.CustomerPage]
|
||||
class CustomerRoute extends _i27.PageRouteInfo<void> {
|
||||
const CustomerRoute({List<_i27.PageRouteInfo>? children})
|
||||
class CustomerRoute extends _i29.PageRouteInfo<void> {
|
||||
const CustomerRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(CustomerRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'CustomerRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i27.WrappedRoute(child: const _i3.CustomerPage());
|
||||
return _i29.WrappedRoute(child: const _i3.CustomerPage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i4.DailyTasksFormPage]
|
||||
class DailyTasksFormRoute extends _i27.PageRouteInfo<void> {
|
||||
const DailyTasksFormRoute({List<_i27.PageRouteInfo>? children})
|
||||
class DailyTasksFormRoute extends _i29.PageRouteInfo<void> {
|
||||
const DailyTasksFormRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(DailyTasksFormRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'DailyTasksFormRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i4.DailyTasksFormPage();
|
||||
@@ -132,32 +136,32 @@ class DailyTasksFormRoute extends _i27.PageRouteInfo<void> {
|
||||
|
||||
/// generated route for
|
||||
/// [_i5.DownloadReportPage]
|
||||
class DownloadReportRoute extends _i27.PageRouteInfo<void> {
|
||||
const DownloadReportRoute({List<_i27.PageRouteInfo>? children})
|
||||
class DownloadReportRoute extends _i29.PageRouteInfo<void> {
|
||||
const DownloadReportRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(DownloadReportRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'DownloadReportRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i27.WrappedRoute(child: const _i5.DownloadReportPage());
|
||||
return _i29.WrappedRoute(child: const _i5.DownloadReportPage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i6.ErrorPage]
|
||||
class ErrorRoute extends _i27.PageRouteInfo<ErrorRouteArgs> {
|
||||
class ErrorRoute extends _i29.PageRouteInfo<ErrorRouteArgs> {
|
||||
ErrorRoute({
|
||||
_i28.Key? key,
|
||||
_i30.Key? key,
|
||||
String? title,
|
||||
String? message,
|
||||
_i28.VoidCallback? onRetry,
|
||||
_i28.VoidCallback? onBack,
|
||||
_i30.VoidCallback? onRetry,
|
||||
_i30.VoidCallback? onBack,
|
||||
String? errorCode,
|
||||
_i28.IconData? errorIcon,
|
||||
List<_i27.PageRouteInfo>? children,
|
||||
_i30.IconData? errorIcon,
|
||||
List<_i29.PageRouteInfo>? children,
|
||||
}) : super(
|
||||
ErrorRoute.name,
|
||||
args: ErrorRouteArgs(
|
||||
@@ -174,7 +178,7 @@ class ErrorRoute extends _i27.PageRouteInfo<ErrorRouteArgs> {
|
||||
|
||||
static const String name = 'ErrorRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
final args = data.argsAs<ErrorRouteArgs>(
|
||||
@@ -204,19 +208,19 @@ class ErrorRouteArgs {
|
||||
this.errorIcon,
|
||||
});
|
||||
|
||||
final _i28.Key? key;
|
||||
final _i30.Key? key;
|
||||
|
||||
final String? title;
|
||||
|
||||
final String? message;
|
||||
|
||||
final _i28.VoidCallback? onRetry;
|
||||
final _i30.VoidCallback? onRetry;
|
||||
|
||||
final _i28.VoidCallback? onBack;
|
||||
final _i30.VoidCallback? onBack;
|
||||
|
||||
final String? errorCode;
|
||||
|
||||
final _i28.IconData? errorIcon;
|
||||
final _i30.IconData? errorIcon;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@@ -226,77 +230,77 @@ class ErrorRouteArgs {
|
||||
|
||||
/// generated route for
|
||||
/// [_i7.ExclusiveSummaryPage]
|
||||
class ExclusiveSummaryRoute extends _i27.PageRouteInfo<void> {
|
||||
const ExclusiveSummaryRoute({List<_i27.PageRouteInfo>? children})
|
||||
class ExclusiveSummaryRoute extends _i29.PageRouteInfo<void> {
|
||||
const ExclusiveSummaryRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(ExclusiveSummaryRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'ExclusiveSummaryRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i27.WrappedRoute(child: const _i7.ExclusiveSummaryPage());
|
||||
return _i29.WrappedRoute(child: const _i7.ExclusiveSummaryPage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i8.FinancePage]
|
||||
class FinanceRoute extends _i27.PageRouteInfo<void> {
|
||||
const FinanceRoute({List<_i27.PageRouteInfo>? children})
|
||||
class FinanceRoute extends _i29.PageRouteInfo<void> {
|
||||
const FinanceRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(FinanceRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'FinanceRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i27.WrappedRoute(child: const _i8.FinancePage());
|
||||
return _i29.WrappedRoute(child: const _i8.FinancePage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i9.HomePage]
|
||||
class HomeRoute extends _i27.PageRouteInfo<void> {
|
||||
const HomeRoute({List<_i27.PageRouteInfo>? children})
|
||||
class HomeRoute extends _i29.PageRouteInfo<void> {
|
||||
const HomeRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(HomeRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'HomeRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i27.WrappedRoute(child: const _i9.HomePage());
|
||||
return _i29.WrappedRoute(child: const _i9.HomePage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i10.InventoryPage]
|
||||
class InventoryRoute extends _i27.PageRouteInfo<void> {
|
||||
const InventoryRoute({List<_i27.PageRouteInfo>? children})
|
||||
class InventoryRoute extends _i29.PageRouteInfo<void> {
|
||||
const InventoryRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(InventoryRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'InventoryRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i27.WrappedRoute(child: const _i10.InventoryPage());
|
||||
return _i29.WrappedRoute(child: const _i10.InventoryPage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i11.LanguagePage]
|
||||
class LanguageRoute extends _i27.PageRouteInfo<void> {
|
||||
const LanguageRoute({List<_i27.PageRouteInfo>? children})
|
||||
class LanguageRoute extends _i29.PageRouteInfo<void> {
|
||||
const LanguageRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(LanguageRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'LanguageRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i11.LanguagePage();
|
||||
@@ -306,29 +310,29 @@ class LanguageRoute extends _i27.PageRouteInfo<void> {
|
||||
|
||||
/// generated route for
|
||||
/// [_i12.LoginPage]
|
||||
class LoginRoute extends _i27.PageRouteInfo<void> {
|
||||
const LoginRoute({List<_i27.PageRouteInfo>? children})
|
||||
class LoginRoute extends _i29.PageRouteInfo<void> {
|
||||
const LoginRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(LoginRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'LoginRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i27.WrappedRoute(child: const _i12.LoginPage());
|
||||
return _i29.WrappedRoute(child: const _i12.LoginPage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i13.MainPage]
|
||||
class MainRoute extends _i27.PageRouteInfo<void> {
|
||||
const MainRoute({List<_i27.PageRouteInfo>? children})
|
||||
class MainRoute extends _i29.PageRouteInfo<void> {
|
||||
const MainRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(MainRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'MainRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i13.MainPage();
|
||||
@@ -338,11 +342,11 @@ class MainRoute extends _i27.PageRouteInfo<void> {
|
||||
|
||||
/// generated route for
|
||||
/// [_i14.OrderDetailPage]
|
||||
class OrderDetailRoute extends _i27.PageRouteInfo<OrderDetailRouteArgs> {
|
||||
class OrderDetailRoute extends _i29.PageRouteInfo<OrderDetailRouteArgs> {
|
||||
OrderDetailRoute({
|
||||
_i28.Key? key,
|
||||
required _i29.Order order,
|
||||
List<_i27.PageRouteInfo>? children,
|
||||
_i30.Key? key,
|
||||
required _i31.Order order,
|
||||
List<_i29.PageRouteInfo>? children,
|
||||
}) : super(
|
||||
OrderDetailRoute.name,
|
||||
args: OrderDetailRouteArgs(key: key, order: order),
|
||||
@@ -351,7 +355,7 @@ class OrderDetailRoute extends _i27.PageRouteInfo<OrderDetailRouteArgs> {
|
||||
|
||||
static const String name = 'OrderDetailRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
final args = data.argsAs<OrderDetailRouteArgs>();
|
||||
@@ -363,9 +367,9 @@ class OrderDetailRoute extends _i27.PageRouteInfo<OrderDetailRouteArgs> {
|
||||
class OrderDetailRouteArgs {
|
||||
const OrderDetailRouteArgs({this.key, required this.order});
|
||||
|
||||
final _i28.Key? key;
|
||||
final _i30.Key? key;
|
||||
|
||||
final _i29.Order order;
|
||||
final _i31.Order order;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@@ -375,91 +379,91 @@ class OrderDetailRouteArgs {
|
||||
|
||||
/// generated route for
|
||||
/// [_i15.OrderPage]
|
||||
class OrderRoute extends _i27.PageRouteInfo<void> {
|
||||
const OrderRoute({List<_i27.PageRouteInfo>? children})
|
||||
class OrderRoute extends _i29.PageRouteInfo<void> {
|
||||
const OrderRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(OrderRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'OrderRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i27.WrappedRoute(child: const _i15.OrderPage());
|
||||
return _i29.WrappedRoute(child: const _i15.OrderPage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i16.OutletInformationPage]
|
||||
class OutletInformationRoute extends _i27.PageRouteInfo<void> {
|
||||
const OutletInformationRoute({List<_i27.PageRouteInfo>? children})
|
||||
class OutletInformationRoute extends _i29.PageRouteInfo<void> {
|
||||
const OutletInformationRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(OutletInformationRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'OutletInformationRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i27.WrappedRoute(child: const _i16.OutletInformationPage());
|
||||
return _i29.WrappedRoute(child: const _i16.OutletInformationPage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i17.ProductAnalyticPage]
|
||||
class ProductAnalyticRoute extends _i27.PageRouteInfo<void> {
|
||||
const ProductAnalyticRoute({List<_i27.PageRouteInfo>? children})
|
||||
class ProductAnalyticRoute extends _i29.PageRouteInfo<void> {
|
||||
const ProductAnalyticRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(ProductAnalyticRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'ProductAnalyticRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i27.WrappedRoute(child: const _i17.ProductAnalyticPage());
|
||||
return _i29.WrappedRoute(child: const _i17.ProductAnalyticPage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i18.ProductPage]
|
||||
class ProductRoute extends _i27.PageRouteInfo<void> {
|
||||
const ProductRoute({List<_i27.PageRouteInfo>? children})
|
||||
class ProductRoute extends _i29.PageRouteInfo<void> {
|
||||
const ProductRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(ProductRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'ProductRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i27.WrappedRoute(child: const _i18.ProductPage());
|
||||
return _i29.WrappedRoute(child: const _i18.ProductPage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i19.ProfileChangePasswordPage]
|
||||
class ProfileChangePasswordRoute extends _i27.PageRouteInfo<void> {
|
||||
const ProfileChangePasswordRoute({List<_i27.PageRouteInfo>? children})
|
||||
class ProfileChangePasswordRoute extends _i29.PageRouteInfo<void> {
|
||||
const ProfileChangePasswordRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(ProfileChangePasswordRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'ProfileChangePasswordRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i27.WrappedRoute(child: const _i19.ProfileChangePasswordPage());
|
||||
return _i29.WrappedRoute(child: const _i19.ProfileChangePasswordPage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i20.ProfileEditPage]
|
||||
class ProfileEditRoute extends _i27.PageRouteInfo<ProfileEditRouteArgs> {
|
||||
class ProfileEditRoute extends _i29.PageRouteInfo<ProfileEditRouteArgs> {
|
||||
ProfileEditRoute({
|
||||
_i28.Key? key,
|
||||
required _i30.User user,
|
||||
List<_i27.PageRouteInfo>? children,
|
||||
_i30.Key? key,
|
||||
required _i32.User user,
|
||||
List<_i29.PageRouteInfo>? children,
|
||||
}) : super(
|
||||
ProfileEditRoute.name,
|
||||
args: ProfileEditRouteArgs(key: key, user: user),
|
||||
@@ -468,11 +472,11 @@ class ProfileEditRoute extends _i27.PageRouteInfo<ProfileEditRouteArgs> {
|
||||
|
||||
static const String name = 'ProfileEditRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
final args = data.argsAs<ProfileEditRouteArgs>();
|
||||
return _i27.WrappedRoute(
|
||||
return _i29.WrappedRoute(
|
||||
child: _i20.ProfileEditPage(key: args.key, user: args.user),
|
||||
);
|
||||
},
|
||||
@@ -482,9 +486,9 @@ class ProfileEditRoute extends _i27.PageRouteInfo<ProfileEditRouteArgs> {
|
||||
class ProfileEditRouteArgs {
|
||||
const ProfileEditRouteArgs({this.key, required this.user});
|
||||
|
||||
final _i28.Key? key;
|
||||
final _i30.Key? key;
|
||||
|
||||
final _i30.User user;
|
||||
final _i32.User user;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@@ -494,96 +498,179 @@ class ProfileEditRouteArgs {
|
||||
|
||||
/// generated route for
|
||||
/// [_i21.ProfilePage]
|
||||
class ProfileRoute extends _i27.PageRouteInfo<void> {
|
||||
const ProfileRoute({List<_i27.PageRouteInfo>? children})
|
||||
class ProfileRoute extends _i29.PageRouteInfo<void> {
|
||||
const ProfileRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(ProfileRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'ProfileRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i27.WrappedRoute(child: const _i21.ProfilePage());
|
||||
return _i29.WrappedRoute(child: const _i21.ProfilePage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i22.PurchasePage]
|
||||
class PurchaseRoute extends _i27.PageRouteInfo<void> {
|
||||
const PurchaseRoute({List<_i27.PageRouteInfo>? children})
|
||||
/// [_i22.ProfitSharingDetailPage]
|
||||
class ProfitSharingDetailRoute
|
||||
extends _i29.PageRouteInfo<ProfitSharingDetailRouteArgs> {
|
||||
ProfitSharingDetailRoute({
|
||||
_i30.Key? key,
|
||||
required String parentCategoryId,
|
||||
required String parentCategoryName,
|
||||
required DateTime dateFrom,
|
||||
required DateTime dateTo,
|
||||
List<_i29.PageRouteInfo>? children,
|
||||
}) : super(
|
||||
ProfitSharingDetailRoute.name,
|
||||
args: ProfitSharingDetailRouteArgs(
|
||||
key: key,
|
||||
parentCategoryId: parentCategoryId,
|
||||
parentCategoryName: parentCategoryName,
|
||||
dateFrom: dateFrom,
|
||||
dateTo: dateTo,
|
||||
),
|
||||
initialChildren: children,
|
||||
);
|
||||
|
||||
static const String name = 'ProfitSharingDetailRoute';
|
||||
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
final args = data.argsAs<ProfitSharingDetailRouteArgs>();
|
||||
return _i29.WrappedRoute(
|
||||
child: _i22.ProfitSharingDetailPage(
|
||||
key: args.key,
|
||||
parentCategoryId: args.parentCategoryId,
|
||||
parentCategoryName: args.parentCategoryName,
|
||||
dateFrom: args.dateFrom,
|
||||
dateTo: args.dateTo,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class ProfitSharingDetailRouteArgs {
|
||||
const ProfitSharingDetailRouteArgs({
|
||||
this.key,
|
||||
required this.parentCategoryId,
|
||||
required this.parentCategoryName,
|
||||
required this.dateFrom,
|
||||
required this.dateTo,
|
||||
});
|
||||
|
||||
final _i30.Key? key;
|
||||
|
||||
final String parentCategoryId;
|
||||
|
||||
final String parentCategoryName;
|
||||
|
||||
final DateTime dateFrom;
|
||||
|
||||
final DateTime dateTo;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfitSharingDetailRouteArgs{key: $key, parentCategoryId: $parentCategoryId, parentCategoryName: $parentCategoryName, dateFrom: $dateFrom, dateTo: $dateTo}';
|
||||
}
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i23.ProfitSharingPage]
|
||||
class ProfitSharingRoute extends _i29.PageRouteInfo<void> {
|
||||
const ProfitSharingRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(ProfitSharingRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'ProfitSharingRoute';
|
||||
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i29.WrappedRoute(child: const _i23.ProfitSharingPage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i24.PurchasePage]
|
||||
class PurchaseRoute extends _i29.PageRouteInfo<void> {
|
||||
const PurchaseRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(PurchaseRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'PurchaseRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i27.WrappedRoute(child: const _i22.PurchasePage());
|
||||
return _i29.WrappedRoute(child: const _i24.PurchasePage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i23.ReportPage]
|
||||
class ReportRoute extends _i27.PageRouteInfo<void> {
|
||||
const ReportRoute({List<_i27.PageRouteInfo>? children})
|
||||
/// [_i25.ReportPage]
|
||||
class ReportRoute extends _i29.PageRouteInfo<void> {
|
||||
const ReportRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(ReportRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'ReportRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i27.WrappedRoute(child: const _i23.ReportPage());
|
||||
return _i29.WrappedRoute(child: const _i25.ReportPage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i24.SalesPage]
|
||||
class SalesRoute extends _i27.PageRouteInfo<void> {
|
||||
const SalesRoute({List<_i27.PageRouteInfo>? children})
|
||||
/// [_i26.SalesPage]
|
||||
class SalesRoute extends _i29.PageRouteInfo<void> {
|
||||
const SalesRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(SalesRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'SalesRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return _i27.WrappedRoute(child: const _i24.SalesPage());
|
||||
return _i29.WrappedRoute(child: const _i26.SalesPage());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i25.SchedulePage]
|
||||
class ScheduleRoute extends _i27.PageRouteInfo<void> {
|
||||
const ScheduleRoute({List<_i27.PageRouteInfo>? children})
|
||||
/// [_i27.SchedulePage]
|
||||
class ScheduleRoute extends _i29.PageRouteInfo<void> {
|
||||
const ScheduleRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(ScheduleRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'ScheduleRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i25.SchedulePage();
|
||||
return const _i27.SchedulePage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [_i26.SplashPage]
|
||||
class SplashRoute extends _i27.PageRouteInfo<void> {
|
||||
const SplashRoute({List<_i27.PageRouteInfo>? children})
|
||||
/// [_i28.SplashPage]
|
||||
class SplashRoute extends _i29.PageRouteInfo<void> {
|
||||
const SplashRoute({List<_i29.PageRouteInfo>? children})
|
||||
: super(SplashRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'SplashRoute';
|
||||
|
||||
static _i27.PageInfo page = _i27.PageInfo(
|
||||
static _i29.PageInfo page = _i29.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const _i26.SplashPage();
|
||||
return const _i28.SplashPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user