Efril 0917c5132b
Some checks are pending
Build & Deploy iOS to TestFlight / build-and-deploy (push) Waiting to run
feat: update profit loss ui
2026-06-24 10:14:37 +07:00

239 lines
6.7 KiB
Dart

import 'package:flutter/material.dart';
import '../../../../common/extension/extension.dart';
import '../../../../common/theme/theme.dart';
import '../../../../domain/analytic/analytic.dart';
class ProfitLossReport extends StatelessWidget {
final List<ProfitLossMainSummaryItem> mainSummary;
final ProfitLossSummary summary;
final int selectedTabIndex;
const ProfitLossReport({
super.key,
required this.mainSummary,
required this.summary,
required this.selectedTabIndex,
});
@override
Widget build(BuildContext context) {
final isToday = selectedTabIndex == 0;
final marginPct = isToday
? summary.netProfitMargin.round()
: summary.netProfitMargin.round();
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: AppColor.textLight.withOpacity(0.08),
spreadRadius: 1,
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Title row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
context.lang.profit_loss_report,
style: AppStyle.lg.copyWith(
fontWeight: FontWeight.w700,
color: AppColor.textPrimary,
),
),
Text(
'margin $marginPct%',
style: AppStyle.sm.copyWith(
color: AppColor.textSecondary,
fontWeight: FontWeight.w400,
),
),
],
),
const SizedBox(height: 20),
// Main summary items
...mainSummary.map((item) => _buildSummarySection(item, isToday)),
const SizedBox(height: 12),
// Net Profit/Loss footer
_buildNetProfitFooter(context, isToday),
],
),
);
}
Widget _buildSummarySection(ProfitLossMainSummaryItem item, bool isToday) {
final nominal = isToday ? item.todayNominal : item.mtdNominal;
final pct = isToday ? item.todayPct : item.mtdPct;
return Column(
children: [
// Main item row
_buildItemRow(
label: item.label,
nominal: nominal,
pct: pct,
isBold: item.isBold,
isSubItem: false,
),
// Sub items
...item.subItems.map((subItem) {
final subNominal = isToday
? subItem.todayNominal
: subItem.mtdNominal;
final subPct = isToday ? subItem.todayPct : subItem.mtdPct;
return _buildItemRow(
label: subItem.label,
nominal: subNominal,
pct: subPct,
isBold: subItem.isBold,
isSubItem: true,
);
}),
// Divider after section (except for sub-items only sections)
if (item.isBold)
const Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: Divider(height: 1, color: AppColor.borderLight),
),
],
);
}
Widget _buildItemRow({
required String label,
required int nominal,
required double pct,
required bool isBold,
required bool isSubItem,
}) {
final isNegative = nominal < 0;
final displayNominal = isNegative
? '-${nominal.abs().currencyFormatRp}'
: nominal.currencyFormatRp;
final pctText = '${pct.round()}%';
// Determine color based on context
Color nominalColor;
if (isBold && isNegative) {
nominalColor = AppColor.error;
} else if (isBold) {
nominalColor = AppColor.textPrimary;
} else {
nominalColor = AppColor.textPrimary;
}
return Padding(
padding: EdgeInsets.only(left: isSubItem ? 16 : 0, top: 6, bottom: 6),
child: Row(
children: [
// Label
Expanded(
flex: 5,
child: Text(
label,
style: isBold
? AppStyle.md.copyWith(
fontWeight: FontWeight.w700,
color: AppColor.textPrimary,
)
: AppStyle.md.copyWith(
color: AppColor.textSecondary,
fontWeight: FontWeight.w400,
),
),
),
// Nominal
Expanded(
flex: 3,
child: Text(
displayNominal,
textAlign: TextAlign.right,
style: isBold
? AppStyle.md.copyWith(
fontWeight: FontWeight.w700,
color: nominalColor,
)
: AppStyle.md.copyWith(
color: AppColor.textPrimary,
fontWeight: FontWeight.w500,
),
),
),
// Percentage
SizedBox(
width: 48,
child: Text(
pctText,
textAlign: TextAlign.right,
style: AppStyle.sm.copyWith(
color: AppColor.textSecondary,
fontWeight: FontWeight.w400,
),
),
),
],
),
);
}
Widget _buildNetProfitFooter(BuildContext context, bool isToday) {
final netProfit = summary.netProfit;
final isNegative = netProfit < 0;
final displayValue = isNegative
? '-${netProfit.abs().currencyFormatRp}'
: netProfit.currencyFormatRp;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration(
color: isNegative
? AppColor.error.withOpacity(0.08)
: AppColor.success.withOpacity(0.08),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Flexible(
child: Text(
context.lang.net_profit_loss,
style: AppStyle.md.copyWith(
fontWeight: FontWeight.w700,
color: isNegative ? AppColor.error : AppColor.success,
),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 12),
Text(
displayValue,
style: AppStyle.lg.copyWith(
fontWeight: FontWeight.w900,
color: isNegative ? AppColor.error : AppColor.success,
fontSize: 20,
),
),
],
),
);
}
}