feat: lang
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncfusion_flutter_datepicker/datepicker.dart';
|
||||
|
||||
import '../../../common/extension/extension.dart';
|
||||
|
||||
class DateRangePickerBottomSheet {
|
||||
static Future<DateRangePickerSelectionChangedArgs?> show({
|
||||
required BuildContext context,
|
||||
@@ -9,8 +11,8 @@ class DateRangePickerBottomSheet {
|
||||
DateTime? initialEndDate,
|
||||
DateTime? minDate,
|
||||
DateTime? maxDate,
|
||||
String confirmText = 'Pilih',
|
||||
String cancelText = 'Batal',
|
||||
String? confirmText,
|
||||
String? cancelText,
|
||||
Color primaryColor = Colors.blue,
|
||||
Function(DateTime? startDate, DateTime? endDate)? onChanged,
|
||||
}) async {
|
||||
@@ -26,8 +28,8 @@ class DateRangePickerBottomSheet {
|
||||
initialEndDate: initialEndDate,
|
||||
minDate: minDate,
|
||||
maxDate: maxDate,
|
||||
confirmText: confirmText,
|
||||
cancelText: cancelText,
|
||||
confirmText: confirmText ?? context.lang.select,
|
||||
cancelText: cancelText ?? context.lang.cancel,
|
||||
primaryColor: primaryColor,
|
||||
onChanged: onChanged,
|
||||
),
|
||||
@@ -104,7 +106,7 @@ class _DateRangePickerBottomSheetState
|
||||
return _formatDate(range.startDate!);
|
||||
}
|
||||
}
|
||||
return 'Belum ada tanggal dipilih';
|
||||
return context.lang.no_date_selected;
|
||||
}
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
@@ -187,7 +189,7 @@ class _DateRangePickerBottomSheetState
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Tanggal Terpilih:',
|
||||
'${context.lang.selected_date}:',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncfusion_flutter_datepicker/datepicker.dart';
|
||||
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../bottom_sheet/date_range_bottom_sheet.dart';
|
||||
|
||||
@@ -22,7 +23,7 @@ class DateRangePickerField extends StatefulWidget {
|
||||
final double height;
|
||||
|
||||
const DateRangePickerField({
|
||||
Key? key,
|
||||
super.key,
|
||||
this.label,
|
||||
this.placeholder = 'Pilih rentang tanggal',
|
||||
this.startDate,
|
||||
@@ -38,7 +39,7 @@ class DateRangePickerField extends StatefulWidget {
|
||||
this.placeholderStyle,
|
||||
this.decoration,
|
||||
this.height = 52.0,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<DateRangePickerField> createState() => _DateRangePickerFieldState();
|
||||
@@ -83,7 +84,7 @@ class _DateRangePickerFieldState extends State<DateRangePickerField> {
|
||||
|
||||
final result = await DateRangePickerBottomSheet.show(
|
||||
context: context,
|
||||
title: widget.label ?? 'Pilih Rentang Tanggal',
|
||||
title: widget.label ?? context.lang.select_date_range,
|
||||
initialStartDate: widget.startDate,
|
||||
initialEndDate: widget.endDate,
|
||||
minDate: widget.minDate,
|
||||
@@ -294,7 +295,7 @@ class _DateRangePickerFieldOutlinedState
|
||||
|
||||
final result = await DateRangePickerBottomSheet.show(
|
||||
context: context,
|
||||
title: widget.label ?? 'Pilih Rentang Tanggal',
|
||||
title: widget.label ?? context.lang.select_date_range,
|
||||
initialStartDate: widget.startDate,
|
||||
initialEndDate: widget.endDate,
|
||||
minDate: widget.minDate,
|
||||
@@ -412,120 +413,3 @@ class _DateRangePickerFieldOutlinedState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage Example Widget
|
||||
class DateRangePickerExample extends StatefulWidget {
|
||||
@override
|
||||
_DateRangePickerExampleState createState() => _DateRangePickerExampleState();
|
||||
}
|
||||
|
||||
class _DateRangePickerExampleState extends State<DateRangePickerExample> {
|
||||
DateTime? _startDate;
|
||||
DateTime? _endDate;
|
||||
DateTime? _startDate2;
|
||||
DateTime? _endDate2;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Date Range Picker Example'),
|
||||
backgroundColor: AppColor.primary,
|
||||
foregroundColor: AppColor.white,
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Default Style',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
DateRangePickerField(
|
||||
label: 'Periode Laporan',
|
||||
placeholder: 'Pilih tanggal mulai - selesai',
|
||||
startDate: _startDate,
|
||||
endDate: _endDate,
|
||||
primaryColor: AppColor.primary,
|
||||
onChanged: (start, end) {
|
||||
setState(() {
|
||||
_startDate = start;
|
||||
_endDate = end;
|
||||
});
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
Text(
|
||||
'Outlined Style',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
DateRangePickerFieldOutlined(
|
||||
label: 'Rentang Waktu',
|
||||
placeholder: 'Pilih rentang tanggal',
|
||||
startDate: _startDate2,
|
||||
endDate: _endDate2,
|
||||
primaryColor: AppColor.secondary,
|
||||
onChanged: (start, end) {
|
||||
setState(() {
|
||||
_startDate2 = start;
|
||||
_endDate2 = end;
|
||||
});
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Display selected dates
|
||||
if (_startDate != null ||
|
||||
_endDate != null ||
|
||||
_startDate2 != null ||
|
||||
_endDate2 != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.background,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Selected Dates:',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (_startDate != null)
|
||||
Text(
|
||||
'Default: ${_startDate!} - ${_endDate ?? 'Not selected'}',
|
||||
),
|
||||
if (_startDate2 != null)
|
||||
Text(
|
||||
'Outlined: ${_startDate2!} - ${_endDate2 ?? 'Not selected'}',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ import 'package:auto_route/auto_route.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../components/assets/assets.gen.dart';
|
||||
|
||||
@RoutePage()
|
||||
class AboutAppPage extends StatefulWidget {
|
||||
@@ -107,7 +109,7 @@ class _AboutAppPageState extends State<AboutAppPage>
|
||||
return Opacity(
|
||||
opacity: _fadeAnimation.value,
|
||||
child: Text(
|
||||
'Tentang Aplikasi',
|
||||
context.lang.about_app,
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -142,7 +144,7 @@ class _AboutAppPageState extends State<AboutAppPage>
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.black.withOpacity(0.2),
|
||||
@@ -151,10 +153,9 @@ class _AboutAppPageState extends State<AboutAppPage>
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Icon(
|
||||
Icons.mobile_friendly,
|
||||
size: 50,
|
||||
color: AppColor.primary,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Assets.images.logo.image(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -247,7 +248,7 @@ class _AboutAppPageState extends State<AboutAppPage>
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
'Informasi Aplikasi',
|
||||
context.lang.app_information,
|
||||
style: AppStyle.h6.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.primary,
|
||||
@@ -256,18 +257,24 @@ class _AboutAppPageState extends State<AboutAppPage>
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildInfoRow('Nama Aplikasi', packageInfo?.appName ?? 'Loading...'),
|
||||
_buildInfoRow('Versi', packageInfo?.version ?? 'Loading...'),
|
||||
_buildInfoRow(
|
||||
'Build Number',
|
||||
context.lang.app_name,
|
||||
packageInfo?.appName ?? 'Loading...',
|
||||
),
|
||||
_buildInfoRow(
|
||||
context.lang.version,
|
||||
packageInfo?.version ?? 'Loading...',
|
||||
),
|
||||
_buildInfoRow(
|
||||
context.lang.build_number,
|
||||
packageInfo?.buildNumber ?? 'Loading...',
|
||||
),
|
||||
_buildInfoRow(
|
||||
'Package Name',
|
||||
context.lang.package_name,
|
||||
packageInfo?.packageName ?? 'Loading...',
|
||||
),
|
||||
_buildInfoRow(
|
||||
'Device',
|
||||
context.lang.device,
|
||||
deviceInfo.isEmpty ? 'Loading...' : deviceInfo,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
|
||||
@RoutePage()
|
||||
@@ -114,7 +115,7 @@ class _ComingSoonPageState extends State<ComingSoonPage>
|
||||
child: SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: Text(
|
||||
'Coming Soon',
|
||||
context.lang.coming_soon,
|
||||
style: AppStyle.h1.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -134,7 +135,7 @@ class _ComingSoonPageState extends State<ComingSoonPage>
|
||||
child: SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: Text(
|
||||
'Something amazing is brewing!\nStay tuned for the big reveal.',
|
||||
context.lang.coming_soon_desc,
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.9),
|
||||
height: 1.5,
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import '../../../application/customer/customer_loader/customer_loader_bloc.dart';
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../../domain/customer/customer.dart';
|
||||
import '../../../injection.dart';
|
||||
@@ -31,7 +32,7 @@ class CustomerPage extends StatefulWidget implements AutoRouteWrapper {
|
||||
class _CustomerPageState extends State<CustomerPage>
|
||||
with TickerProviderStateMixin {
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
ScrollController _scrollController = ScrollController();
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
bool _isGridView = false;
|
||||
|
||||
@override
|
||||
@@ -72,7 +73,7 @@ class _CustomerPageState extends State<CustomerPage>
|
||||
floating: false,
|
||||
pinned: true,
|
||||
backgroundColor: AppColor.primary,
|
||||
flexibleSpace: CustomAppBar(title: 'Pelanggan'),
|
||||
flexibleSpace: CustomAppBar(title: context.lang.customer),
|
||||
actions: [
|
||||
ActionIconButton(onTap: () {}, icon: LineIcons.search),
|
||||
],
|
||||
@@ -148,7 +149,7 @@ class _CustomerPageState extends State<CustomerPage>
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 16,
|
||||
mainAxisSpacing: 16,
|
||||
childAspectRatio: 0.8,
|
||||
childAspectRatio: 0.65,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
final customer = customers[index];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/customer/customer.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
@@ -175,78 +176,6 @@ class CustomerCard extends StatelessWidget {
|
||||
const SpaceHeight(12),
|
||||
],
|
||||
|
||||
// Status Badge
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: customer.isActive
|
||||
? AppColor.success.withOpacity(0.1)
|
||||
: AppColor.error.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: customer.isActive
|
||||
? AppColor.success.withOpacity(0.3)
|
||||
: AppColor.error.withOpacity(0.3),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: customer.isActive
|
||||
? AppColor.success
|
||||
: AppColor.error,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(6),
|
||||
Text(
|
||||
customer.isActive ? 'Active' : 'Inactive',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: customer.isActive
|
||||
? AppColor.success
|
||||
: AppColor.error,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Additional info if available
|
||||
if (customer.address.isNotEmpty) ...[
|
||||
const SpaceHeight(8),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.location_on_outlined,
|
||||
size: 14,
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
const SpaceWidth(4),
|
||||
Flexible(
|
||||
child: Text(
|
||||
customer.address,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
// Metadata info
|
||||
if (customer.metadata.isNotEmpty && _hasRelevantMetadata()) ...[
|
||||
const SpaceHeight(8),
|
||||
@@ -284,7 +213,7 @@ class CustomerCard extends StatelessWidget {
|
||||
if (customer.createdAt.isNotEmpty) ...[
|
||||
const SpaceHeight(8),
|
||||
Text(
|
||||
'Joined ${_formatDate(customer.createdAt)}',
|
||||
'${context.lang.joined} ${_formatDate(context, customer.createdAt)}',
|
||||
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
@@ -335,7 +264,7 @@ class CustomerCard extends StatelessWidget {
|
||||
return colors[index];
|
||||
}
|
||||
|
||||
String _formatDate(String dateStr) {
|
||||
String _formatDate(BuildContext context, String dateStr) {
|
||||
try {
|
||||
final date = DateTime.parse(dateStr);
|
||||
final now = DateTime.now();
|
||||
@@ -346,13 +275,13 @@ class CustomerCard extends StatelessWidget {
|
||||
} else if (difference == 1) {
|
||||
return 'yesterday';
|
||||
} else if (difference < 30) {
|
||||
return '${difference}d ago';
|
||||
return '${difference}d ${context.lang.ago}';
|
||||
} else if (difference < 365) {
|
||||
final months = (difference / 30).floor();
|
||||
return '${months}mo ago';
|
||||
return '${months}mo ${context.lang.ago}';
|
||||
} else {
|
||||
final years = (difference / 365).floor();
|
||||
return '${years}y ago';
|
||||
return '${years}y ${context.lang.ago}';
|
||||
}
|
||||
} catch (e) {
|
||||
return dateStr;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/customer/customer.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
@@ -245,7 +246,9 @@ class CustomerTile extends StatelessWidget {
|
||||
),
|
||||
const SpaceWidth(6),
|
||||
Text(
|
||||
customer.isActive ? 'Active' : 'Inactive',
|
||||
customer.isActive
|
||||
? context.lang.active
|
||||
: context.lang.inactive,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: customer.isActive
|
||||
? AppColor.success
|
||||
@@ -260,7 +263,7 @@ class CustomerTile extends StatelessWidget {
|
||||
// Created date
|
||||
if (customer.createdAt.isNotEmpty)
|
||||
Text(
|
||||
'Joined ${_formatDate(customer.createdAt)}',
|
||||
'${context.lang.joined} ${_formatDate(context, customer.createdAt)}',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
@@ -326,7 +329,7 @@ class CustomerTile extends StatelessWidget {
|
||||
return colors[index];
|
||||
}
|
||||
|
||||
String _formatDate(String dateStr) {
|
||||
String _formatDate(BuildContext context, String dateStr) {
|
||||
try {
|
||||
final date = DateTime.parse(dateStr);
|
||||
final now = DateTime.now();
|
||||
@@ -337,13 +340,13 @@ class CustomerTile extends StatelessWidget {
|
||||
} else if (difference == 1) {
|
||||
return 'yesterday';
|
||||
} else if (difference < 30) {
|
||||
return '${difference}d ago';
|
||||
return '${difference}d ${context.lang.ago}';
|
||||
} else if (difference < 365) {
|
||||
final months = (difference / 30).floor();
|
||||
return '${months}mo ago';
|
||||
return '${months}mo ${context.lang.ago}';
|
||||
} else {
|
||||
final years = (difference / 365).floor();
|
||||
return '${years}y ago';
|
||||
return '${years}y ${context.lang.ago}';
|
||||
}
|
||||
} catch (e) {
|
||||
return dateStr;
|
||||
|
||||
@@ -113,7 +113,7 @@ class _DownloadReportPageState extends State<DownloadReportPage>
|
||||
pinned: true,
|
||||
elevation: 0,
|
||||
backgroundColor: AppColor.primary,
|
||||
flexibleSpace: CustomAppBar(title: 'Download Report'),
|
||||
flexibleSpace: CustomAppBar(title: context.lang.download_report),
|
||||
),
|
||||
|
||||
// Content
|
||||
@@ -134,9 +134,8 @@ class _DownloadReportPageState extends State<DownloadReportPage>
|
||||
>(
|
||||
builder: (context, state) {
|
||||
return _ReportOptionCard(
|
||||
title: 'Transaction Report',
|
||||
subtitle:
|
||||
'Export all transaction data with detailed analytics',
|
||||
title: context.lang.transaction_report,
|
||||
subtitle: context.lang.transaction_report_desc,
|
||||
icon: Icons.receipt_long_outlined,
|
||||
gradient: const [
|
||||
AppColor.primary,
|
||||
@@ -205,9 +204,8 @@ class _DownloadReportPageState extends State<DownloadReportPage>
|
||||
BlocBuilder<InventoryReportBloc, InventoryReportState>(
|
||||
builder: (context, state) {
|
||||
return _ReportOptionCard(
|
||||
title: 'Inventory Report',
|
||||
subtitle:
|
||||
'Export inventory and stock data with trends',
|
||||
title: context.lang.invetory_report,
|
||||
subtitle: context.lang.invetory_report_desc,
|
||||
icon: Icons.inventory_2_outlined,
|
||||
gradient: const [
|
||||
AppColor.secondary,
|
||||
@@ -479,7 +477,7 @@ class _ReportOptionCardState extends State<_ReportOptionCard>
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Select Date Range',
|
||||
context.lang.select_date_range,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -489,7 +487,7 @@ class _ReportOptionCardState extends State<_ReportOptionCard>
|
||||
|
||||
// Date Range Picker Field Style
|
||||
DateRangePickerField(
|
||||
placeholder: 'Select date range',
|
||||
placeholder: context.lang.select_date_range,
|
||||
startDate: widget.startDate,
|
||||
endDate: widget.endDate,
|
||||
onChanged: widget.onDateRangeChanged,
|
||||
@@ -542,7 +540,7 @@ class _ReportOptionCardState extends State<_ReportOptionCard>
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Download Report',
|
||||
context.lang.download_report,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: widget.gradient.first,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
||||
@@ -126,7 +126,7 @@ class _FinancePageState extends State<FinancePage>
|
||||
pinned: true,
|
||||
backgroundColor: AppColor.primary,
|
||||
elevation: 0,
|
||||
flexibleSpace: CustomAppBar(title: 'Keuangan'),
|
||||
flexibleSpace: CustomAppBar(title: context.lang.finance),
|
||||
),
|
||||
|
||||
// Header dengan filter periode
|
||||
@@ -221,7 +221,7 @@ class _FinancePageState extends State<FinancePage>
|
||||
children: [
|
||||
Expanded(
|
||||
child: FinanceSummaryCard(
|
||||
title: 'Total Pendapatan',
|
||||
title: context.lang.total_revenue,
|
||||
amount: summary.totalRevenue.currencyFormatRp,
|
||||
icon: LineIcons.arrowUp,
|
||||
color: AppColor.success,
|
||||
@@ -231,7 +231,7 @@ class _FinancePageState extends State<FinancePage>
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: FinanceSummaryCard(
|
||||
title: 'Total Pengeluaran',
|
||||
title: context.lang.total_expenditures,
|
||||
amount: summary.totalCost.currencyFormatRp,
|
||||
icon: LineIcons.arrowDown,
|
||||
color: AppColor.error,
|
||||
@@ -245,7 +245,7 @@ class _FinancePageState extends State<FinancePage>
|
||||
children: [
|
||||
Expanded(
|
||||
child: FinanceSummaryCard(
|
||||
title: 'Keuntungan Bersih',
|
||||
title: context.lang.net_profit,
|
||||
amount: summary.netProfit.currencyFormatRp,
|
||||
icon: LineIcons.lineChart,
|
||||
color: AppColor.info,
|
||||
@@ -255,7 +255,7 @@ class _FinancePageState extends State<FinancePage>
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: FinanceSummaryCard(
|
||||
title: 'Margin Profit',
|
||||
title: context.lang.margin_profit,
|
||||
amount: '${summary.profitabilityRatio.round()}%',
|
||||
icon: LineIcons.percent,
|
||||
color: AppColor.warning,
|
||||
@@ -304,14 +304,14 @@ class _FinancePageState extends State<FinancePage>
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Analisis Produk',
|
||||
context.lang.product_analytic,
|
||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
child: Text(
|
||||
'Lihat Semua',
|
||||
context.lang.view_all,
|
||||
style: AppStyle.sm.copyWith(color: AppColor.primary),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
|
||||
@@ -57,7 +58,7 @@ class FinanceCashFlow extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Analisis Cash Flow',
|
||||
context.lang.cash_flow_analysis,
|
||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
@@ -78,7 +79,7 @@ class FinanceCashFlow extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildCashFlowIndicator(
|
||||
'Cash In',
|
||||
context.lang.cash_in,
|
||||
_formatCurrency(totalCashIn),
|
||||
LineIcons.arrowUp,
|
||||
AppColor.success,
|
||||
@@ -87,7 +88,7 @@ class FinanceCashFlow extends StatelessWidget {
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: _buildCashFlowIndicator(
|
||||
'Cash Out',
|
||||
context.lang.cash_out,
|
||||
_formatCurrency(totalCashOut),
|
||||
LineIcons.arrowDown,
|
||||
AppColor.error,
|
||||
@@ -96,7 +97,7 @@ class FinanceCashFlow extends StatelessWidget {
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: _buildCashFlowIndicator(
|
||||
'Net Flow',
|
||||
context.lang.net_flow,
|
||||
_formatCurrency(netFlow),
|
||||
LineIcons.equals,
|
||||
AppColor.info,
|
||||
@@ -119,7 +120,7 @@ class FinanceCashFlow extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Grafik Cash Flow ${dailyData.length} Hari Terakhir',
|
||||
context.lang.cash_flow_chart(dailyData.length),
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -136,11 +137,11 @@ class FinanceCashFlow extends StatelessWidget {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildChartLegend('Cash In', AppColor.success),
|
||||
_buildChartLegend(context.lang.cash_in, AppColor.success),
|
||||
const SizedBox(width: 20),
|
||||
_buildChartLegend('Cash Out', AppColor.error),
|
||||
_buildChartLegend(context.lang.cash_out, AppColor.error),
|
||||
const SizedBox(width: 20),
|
||||
_buildChartLegend('Net Flow', AppColor.info),
|
||||
_buildChartLegend(context.lang.net_flow, AppColor.info),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -51,7 +51,7 @@ class FinanceCategory extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Kategori Penjualan',
|
||||
context.lang.sales_category,
|
||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
@@ -60,10 +60,11 @@ class FinanceCategory extends StatelessWidget {
|
||||
|
||||
// Show empty state if no categories
|
||||
if (categories.isEmpty)
|
||||
_buildEmptyState()
|
||||
_buildEmptyState(context)
|
||||
else
|
||||
...sortedCategories.asMap().entries.map(
|
||||
(entry) => _buildCategoryItem(
|
||||
context,
|
||||
entry.value,
|
||||
_calculatePercentage(entry.value.totalRevenue, totalRevenue),
|
||||
_getCategoryColor(entry.key),
|
||||
@@ -75,6 +76,7 @@ class FinanceCategory extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildCategoryItem(
|
||||
BuildContext context,
|
||||
CategoryAnalyticItem category,
|
||||
double percentage,
|
||||
Color color,
|
||||
@@ -111,7 +113,7 @@ class FinanceCategory extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'${category.productCount} produk • ${category.orderCount} pesanan',
|
||||
'${category.productCount} ${context.lang.product} • ${category.orderCount} ${context.lang.orders}',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
@@ -134,7 +136,7 @@ class FinanceCategory extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'${NumberFormat('#,###', 'id_ID').format(category.totalQuantity)} unit',
|
||||
'${NumberFormat('#,###', 'id_ID').format(category.totalQuantity)} ${context.lang.unit}',
|
||||
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
],
|
||||
@@ -161,10 +163,10 @@ class FinanceCategory extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState() {
|
||||
Widget _buildEmptyState(BuildContext context) {
|
||||
return EmptyWidget(
|
||||
title: 'Belum ada data kategori',
|
||||
message: 'Data kategori penjualan akan muncul di sini',
|
||||
title: context.lang.category_no_data,
|
||||
message: context.lang.category_no_data_desc,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -81,21 +81,21 @@ class ProfitLossProduct extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildMetricColumn(
|
||||
'Pendapatan',
|
||||
context.lang.revenue,
|
||||
product.revenue.currencyFormatRp,
|
||||
AppColor.success,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildMetricColumn(
|
||||
'Biaya',
|
||||
context.lang.cost,
|
||||
product.cost.currencyFormatRp,
|
||||
AppColor.error,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildMetricColumn(
|
||||
'Laba Kotor',
|
||||
context.lang.gross_profit,
|
||||
product.grossProfit.currencyFormatRp,
|
||||
AppColor.info,
|
||||
),
|
||||
@@ -109,14 +109,14 @@ class ProfitLossProduct extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildMetricColumn(
|
||||
'Harga Rata-rata',
|
||||
context.lang.average_price,
|
||||
product.averagePrice.currencyFormatRp,
|
||||
AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildMetricColumn(
|
||||
'Laba per Unit',
|
||||
context.lang.profit_per_unit,
|
||||
product.profitPerUnit.currencyFormatRp,
|
||||
AppColor.primary,
|
||||
),
|
||||
|
||||
@@ -46,7 +46,7 @@ class FinanceProfitLoss extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
'Detail Profit & Loss',
|
||||
context.lang.profit_loss_detail,
|
||||
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
@@ -55,7 +55,7 @@ class FinanceProfitLoss extends StatelessWidget {
|
||||
|
||||
// Total Revenue (Penjualan Kotor)
|
||||
_buildPLItem(
|
||||
'Penjualan Kotor',
|
||||
context.lang.gross_sales,
|
||||
data.totalRevenue.currencyFormatRp,
|
||||
AppColor.success,
|
||||
true,
|
||||
@@ -63,7 +63,7 @@ class FinanceProfitLoss extends StatelessWidget {
|
||||
|
||||
// Discount (Diskon & Retur)
|
||||
_buildPLItem(
|
||||
'Diskon & Retur',
|
||||
'${context.lang.discount} & ${context.lang.return_text}',
|
||||
'- ${data.totalDiscount.currencyFormatRp}',
|
||||
AppColor.error,
|
||||
false,
|
||||
@@ -73,7 +73,7 @@ class FinanceProfitLoss extends StatelessWidget {
|
||||
|
||||
// Net Sales (Penjualan Bersih = Total Revenue - Discount)
|
||||
_buildPLItem(
|
||||
'Penjualan Bersih',
|
||||
context.lang.net_sales,
|
||||
(data.totalRevenue - data.totalDiscount).currencyFormatRp,
|
||||
AppColor.textPrimary,
|
||||
true,
|
||||
@@ -84,7 +84,7 @@ class FinanceProfitLoss extends StatelessWidget {
|
||||
|
||||
// Cost of Goods Sold (HPP)
|
||||
_buildPLItem(
|
||||
'HPP (Harga Pokok Penjualan)',
|
||||
'${context.lang.cogs} (${context.lang.cost_of_goods_sold})',
|
||||
'- ${data.totalCost.currencyFormatRp}',
|
||||
AppColor.error,
|
||||
false,
|
||||
@@ -94,7 +94,7 @@ class FinanceProfitLoss extends StatelessWidget {
|
||||
|
||||
// Gross Profit (Laba Kotor)
|
||||
_buildPLItem(
|
||||
'Laba Kotor',
|
||||
context.lang.gross_profit,
|
||||
data.grossProfit.currencyFormatRp,
|
||||
AppColor.success,
|
||||
true,
|
||||
@@ -107,7 +107,7 @@ class FinanceProfitLoss extends StatelessWidget {
|
||||
|
||||
// Operational Cost (Biaya Operasional) - calculated as difference
|
||||
_buildPLItem(
|
||||
'Biaya Operasional',
|
||||
context.lang.operating_costs,
|
||||
'- ${_calculateOperationalCost().currencyFormatRp}',
|
||||
AppColor.error,
|
||||
false,
|
||||
@@ -117,7 +117,7 @@ class FinanceProfitLoss extends StatelessWidget {
|
||||
|
||||
// Net Profit (Laba Bersih)
|
||||
_buildPLItem(
|
||||
'Laba Bersih',
|
||||
context.lang.net_profit,
|
||||
data.netProfit.currencyFormatRp,
|
||||
AppColor.primary,
|
||||
true,
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../router/app_router.gr.dart';
|
||||
import 'feature_tile.dart';
|
||||
@@ -36,25 +37,25 @@ class HomeFeature extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
HomeFeatureTile(
|
||||
title: 'Penjualan',
|
||||
title: context.lang.sales,
|
||||
color: const Color(0xFF4CAF50),
|
||||
icon: LineIcons.receipt,
|
||||
onTap: () => context.router.push(SalesRoute()),
|
||||
),
|
||||
HomeFeatureTile(
|
||||
title: 'Pembelian',
|
||||
title: context.lang.purchase,
|
||||
color: const Color(0xFF2196F3),
|
||||
icon: LineIcons.shoppingCart,
|
||||
onTap: () => context.router.push(PurchaseRoute()),
|
||||
),
|
||||
HomeFeatureTile(
|
||||
title: 'Keuangan',
|
||||
title: context.lang.finance,
|
||||
color: const Color(0xFF8BC34A),
|
||||
icon: LineIcons.moneyCheck,
|
||||
onTap: () => context.router.push(FinanceRoute()),
|
||||
),
|
||||
HomeFeatureTile(
|
||||
title: 'Produk',
|
||||
title: context.lang.product,
|
||||
color: const Color(0xFFFF9800),
|
||||
icon: LineIcons.box,
|
||||
onTap: () => context.router.push(ProductAnalyticRoute()),
|
||||
@@ -66,25 +67,25 @@ class HomeFeature extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
HomeFeatureTile(
|
||||
title: 'Form',
|
||||
title: context.lang.form,
|
||||
color: const Color(0xFFE91E63),
|
||||
icon: LineIcons.fileAlt,
|
||||
onTap: () => context.router.push(DailyTasksFormRoute()),
|
||||
),
|
||||
HomeFeatureTile(
|
||||
title: 'Jadwal',
|
||||
title: context.lang.schedule,
|
||||
color: const Color(0xFF9C27B0),
|
||||
icon: LineIcons.calendar,
|
||||
onTap: () => context.router.push(ScheduleRoute()),
|
||||
),
|
||||
HomeFeatureTile(
|
||||
title: 'Inventaris',
|
||||
title: context.lang.inventory,
|
||||
color: const Color(0xFF00BCD4),
|
||||
icon: LineIcons.archive,
|
||||
onTap: () => context.router.push(InventoryRoute()),
|
||||
),
|
||||
HomeFeatureTile(
|
||||
title: 'Pelanggan',
|
||||
title: context.lang.customer,
|
||||
color: const Color(0xFFFF5722),
|
||||
icon: LineIcons.userPlus,
|
||||
onTap: () => context.router.push(CustomerRoute()),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
@@ -21,30 +22,30 @@ class HomeStats extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
HomeTitle(title: 'Ringkasan Hari Ini'),
|
||||
HomeTitle(title: context.lang.today_summary),
|
||||
const SpaceHeight(20),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: HomeStatsTile(
|
||||
title: 'Pesanan',
|
||||
title: context.lang.order,
|
||||
value: overview.totalOrders.toString(),
|
||||
icon: Icons.receipt_long_rounded,
|
||||
color: AppColor.info,
|
||||
subtitle: 'Hari ini',
|
||||
subtitle: context.lang.today,
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceWidth(16),
|
||||
Expanded(
|
||||
child: HomeStatsTile(
|
||||
title: 'Pelanggan Baru',
|
||||
title: context.lang.new_customer,
|
||||
value: overview.totalCustomers.toString(),
|
||||
icon: Icons.person_add_outlined,
|
||||
color: AppColor.primary,
|
||||
subtitle: overview.totalCustomers < 1
|
||||
? 'Hari ini'
|
||||
: 'bertambah',
|
||||
? context.lang.today
|
||||
: context.lang.increase,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -54,21 +55,21 @@ class HomeStats extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: HomeStatsTile(
|
||||
title: 'Refund',
|
||||
title: context.lang.refund,
|
||||
value: overview.refundedOrders.toString(),
|
||||
icon: LineIcons.alternateExchange,
|
||||
color: AppColor.warning,
|
||||
subtitle: 'Hari ini',
|
||||
subtitle: context.lang.today,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(16),
|
||||
Expanded(
|
||||
child: HomeStatsTile(
|
||||
title: 'Void',
|
||||
title: context.lang.void_text,
|
||||
value: overview.voidedOrders.toString(),
|
||||
icon: Icons.cancel_rounded,
|
||||
color: AppColor.error,
|
||||
subtitle: 'Hari ini',
|
||||
subtitle: context.lang.today,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
@@ -19,12 +20,13 @@ class HomeTopProduct extends StatelessWidget {
|
||||
).copyWith(bottom: 0),
|
||||
child: Column(
|
||||
children: [
|
||||
HomeTitle(title: 'Product Terlaris Hari Ini'),
|
||||
HomeTitle(title: context.lang.today_top_product),
|
||||
SpaceHeight(20),
|
||||
ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: products.length,
|
||||
padding: EdgeInsets.zero,
|
||||
itemBuilder: (context, index) {
|
||||
return HomeTopProductTile(
|
||||
product: products[index],
|
||||
|
||||
@@ -44,7 +44,7 @@ class HomeTopProductTile extends StatelessWidget {
|
||||
// Header Row - Ranking dan Revenue
|
||||
Row(
|
||||
children: [
|
||||
_buildRankingBadge(),
|
||||
_buildRankingBadge(context),
|
||||
const Spacer(),
|
||||
_buildRevenueDisplay(),
|
||||
],
|
||||
@@ -71,7 +71,7 @@ class HomeTopProductTile extends StatelessWidget {
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Metrics dalam Grid 2x2
|
||||
_buildMetricsGrid(),
|
||||
_buildMetricsGrid(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -80,7 +80,7 @@ class HomeTopProductTile extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRankingBadge() {
|
||||
Widget _buildRankingBadge(BuildContext context) {
|
||||
Color badgeColor;
|
||||
IconData icon;
|
||||
|
||||
@@ -115,7 +115,7 @@ class HomeTopProductTile extends StatelessWidget {
|
||||
Icon(icon, color: badgeColor, size: 16),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'Rank #$ranking',
|
||||
'${context.lang.rank} #$ranking',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: badgeColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -157,7 +157,7 @@ class HomeTopProductTile extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMetricsGrid() {
|
||||
Widget _buildMetricsGrid(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -165,14 +165,14 @@ class HomeTopProductTile extends StatelessWidget {
|
||||
children: [
|
||||
_buildMetricCard(
|
||||
icon: Icons.shopping_cart_outlined,
|
||||
label: 'Quantity Sold',
|
||||
label: context.lang.quantity_sold,
|
||||
value: product.quantitySold.toString(),
|
||||
color: AppColor.info,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildMetricCard(
|
||||
icon: Icons.attach_money,
|
||||
label: 'Average Price',
|
||||
label: context.lang.average_price,
|
||||
value: product.averagePrice.round().currencyFormatRp,
|
||||
color: AppColor.success,
|
||||
),
|
||||
@@ -185,14 +185,14 @@ class HomeTopProductTile extends StatelessWidget {
|
||||
children: [
|
||||
_buildMetricCard(
|
||||
icon: Icons.receipt_outlined,
|
||||
label: 'Total Orders',
|
||||
label: context.lang.total_orders,
|
||||
value: product.orderCount.toString(),
|
||||
color: AppColor.warning,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildMetricCard(
|
||||
icon: Icons.trending_up,
|
||||
label: 'Performance',
|
||||
label: context.lang.perfomance,
|
||||
value: 'Top $ranking',
|
||||
color: AppColor.primary,
|
||||
),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../application/analytic/inventory_analytic_loader/inventory_analytic_loader_bloc.dart';
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../../domain/analytic/analytic.dart';
|
||||
import '../../../injection.dart';
|
||||
@@ -95,11 +96,11 @@ class _InventoryPageState extends State<InventoryPage>
|
||||
String getStatusText(String status) {
|
||||
switch (status) {
|
||||
case 'available':
|
||||
return 'Tersedia';
|
||||
return context.lang.available;
|
||||
case 'low_stock':
|
||||
return 'Stok Rendah';
|
||||
return context.lang.low_stock;
|
||||
case 'out_of_stock':
|
||||
return 'Habis';
|
||||
return context.lang.out_of_stock;
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
@@ -189,7 +190,7 @@ class _InventoryPageState extends State<InventoryPage>
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
child: const Row(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
@@ -199,7 +200,7 @@ class _InventoryPageState extends State<InventoryPage>
|
||||
size: 16,
|
||||
),
|
||||
SizedBox(width: 6),
|
||||
Text('Produk'),
|
||||
Text(context.lang.product),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -210,7 +211,7 @@ class _InventoryPageState extends State<InventoryPage>
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
),
|
||||
child: const Row(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
@@ -220,7 +221,7 @@ class _InventoryPageState extends State<InventoryPage>
|
||||
size: 16,
|
||||
),
|
||||
SizedBox(width: 6),
|
||||
Text('Bahan'),
|
||||
Text(context.lang.ingredients),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -254,7 +255,7 @@ class _InventoryPageState extends State<InventoryPage>
|
||||
pinned: true,
|
||||
elevation: 0,
|
||||
backgroundColor: AppColor.primary,
|
||||
flexibleSpace: CustomAppBar(title: 'Inventaris'),
|
||||
flexibleSpace: CustomAppBar(title: context.lang.inventory),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -308,7 +309,7 @@ class _InventoryPageState extends State<InventoryPage>
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
'Total Produk',
|
||||
context.lang.total_products,
|
||||
inventory.totalProducts.toString(),
|
||||
Icons.inventory_2_rounded,
|
||||
AppColor.primary,
|
||||
@@ -317,7 +318,7 @@ class _InventoryPageState extends State<InventoryPage>
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
'Produk Terjual',
|
||||
context.lang.total_sold,
|
||||
inventory.totalSoldProducts.toString(),
|
||||
Icons.check_circle_rounded,
|
||||
AppColor.success,
|
||||
@@ -330,7 +331,7 @@ class _InventoryPageState extends State<InventoryPage>
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
'Stok Rendah',
|
||||
context.lang.low_stock,
|
||||
inventory.lowStockProducts.toString(),
|
||||
Icons.warning_rounded,
|
||||
AppColor.warning,
|
||||
@@ -339,7 +340,7 @@ class _InventoryPageState extends State<InventoryPage>
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
'Stok Kosong',
|
||||
context.lang.zero_stock,
|
||||
inventory.zeroStockProducts.toString(),
|
||||
Icons.error_rounded,
|
||||
AppColor.error,
|
||||
@@ -361,7 +362,7 @@ class _InventoryPageState extends State<InventoryPage>
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
'Total Bahan',
|
||||
context.lang.total_ingredients,
|
||||
inventory.totalIngredients.toString(),
|
||||
Icons.restaurant_menu_rounded,
|
||||
AppColor.primary,
|
||||
@@ -370,7 +371,7 @@ class _InventoryPageState extends State<InventoryPage>
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
'Bahan Terjual',
|
||||
context.lang.total_sold,
|
||||
inventory.totalSoldIngredients.toString(),
|
||||
Icons.check_circle_rounded,
|
||||
AppColor.success,
|
||||
@@ -383,7 +384,7 @@ class _InventoryPageState extends State<InventoryPage>
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
'Stok Kurang',
|
||||
context.lang.low_stock,
|
||||
inventory.lowStockIngredients.toString(),
|
||||
Icons.warning_rounded,
|
||||
AppColor.warning,
|
||||
@@ -392,7 +393,7 @@ class _InventoryPageState extends State<InventoryPage>
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: _buildStatCard(
|
||||
'Habis',
|
||||
context.lang.zero_stock,
|
||||
inventory.zeroStockIngredients.toString(),
|
||||
Icons.error_rounded,
|
||||
AppColor.error,
|
||||
|
||||
@@ -182,7 +182,11 @@ class InventoryProductTile extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
child: Text(
|
||||
_getStatusText(),
|
||||
item.isZeroStock
|
||||
? context.lang.out_of_stock
|
||||
: item.isLowStock
|
||||
? context.lang.low_stock
|
||||
: context.lang.available,
|
||||
style: AppStyle.xs.copyWith(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -202,7 +206,7 @@ class InventoryProductTile extends StatelessWidget {
|
||||
Expanded(
|
||||
child: _buildInfoItem(
|
||||
LineIcons.boxes,
|
||||
'Stok',
|
||||
context.lang.stock,
|
||||
'${NumberFormat('#,###', 'id_ID').format(item.quantity)} pcs',
|
||||
_getQuantityColor(),
|
||||
),
|
||||
@@ -212,7 +216,7 @@ class InventoryProductTile extends StatelessWidget {
|
||||
Expanded(
|
||||
child: _buildInfoItem(
|
||||
LineIcons.dollarSign,
|
||||
'Nilai',
|
||||
context.lang.value_text,
|
||||
_formatCurrencyShort(item.totalValue),
|
||||
AppColor.info,
|
||||
),
|
||||
@@ -259,14 +263,19 @@ class InventoryProductTile extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Stok Menipis',
|
||||
context.lang.low_stock,
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.warning,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Segera reorder minimal ${NumberFormat('#,###', 'id_ID').format(item.reorderLevel)} pcs',
|
||||
context.lang.low_stock_desc(
|
||||
NumberFormat(
|
||||
'#,###',
|
||||
'id_ID',
|
||||
).format(item.reorderLevel),
|
||||
),
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -301,14 +310,14 @@ class InventoryProductTile extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Stok Habis',
|
||||
context.lang.out_of_stock,
|
||||
style: AppStyle.sm.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColor.error,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Produk tidak tersedia untuk dijual',
|
||||
context.lang.out_of_stock_desc,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -330,7 +339,7 @@ class InventoryProductTile extends StatelessWidget {
|
||||
Expanded(
|
||||
child: _buildMovementInfo(
|
||||
LineIcons.arrowUp,
|
||||
'Masuk',
|
||||
context.lang.in_text,
|
||||
'${NumberFormat('#,###', 'id_ID').format(item.totalIn)} pcs',
|
||||
AppColor.success,
|
||||
),
|
||||
@@ -341,7 +350,7 @@ class InventoryProductTile extends StatelessWidget {
|
||||
Expanded(
|
||||
child: _buildMovementInfo(
|
||||
LineIcons.arrowDown,
|
||||
'Keluar',
|
||||
context.lang.out_text,
|
||||
'${NumberFormat('#,###', 'id_ID').format(item.totalOut)} pcs',
|
||||
AppColor.error,
|
||||
),
|
||||
@@ -476,12 +485,6 @@ class InventoryProductTile extends StatelessWidget {
|
||||
return AppColor.textPrimary;
|
||||
}
|
||||
|
||||
String _getStatusText() {
|
||||
if (item.isZeroStock) return 'HABIS';
|
||||
if (item.isLowStock) return 'MINIM';
|
||||
return 'TERSEDIA';
|
||||
}
|
||||
|
||||
IconData _getCategoryIcon() {
|
||||
final category = item.categoryName.toLowerCase();
|
||||
if (category.contains('elektronik') || category.contains('gadget')) {
|
||||
|
||||
@@ -33,8 +33,8 @@ class _MainBottomNavbarState extends State<MainBottomNavbar> {
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: LineIcon(LineIcons.moneyBill),
|
||||
label: 'Order',
|
||||
tooltip: 'Order',
|
||||
label: context.lang.order,
|
||||
tooltip: context.lang.order,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: LineIcon(LineIcons.barChart),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'dart:math' show cos, sin;
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/order/order.dart';
|
||||
import '../../../components/appbar/appbar.dart';
|
||||
@@ -22,33 +23,33 @@ class OrderDetailPage extends StatelessWidget {
|
||||
expandedHeight: 120,
|
||||
pinned: true,
|
||||
backgroundColor: AppColor.primary,
|
||||
flexibleSpace: const CustomAppBar(title: "Detail Pesanan"),
|
||||
flexibleSpace: CustomAppBar(title: context.lang.order_details),
|
||||
elevation: 0,
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
// Order Status Card
|
||||
_buildOrderStatusCard(),
|
||||
_buildOrderStatusCard(context),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Table Visual Card (for dine-in)
|
||||
if (_isDineIn()) _buildTableVisualCard(),
|
||||
if (_isDineIn()) _buildTableVisualCard(context),
|
||||
|
||||
// Order Info Card
|
||||
_buildOrderInfoCard(),
|
||||
_buildOrderInfoCard(context),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Order Items Section
|
||||
_buildOrderItemsSection(),
|
||||
_buildOrderItemsSection(context),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Payment Summary Card
|
||||
_buildPaymentSummaryCard(),
|
||||
_buildPaymentSummaryCard(context),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Payment Methods Section
|
||||
_buildPaymentMethodsSection(),
|
||||
_buildPaymentMethodsSection(context),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
@@ -58,7 +59,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOrderStatusCard() {
|
||||
Widget _buildOrderStatusCard(BuildContext context) {
|
||||
Color statusColor = _getStatusColor(order.status);
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
@@ -123,7 +124,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Order Number',
|
||||
context.lang.order_number,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -157,7 +158,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Status Pesanan',
|
||||
context.lang.order_status,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -197,7 +198,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'Total Amount',
|
||||
context.lang.total_amount,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -222,7 +223,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTableVisualCard() {
|
||||
Widget _buildTableVisualCard(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||
height: 220, // Increased height
|
||||
@@ -284,14 +285,14 @@ class OrderDetailPage extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Makan di Tempat',
|
||||
context.lang.dine_in,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.white.withOpacity(0.9),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Dine In Experience',
|
||||
context.lang.dine_in_experience,
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -347,7 +348,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'MEJA',
|
||||
context.lang.table,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.primary.withOpacity(
|
||||
0.7,
|
||||
@@ -417,7 +418,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'${order.orderItems.length} Items',
|
||||
'${order.orderItems.length} ${context.lang.items}',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.white,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -470,7 +471,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOrderInfoCard() {
|
||||
Widget _buildOrderInfoCard(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
@@ -517,7 +518,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
'Informasi Pesanan',
|
||||
context.lang.order_information,
|
||||
style: AppStyle.xl.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.primary,
|
||||
@@ -527,24 +528,28 @@ class OrderDetailPage extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
if (_shouldShowTableNumber())
|
||||
_buildInfoRow(Icons.table_restaurant, 'Meja', order.tableNumber),
|
||||
_buildInfoRow(
|
||||
Icons.table_restaurant,
|
||||
context.lang.table,
|
||||
order.tableNumber,
|
||||
),
|
||||
_buildInfoRow(
|
||||
Icons.delivery_dining,
|
||||
'Tipe Pesanan',
|
||||
context.lang.order_type,
|
||||
order.orderType,
|
||||
),
|
||||
_buildInfoRow(
|
||||
Icons.payment,
|
||||
'Status Pembayaran',
|
||||
context.lang.payment_status,
|
||||
order.paymentStatus,
|
||||
),
|
||||
_buildInfoRow(
|
||||
Icons.access_time,
|
||||
'Dibuat',
|
||||
context.lang.created,
|
||||
_formatDateTime(order.createdAt),
|
||||
),
|
||||
if (order.notes.isNotEmpty)
|
||||
_buildInfoRow(Icons.note, 'Catatan', order.notes),
|
||||
_buildInfoRow(Icons.note, context.lang.note, order.notes),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -590,7 +595,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOrderItemsSection() {
|
||||
Widget _buildOrderItemsSection(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
@@ -612,7 +617,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'Item Pesanan',
|
||||
context.lang.order_item,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
@@ -629,7 +634,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
'${order.orderItems.length} Item',
|
||||
'${order.orderItems.length} ${context.lang.item}',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -647,7 +652,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
separatorBuilder: (context, index) => const Divider(height: 24),
|
||||
itemBuilder: (context, index) {
|
||||
final item = order.orderItems[index];
|
||||
return _buildOrderItemCard(item);
|
||||
return _buildOrderItemCard(context, item);
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -655,9 +660,9 @@ class OrderDetailPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOrderItemCard(OrderItem item) {
|
||||
Widget _buildOrderItemCard(BuildContext context, OrderItem item) {
|
||||
// Assume we add status to OrderItem model or determine from order status
|
||||
String itemStatus = _getItemStatus(item);
|
||||
String itemStatus = _getItemStatus(context, item);
|
||||
Color itemStatusColor = _getItemStatusColor(itemStatus);
|
||||
|
||||
return Container(
|
||||
@@ -786,7 +791,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
'Diskon: -Rp ${_formatCurrency(item.discountAmount)}',
|
||||
'${context.lang.discount}: -Rp ${_formatCurrency(item.discountAmount)}',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.error,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -811,7 +816,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Total Item',
|
||||
context.lang.total_item,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -845,7 +850,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPaymentSummaryCard() {
|
||||
Widget _buildPaymentSummaryCard(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
@@ -866,26 +871,38 @@ class OrderDetailPage extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Ringkasan Pembayaran',
|
||||
context.lang.payment_summary,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildSummaryRow('Subtotal', order.subtotal),
|
||||
_buildSummaryRow(context.lang.subtotal, order.subtotal),
|
||||
if (order.discountAmount > 0)
|
||||
_buildSummaryRow(
|
||||
'Diskon',
|
||||
context.lang.discount,
|
||||
-order.discountAmount,
|
||||
isDiscount: true,
|
||||
),
|
||||
_buildSummaryRow('Pajak', order.taxAmount),
|
||||
_buildSummaryRow(context.lang.tax, order.taxAmount),
|
||||
const Divider(height: 24),
|
||||
_buildSummaryRow('Total', order.totalAmount, isTotal: true),
|
||||
_buildSummaryRow('Dibayar', order.totalPaid, isSuccess: true),
|
||||
_buildSummaryRow(
|
||||
context.lang.total,
|
||||
order.totalAmount,
|
||||
isTotal: true,
|
||||
),
|
||||
_buildSummaryRow(
|
||||
context.lang.paid,
|
||||
order.totalPaid,
|
||||
isSuccess: true,
|
||||
),
|
||||
if (order.remainingAmount > 0)
|
||||
_buildSummaryRow('Sisa', order.remainingAmount, isError: true),
|
||||
_buildSummaryRow(
|
||||
context.lang.remaining,
|
||||
order.remainingAmount,
|
||||
isError: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -930,7 +947,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPaymentMethodsSection() {
|
||||
Widget _buildPaymentMethodsSection(BuildContext context) {
|
||||
if (order.payments.isEmpty) return const SizedBox();
|
||||
|
||||
return Container(
|
||||
@@ -952,7 +969,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 20, 20, 0),
|
||||
child: Text(
|
||||
'Metode Pembayaran',
|
||||
context.lang.payment_method,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
@@ -1159,7 +1176,7 @@ class OrderDetailPage extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
String _getItemStatus(OrderItem item) {
|
||||
String _getItemStatus(BuildContext context, OrderItem item) {
|
||||
// Logic untuk menentukan status item berdasarkan:
|
||||
// 1. Status order secara keseluruhan
|
||||
// 2. Jika ada field status di OrderItem model
|
||||
@@ -1167,26 +1184,24 @@ class OrderDetailPage extends StatelessWidget {
|
||||
|
||||
switch (order.status.toLowerCase()) {
|
||||
case 'completed':
|
||||
return 'Selesai';
|
||||
return context.lang.completed;
|
||||
case 'pending':
|
||||
return 'Menunggu';
|
||||
return context.lang.pending;
|
||||
case 'cancelled':
|
||||
return 'Dibatalkan';
|
||||
return context.lang.void_text;
|
||||
default:
|
||||
return 'Menunggu';
|
||||
return context.lang.pending;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getItemStatusColor(String status) {
|
||||
switch (status.toLowerCase()) {
|
||||
case 'selesai':
|
||||
case 'completed':
|
||||
return AppColor.success;
|
||||
case 'diproses':
|
||||
case 'pending':
|
||||
return AppColor.warning;
|
||||
case 'dibatalkan':
|
||||
case 'cancelled':
|
||||
return AppColor.error;
|
||||
case 'siap':
|
||||
return AppColor.info;
|
||||
default:
|
||||
return AppColor.primary;
|
||||
}
|
||||
|
||||
@@ -3,15 +3,13 @@ import 'dart:developer';
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
import '../../../../application/order/order_loader/order_loader_bloc.dart';
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../injection.dart';
|
||||
import '../../../components/appbar/appbar.dart';
|
||||
import '../../../components/button/button.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
import '../../../components/widgets/empty_widget.dart';
|
||||
import '../../../router/app_router.gr.dart';
|
||||
import 'widgets/filter_header_delegate.dart';
|
||||
@@ -224,11 +222,10 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
|
||||
pinned: true,
|
||||
backgroundColor: AppColor.primary,
|
||||
centerTitle: false,
|
||||
flexibleSpace: CustomAppBar(title: 'Order', isBack: false),
|
||||
actions: [
|
||||
ActionIconButton(onTap: () {}, icon: LineIcons.filter),
|
||||
SpaceWidth(8),
|
||||
],
|
||||
flexibleSpace: CustomAppBar(
|
||||
title: context.lang.orders,
|
||||
isBack: false,
|
||||
),
|
||||
),
|
||||
|
||||
// Pinned Filter Section
|
||||
@@ -285,7 +282,7 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'${state.orders.length} ${state.status.toLowerCase()} order${state.orders.length != 1 ? 's' : ''}',
|
||||
'${state.orders.length} ${state.status.toLowerCase()} ${context.lang.orders}',
|
||||
style: TextStyle(
|
||||
color: AppColor.textSecondary,
|
||||
fontSize: 14,
|
||||
@@ -300,9 +297,10 @@ class _OrderPageState extends State<OrderPage> with TickerProviderStateMixin {
|
||||
_buildShimmerList()
|
||||
else if (state.orders.isEmpty)
|
||||
EmptyWidget(
|
||||
title: 'Order',
|
||||
message:
|
||||
'No ${state.status.toLowerCase()} orders found',
|
||||
title: context.lang.order,
|
||||
message: context.lang.no_order_with_status(
|
||||
state.status.toLowerCase(),
|
||||
),
|
||||
)
|
||||
else
|
||||
ListView.builder(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../../common/extension/extension.dart';
|
||||
import '../../../../components/field/date_range_picker_field.dart';
|
||||
import 'status_tile.dart';
|
||||
|
||||
@@ -36,7 +37,6 @@ class FilterHeaderDelegate extends SliverPersistentHeaderDelegate {
|
||||
double shrinkOffset,
|
||||
bool overlapsContent,
|
||||
) {
|
||||
print('FilterHeaderDelegate build called'); // Debug log
|
||||
return Container(
|
||||
color: backgroundColor,
|
||||
padding: padding,
|
||||
@@ -50,9 +50,6 @@ class FilterHeaderDelegate extends SliverPersistentHeaderDelegate {
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
onChanged: (start, end) {
|
||||
print(
|
||||
'onChanged called in FilterHeaderDelegate: $start - $end',
|
||||
); // Debug log
|
||||
onDateChanged(start, end);
|
||||
},
|
||||
),
|
||||
@@ -70,7 +67,11 @@ class FilterHeaderDelegate extends SliverPersistentHeaderDelegate {
|
||||
right: index < filterOptions.length - 1 ? 8 : 0,
|
||||
),
|
||||
child: OrderStatusTile(
|
||||
label: option,
|
||||
label: option == "All"
|
||||
? context.lang.all
|
||||
: option == "Completed"
|
||||
? context.lang.completed
|
||||
: context.lang.pending,
|
||||
isSelected: option == selectedFilter,
|
||||
onSelected: (isSelected) {
|
||||
if (isSelected) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../../../../../common/extension/extension.dart';
|
||||
import '../../../../../common/theme/theme.dart';
|
||||
import '../../../../../domain/order/order.dart';
|
||||
|
||||
@@ -46,19 +47,19 @@ class OrderTile extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header Row
|
||||
_buildHeaderRow(),
|
||||
_buildHeaderRow(context),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Order Info
|
||||
_buildOrderInfo(),
|
||||
_buildOrderInfo(context),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Amount Section
|
||||
_buildAmountSection(),
|
||||
_buildAmountSection(context),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Footer with Actions
|
||||
_buildFooterActions(),
|
||||
_buildFooterActions(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -67,7 +68,7 @@ class OrderTile extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeaderRow() {
|
||||
Widget _buildHeaderRow(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@@ -94,12 +95,12 @@ class OrderTile extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
_buildStatusChip(),
|
||||
_buildStatusChip(context),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusChip() {
|
||||
Widget _buildStatusChip(BuildContext context) {
|
||||
Color statusColor;
|
||||
String statusText;
|
||||
IconData statusIcon;
|
||||
@@ -107,27 +108,34 @@ class OrderTile extends StatelessWidget {
|
||||
// Check isVoid and isRefund first for display
|
||||
if (order.isVoid) {
|
||||
statusColor = AppColor.error;
|
||||
statusText = 'Void';
|
||||
statusText = context.lang.void_text;
|
||||
statusIcon = Icons.block;
|
||||
} else if (order.isRefund) {
|
||||
statusColor = AppColor.info;
|
||||
statusText = 'Refunded';
|
||||
statusText = context.lang.refund;
|
||||
statusIcon = Icons.undo;
|
||||
} else {
|
||||
// Handle status values (only pending and completed)
|
||||
switch (order.status.toLowerCase()) {
|
||||
case 'completed':
|
||||
statusColor = AppColor.success;
|
||||
statusText = context.lang.completed;
|
||||
statusIcon = Icons.check_circle;
|
||||
case 'paid':
|
||||
case 'finished':
|
||||
statusColor = AppColor.success;
|
||||
statusText = 'Completed';
|
||||
statusText = context.lang.completed;
|
||||
statusIcon = Icons.check_circle;
|
||||
break;
|
||||
case 'pending':
|
||||
statusColor = AppColor.warning;
|
||||
statusText = context.lang.pending;
|
||||
statusIcon = Icons.schedule;
|
||||
break;
|
||||
case 'waiting':
|
||||
case 'processing':
|
||||
statusColor = AppColor.warning;
|
||||
statusText = 'Pending';
|
||||
statusText = context.lang.pending;
|
||||
statusIcon = Icons.schedule;
|
||||
break;
|
||||
default:
|
||||
@@ -163,7 +171,7 @@ class OrderTile extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOrderInfo() {
|
||||
Widget _buildOrderInfo(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -177,7 +185,7 @@ class OrderTile extends StatelessWidget {
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_getOrderInfoText(),
|
||||
_getOrderInfoText(context),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -198,7 +206,7 @@ class OrderTile extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'${order.orderItems.length} items',
|
||||
'${order.orderItems.length} ${context.lang.items}',
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: AppColor.textSecondary,
|
||||
@@ -239,7 +247,7 @@ class OrderTile extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAmountSection() {
|
||||
Widget _buildAmountSection(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -264,8 +272,8 @@ class OrderTile extends StatelessWidget {
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Total Amount',
|
||||
Text(
|
||||
context.lang.total_amount,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColor.textWhite,
|
||||
@@ -274,7 +282,7 @@ class OrderTile extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Rp ${NumberFormat('#,###').format(order.totalAmount)}',
|
||||
order.totalAmount.currencyFormatRp,
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -284,7 +292,7 @@ class OrderTile extends StatelessWidget {
|
||||
if (order.remainingAmount > 0) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Remaining: Rp ${NumberFormat('#,###').format(order.remainingAmount)}',
|
||||
'${context.lang.remaining}: ${order.remainingAmount.currencyFormatRp}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColor.textWhite,
|
||||
@@ -311,7 +319,7 @@ class OrderTile extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFooterActions() {
|
||||
Widget _buildFooterActions(BuildContext context) {
|
||||
// Don't show anything if order is void or refunded
|
||||
if (order.isVoid || order.isRefund) {
|
||||
return const SizedBox.shrink();
|
||||
@@ -326,7 +334,7 @@ class OrderTile extends StatelessWidget {
|
||||
if (order.payments.isNotEmpty) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Payment: ${_getPaymentMethods()}',
|
||||
'${context.lang.payment}: ${_getPaymentMethods()}',
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
color: AppColor.textLight,
|
||||
@@ -337,59 +345,10 @@ class OrderTile extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
if (order.status.toLowerCase() == 'completed') ...[
|
||||
_buildActionButton(
|
||||
icon: Icons.print,
|
||||
label: 'Print',
|
||||
onPressed: onPrint,
|
||||
color: AppColor.info,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildActionButton(
|
||||
icon: Icons.undo,
|
||||
label: 'Refund',
|
||||
onPressed: onRefund,
|
||||
color: AppColor.warning,
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButton({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required VoidCallback? onPressed,
|
||||
required Color color,
|
||||
}) {
|
||||
return Material(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: InkWell(
|
||||
onTap: onPressed,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 16, color: color),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
IconData _getOrderInfoIcon() {
|
||||
switch (order.orderType.toLowerCase()) {
|
||||
case 'dine in':
|
||||
@@ -406,11 +365,11 @@ class OrderTile extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
String _getOrderInfoText() {
|
||||
String _getOrderInfoText(BuildContext context) {
|
||||
switch (order.orderType.toLowerCase()) {
|
||||
case 'dine in':
|
||||
case 'dine_in':
|
||||
return 'Table ${order.tableNumber.isNotEmpty ? order.tableNumber : 'N/A'}';
|
||||
return '${context.lang.table} ${order.tableNumber.isNotEmpty ? order.tableNumber : 'N/A'}';
|
||||
case 'takeaway':
|
||||
case 'take_away':
|
||||
case 'pickup':
|
||||
@@ -419,8 +378,8 @@ class OrderTile extends StatelessWidget {
|
||||
return 'Delivery Order';
|
||||
default:
|
||||
return order.tableNumber.isNotEmpty
|
||||
? 'Table ${order.tableNumber}'
|
||||
: 'Order ${order.orderNumber}';
|
||||
? '${context.lang.table} ${order.tableNumber}'
|
||||
: context.lang.order;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,12 @@ import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../application/outlet/current_outlet_loader/current_outlet_loader_bloc.dart';
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../../domain/outlet/outlet.dart';
|
||||
import '../../../injection.dart';
|
||||
import '../../components/appbar/appbar.dart';
|
||||
import '../../components/spacer/spacer.dart';
|
||||
|
||||
// Outlet Information Page
|
||||
@RoutePage()
|
||||
@@ -79,14 +81,14 @@ class _OutletInformationPageState extends State<OutletInformationPage>
|
||||
expandedHeight: 120.0,
|
||||
floating: false,
|
||||
pinned: true,
|
||||
flexibleSpace: CustomAppBar(title: 'Outlet Information'),
|
||||
flexibleSpace: CustomAppBar(title: context.lang.outlet_information),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: _buildContent(),
|
||||
child: _buildContent(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -95,7 +97,7 @@ class _OutletInformationPageState extends State<OutletInformationPage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent() {
|
||||
Widget _buildContent(BuildContext context) {
|
||||
return BlocBuilder<CurrentOutletLoaderBloc, CurrentOutletLoaderState>(
|
||||
builder: (context, state) {
|
||||
return Padding(
|
||||
@@ -105,15 +107,11 @@ class _OutletInformationPageState extends State<OutletInformationPage>
|
||||
children: [
|
||||
_buildHeaderCard(state.outlet),
|
||||
const SizedBox(height: 20),
|
||||
_buildInfoSection(state.outlet),
|
||||
_buildInfoSection(context, state.outlet),
|
||||
const SizedBox(height: 20),
|
||||
_buildContactSection(state.outlet),
|
||||
_buildBusinessSection(context, state.outlet),
|
||||
const SizedBox(height: 20),
|
||||
_buildBusinessSection(state.outlet),
|
||||
const SizedBox(height: 20),
|
||||
_buildStatusSection(state.outlet),
|
||||
const SizedBox(height: 20),
|
||||
_buildTimestampSection(state.outlet),
|
||||
_buildStatusSection(context, state.outlet),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -190,62 +188,65 @@ class _OutletInformationPageState extends State<OutletInformationPage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoSection(Outlet outlet) {
|
||||
Widget _buildInfoSection(BuildContext context, Outlet outlet) {
|
||||
return _buildAnimatedCard(
|
||||
delay: 200,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSectionTitle('General Information', Icons.info_outline),
|
||||
const SizedBox(height: 16),
|
||||
_buildInfoRow('Outlet ID', outlet.id, Icons.fingerprint),
|
||||
_buildSectionTitle(
|
||||
context.lang.outlet_information,
|
||||
Icons.info_outline,
|
||||
),
|
||||
SpaceHeight(20),
|
||||
_buildInfoRow(
|
||||
'Organization ID',
|
||||
outlet.organizationId,
|
||||
Icons.business,
|
||||
context.lang.address,
|
||||
outlet.address,
|
||||
Icons.location_on,
|
||||
),
|
||||
_buildInfoRow(
|
||||
context.lang.phone_number,
|
||||
outlet.phoneNumber,
|
||||
Icons.phone,
|
||||
),
|
||||
_buildInfoRow('Address', outlet.address, Icons.location_on),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContactSection(Outlet outlet) {
|
||||
return _buildAnimatedCard(
|
||||
delay: 400,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSectionTitle('Contact Information', Icons.contact_phone),
|
||||
const SizedBox(height: 16),
|
||||
_buildInfoRow('Phone Number', outlet.phoneNumber, Icons.phone),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBusinessSection(Outlet outlet) {
|
||||
Widget _buildBusinessSection(BuildContext context, Outlet outlet) {
|
||||
return _buildAnimatedCard(
|
||||
delay: 600,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSectionTitle('Business Settings', Icons.settings_applications),
|
||||
_buildSectionTitle(
|
||||
context.lang.business_settings,
|
||||
Icons.settings_applications,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildInfoRow('Currency', outlet.currency, Icons.monetization_on),
|
||||
_buildInfoRow('Tax Rate', '${outlet.taxRate}%', Icons.percent),
|
||||
_buildInfoRow(
|
||||
context.lang.currency,
|
||||
outlet.currency,
|
||||
Icons.monetization_on,
|
||||
),
|
||||
_buildInfoRow(
|
||||
context.lang.tax_rate,
|
||||
'${outlet.taxRate}%',
|
||||
Icons.percent,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusSection(Outlet outlet) {
|
||||
Widget _buildStatusSection(BuildContext context, Outlet outlet) {
|
||||
return _buildAnimatedCard(
|
||||
delay: 800,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSectionTitle('Status', Icons.toggle_on),
|
||||
_buildSectionTitle(context.lang.status_text, Icons.toggle_on),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
@@ -272,7 +273,7 @@ class _OutletInformationPageState extends State<OutletInformationPage>
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
outlet.isActive ? 'Active' : 'Inactive',
|
||||
outlet.isActive ? context.lang.active : context.lang.inactive,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: outlet.isActive ? AppColor.success : AppColor.error,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -286,29 +287,6 @@ class _OutletInformationPageState extends State<OutletInformationPage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTimestampSection(Outlet outlet) {
|
||||
return _buildAnimatedCard(
|
||||
delay: 1000,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSectionTitle('Timestamps', Icons.schedule),
|
||||
const SizedBox(height: 16),
|
||||
_buildInfoRow(
|
||||
'Created At',
|
||||
_formatDateTime(outlet.createdAt),
|
||||
Icons.add_circle_outline,
|
||||
),
|
||||
_buildInfoRow(
|
||||
'Updated At',
|
||||
_formatDateTime(outlet.updatedAt),
|
||||
Icons.update,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAnimatedCard({required Widget child, required int delay}) {
|
||||
return TweenAnimationBuilder<double>(
|
||||
duration: Duration(milliseconds: 600 + delay),
|
||||
@@ -396,8 +374,4 @@ class _OutletInformationPageState extends State<OutletInformationPage>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDateTime(DateTime dateTime) {
|
||||
return '${dateTime.day}/${dateTime.month}/${dateTime.year} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../application/analytic/product_analytic_loader/product_analytic_loader_bloc.dart';
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
import '../../../../injection.dart';
|
||||
@@ -136,18 +137,7 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
|
||||
pinned: true,
|
||||
elevation: 0,
|
||||
backgroundColor: AppColor.primary,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: AppColor.primaryGradient,
|
||||
),
|
||||
),
|
||||
child: const CustomAppBar(title: "Analisis Produk"),
|
||||
),
|
||||
),
|
||||
flexibleSpace: CustomAppBar(title: context.lang.product_analytic),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -214,7 +204,7 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
|
||||
Expanded(
|
||||
child: _buildSummaryCard(
|
||||
icon: Icons.monetization_on,
|
||||
title: 'Total Pendapatan',
|
||||
title: context.lang.total_revenue,
|
||||
value: _formatCurrency(stats.totalRevenue),
|
||||
color: AppColor.success,
|
||||
),
|
||||
@@ -223,7 +213,7 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
|
||||
Expanded(
|
||||
child: _buildSummaryCard(
|
||||
icon: Icons.shopping_cart,
|
||||
title: 'Total Terjual',
|
||||
title: context.lang.total_sold,
|
||||
value: _formatNumber(stats.totalQuantity),
|
||||
color: AppColor.info,
|
||||
),
|
||||
@@ -236,7 +226,7 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
|
||||
Expanded(
|
||||
child: _buildSummaryCard(
|
||||
icon: Icons.receipt_long,
|
||||
title: 'Total Pesanan',
|
||||
title: context.lang.total_orders,
|
||||
value: _formatNumber(stats.totalOrders),
|
||||
color: AppColor.warning,
|
||||
),
|
||||
@@ -245,7 +235,7 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
|
||||
Expanded(
|
||||
child: _buildSummaryCard(
|
||||
icon: Icons.trending_up,
|
||||
title: 'Rata-rata Nilai',
|
||||
title: context.lang.average_price,
|
||||
value: _formatCurrency(stats.averageOrderValue),
|
||||
color: AppColor.primary,
|
||||
),
|
||||
@@ -541,7 +531,7 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
|
||||
Icon(Icons.trending_up, color: AppColor.success, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Pendapatan',
|
||||
context.lang.revenue,
|
||||
style: _getTextStyle(
|
||||
AppStyle.sm,
|
||||
color: AppColor.success,
|
||||
@@ -552,7 +542,7 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_formatCurrency(product.revenue.toDouble()),
|
||||
product.revenue.currencyFormatRp,
|
||||
style: _getTextStyle(
|
||||
AppStyle.xl,
|
||||
color: AppColor.success,
|
||||
@@ -570,14 +560,14 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
|
||||
_buildStatCard(
|
||||
icon: Icons.shopping_cart_outlined,
|
||||
value: '${product.quantitySold}',
|
||||
label: 'Terjual',
|
||||
label: context.lang.sold,
|
||||
color: AppColor.info,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildStatCard(
|
||||
icon: Icons.receipt_outlined,
|
||||
value: '${product.orderCount}',
|
||||
label: 'Pesanan',
|
||||
label: context.lang.orders,
|
||||
color: AppColor.warning,
|
||||
),
|
||||
],
|
||||
@@ -645,7 +635,7 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Harga Rata-rata',
|
||||
context.lang.average_price,
|
||||
style: _getTextStyle(
|
||||
AppStyle.sm,
|
||||
color: AppColor.textSecondary,
|
||||
@@ -653,7 +643,7 @@ class _ProductAnalyticPageState extends State<ProductAnalyticPage>
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_formatCurrency(product.averagePrice),
|
||||
product.averagePrice.round().currencyFormatRp,
|
||||
style: _getTextStyle(
|
||||
AppStyle.sm,
|
||||
color: _getCategoryColor(product.categoryName),
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:shimmer/shimmer.dart';
|
||||
|
||||
import '../../../../application/category/category_loader/category_loader_bloc.dart';
|
||||
import '../../../../application/product/product_loader/product_loader_bloc.dart';
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/category/category.dart';
|
||||
import '../../../../domain/product/product.dart';
|
||||
@@ -83,7 +84,7 @@ class _ProductPageState extends State<ProductPage>
|
||||
child: CustomScrollView(
|
||||
controller: scrollController,
|
||||
slivers: [
|
||||
_buildSliverAppBar(),
|
||||
_buildSliverAppBar(context),
|
||||
_buildCategoryFilter(),
|
||||
_buildProductContent(state),
|
||||
],
|
||||
@@ -95,13 +96,13 @@ class _ProductPageState extends State<ProductPage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSliverAppBar() {
|
||||
Widget _buildSliverAppBar(BuildContext context) {
|
||||
return SliverAppBar(
|
||||
expandedHeight: 120.0,
|
||||
floating: false,
|
||||
pinned: true,
|
||||
elevation: 0,
|
||||
flexibleSpace: CustomAppBar(title: 'Produk'),
|
||||
flexibleSpace: CustomAppBar(title: context.lang.product),
|
||||
actions: [
|
||||
ActionIconButton(onTap: () {}, icon: LineIcons.search),
|
||||
ActionIconButton(
|
||||
|
||||
+4
-3
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../../application/user/change_password_form/change_password_form_bloc.dart';
|
||||
import '../../../../../common/extension/extension.dart';
|
||||
import '../../../../../common/theme/theme.dart';
|
||||
import '../../../../../injection.dart';
|
||||
import '../../../../components/appbar/appbar.dart';
|
||||
@@ -39,7 +40,7 @@ class _ProfileChangePasswordPageState extends State<ProfileChangePasswordPage> {
|
||||
(f) => AppFlushbar.showUserFailureToast(context, f),
|
||||
(user) {
|
||||
if (context.mounted) {
|
||||
AppFlushbar.showSuccess(context, 'Password changed');
|
||||
AppFlushbar.showSuccess(context, context.lang.password_changed);
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
context.router.back();
|
||||
});
|
||||
@@ -59,7 +60,7 @@ class _ProfileChangePasswordPageState extends State<ProfileChangePasswordPage> {
|
||||
pinned: true,
|
||||
elevation: 0,
|
||||
backgroundColor: AppColor.primary,
|
||||
flexibleSpace: CustomAppBar(title: 'Change Password'),
|
||||
flexibleSpace: CustomAppBar(title: context.lang.change_password),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child:
|
||||
@@ -83,7 +84,7 @@ class _ProfileChangePasswordPageState extends State<ProfileChangePasswordPage> {
|
||||
ChangePasswordNewPassword(),
|
||||
SpaceHeight(24),
|
||||
AppElevatedButton(
|
||||
text: 'Save',
|
||||
text: context.lang.save,
|
||||
isLoading: state.isSubmitting,
|
||||
onPressed: () {
|
||||
context.read<ChangePasswordFormBloc>().add(
|
||||
|
||||
+4
-3
@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import '../../../../../../application/user/change_password_form/change_password_form_bloc.dart';
|
||||
import '../../../../../../common/extension/extension.dart';
|
||||
import '../../../../../components/field/field.dart';
|
||||
|
||||
class ChangePasswordCurrentPassword extends StatelessWidget {
|
||||
@@ -11,9 +12,9 @@ class ChangePasswordCurrentPassword extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppPasswordTextFormField(
|
||||
title: 'Current Password',
|
||||
title: context.lang.current_password,
|
||||
prefixIcon: LineIcons.lock,
|
||||
hintText: 'Please enter your current password',
|
||||
hintText: context.lang.current_password_placeholder,
|
||||
onChanged: (value) => context.read<ChangePasswordFormBloc>().add(
|
||||
ChangePasswordFormEvent.currentPasswordChanged(value),
|
||||
),
|
||||
@@ -23,7 +24,7 @@ class ChangePasswordCurrentPassword extends StatelessWidget {
|
||||
.state
|
||||
.currentPassword
|
||||
.isEmpty) {
|
||||
return 'Please enter your current password';
|
||||
return context.lang.current_password_placeholder;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
+5
-4
@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import '../../../../../../application/user/change_password_form/change_password_form_bloc.dart';
|
||||
import '../../../../../../common/extension/extension.dart';
|
||||
import '../../../../../components/field/field.dart';
|
||||
|
||||
class ChangePasswordNewPassword extends StatelessWidget {
|
||||
@@ -11,20 +12,20 @@ class ChangePasswordNewPassword extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppPasswordTextFormField(
|
||||
title: 'New Password',
|
||||
title: context.lang.new_password,
|
||||
prefixIcon: LineIcons.lock,
|
||||
hintText: 'Please enter your new password',
|
||||
hintText: context.lang.new_password_placeholder,
|
||||
onChanged: (value) => context.read<ChangePasswordFormBloc>().add(
|
||||
ChangePasswordFormEvent.newPasswordChanged(value),
|
||||
),
|
||||
validator: (value) {
|
||||
if (context.read<ChangePasswordFormBloc>().state.newPassword.isEmpty) {
|
||||
return 'Please enter your new password';
|
||||
return context.lang.new_password_placeholder;
|
||||
}
|
||||
|
||||
if (context.read<ChangePasswordFormBloc>().state.newPassword ==
|
||||
context.read<ChangePasswordFormBloc>().state.currentPassword) {
|
||||
return 'New password cannot be same as current password';
|
||||
return context.lang.new_password_not_same;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../../application/auth/auth_bloc.dart';
|
||||
import '../../../../../application/user/user_edit_form/user_edit_form_bloc.dart';
|
||||
import '../../../../../common/extension/extension.dart';
|
||||
import '../../../../../common/theme/theme.dart';
|
||||
import '../../../../../domain/user/user.dart';
|
||||
import '../../../../../injection.dart';
|
||||
@@ -64,7 +65,7 @@ class _ProfileEditPageState extends State<ProfileEditPage> {
|
||||
pinned: true,
|
||||
elevation: 0,
|
||||
backgroundColor: AppColor.primary,
|
||||
flexibleSpace: CustomAppBar(title: 'Profile Edit'),
|
||||
flexibleSpace: CustomAppBar(title: context.lang.edit_profile),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: BlocBuilder<UserEditFormBloc, UserEditFormState>(
|
||||
@@ -85,7 +86,7 @@ class _ProfileEditPageState extends State<ProfileEditPage> {
|
||||
ProfileEditNameField(controller: nameController),
|
||||
SpaceHeight(24),
|
||||
AppElevatedButton(
|
||||
text: 'Save',
|
||||
text: context.lang.save,
|
||||
isLoading: state.isSubmitting,
|
||||
onPressed: () {
|
||||
context.read<UserEditFormBloc>().add(
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import '../../../../../../application/user/user_edit_form/user_edit_form_bloc.dart';
|
||||
import '../../../../../../common/extension/extension.dart';
|
||||
import '../../../../../components/field/field.dart';
|
||||
|
||||
class ProfileEditNameField extends StatelessWidget {
|
||||
@@ -12,9 +13,9 @@ class ProfileEditNameField extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppTextFormField(
|
||||
title: 'Name',
|
||||
title: context.lang.name,
|
||||
prefixIcon: LineIcons.user,
|
||||
hintText: 'Please enter your name',
|
||||
hintText: context.lang.name_placeholder,
|
||||
controller: controller,
|
||||
onChanged: (value) {
|
||||
context.read<UserEditFormBloc>().add(
|
||||
@@ -23,7 +24,7 @@ class ProfileEditNameField extends StatelessWidget {
|
||||
},
|
||||
validator: (value) {
|
||||
if (context.read<UserEditFormBloc>().state.name.isEmpty) {
|
||||
return 'Please enter your name';
|
||||
return context.lang.name_placeholder;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:loader_overlay/loader_overlay.dart';
|
||||
|
||||
import '../../../application/auth/auth_bloc.dart';
|
||||
import '../../../application/auth/logout_form/logout_form_bloc.dart';
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../../injection.dart';
|
||||
import '../../components/button/button.dart';
|
||||
@@ -89,7 +90,7 @@ class ProfilePage extends StatelessWidget implements AutoRouteWrapper {
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Profile',
|
||||
context.lang.profile,
|
||||
style: AppStyle.xl.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 18,
|
||||
|
||||
@@ -34,7 +34,7 @@ class ProfileAccountInfo extends StatelessWidget {
|
||||
Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text(
|
||||
'Account Information',
|
||||
context.lang.account_information,
|
||||
style: AppStyle.xl.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -45,7 +45,7 @@ class ProfileAccountInfo extends StatelessWidget {
|
||||
|
||||
ProfileTile(
|
||||
icon: LineIcons.envelope,
|
||||
title: 'Email',
|
||||
title: context.lang.email,
|
||||
subtitle: user.email,
|
||||
showArrow: false,
|
||||
),
|
||||
@@ -54,7 +54,7 @@ class ProfileAccountInfo extends StatelessWidget {
|
||||
|
||||
ProfileTile(
|
||||
icon: LineIcons.calendarAlt,
|
||||
title: 'Member Since',
|
||||
title: context.lang.member_since,
|
||||
subtitle: user.createdAt.toDate,
|
||||
showArrow: false,
|
||||
),
|
||||
@@ -62,15 +62,15 @@ class ProfileAccountInfo extends StatelessWidget {
|
||||
|
||||
ProfileTile(
|
||||
icon: LineIcons.userEdit,
|
||||
title: 'Ubah Profil',
|
||||
subtitle: 'Ubah profil kamu',
|
||||
title: context.lang.edit_profile,
|
||||
subtitle: context.lang.edit_profile_desc,
|
||||
onTap: () => context.router.push(ProfileEditRoute(user: user)),
|
||||
),
|
||||
ProfileDivider(),
|
||||
ProfileTile(
|
||||
icon: LineIcons.lock,
|
||||
title: 'Change Password',
|
||||
subtitle: 'Change your password',
|
||||
title: context.lang.change_password,
|
||||
subtitle: context.lang.change_password_desc,
|
||||
onTap: () => context.router.push(ProfileChangePasswordRoute()),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../router/app_router.gr.dart';
|
||||
import 'profile_tile.dart';
|
||||
@@ -37,7 +38,7 @@ class _ProfileAppSettingState extends State<ProfileAppSetting> {
|
||||
Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text(
|
||||
'App Settings',
|
||||
context.lang.app_settings,
|
||||
style: AppStyle.xl.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -74,8 +75,8 @@ class _ProfileAppSettingState extends State<ProfileAppSetting> {
|
||||
// ProfileDivider(),
|
||||
ProfileTile(
|
||||
icon: Icons.language_outlined,
|
||||
title: 'Language',
|
||||
subtitle: 'English (US)',
|
||||
title: context.lang.language,
|
||||
subtitle: context.lang.language_desc,
|
||||
onTap: () => context.router.push(LanguageRoute()),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../router/app_router.gr.dart';
|
||||
import 'divider.dart';
|
||||
@@ -30,7 +31,7 @@ class ProfileBusinessSetting extends StatelessWidget {
|
||||
Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text(
|
||||
'Business Settings',
|
||||
context.lang.business_settings,
|
||||
style: AppStyle.xl.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -41,8 +42,8 @@ class ProfileBusinessSetting extends StatelessWidget {
|
||||
|
||||
ProfileTile(
|
||||
icon: Icons.business_outlined,
|
||||
title: 'Outlet Information',
|
||||
subtitle: 'Manage your Outlet details',
|
||||
title: context.lang.outlet_information,
|
||||
subtitle: context.lang.outlet_informatio_desc,
|
||||
onTap: () => context.router.push(OutletInformationRoute()),
|
||||
),
|
||||
|
||||
@@ -50,8 +51,8 @@ class ProfileBusinessSetting extends StatelessWidget {
|
||||
|
||||
ProfileTile(
|
||||
icon: Icons.people_outline,
|
||||
title: 'Staff Management',
|
||||
subtitle: 'Manage employees and permissions',
|
||||
title: context.lang.staff_management,
|
||||
subtitle: context.lang.staff_management_desc,
|
||||
onTap: () => context.router.push(ComingSoonRoute()),
|
||||
),
|
||||
|
||||
@@ -59,16 +60,16 @@ class ProfileBusinessSetting extends StatelessWidget {
|
||||
|
||||
ProfileTile(
|
||||
icon: Icons.inventory_2_outlined,
|
||||
title: 'Product',
|
||||
subtitle: 'Manage your products',
|
||||
title: context.lang.products,
|
||||
subtitle: context.lang.manage_your_products,
|
||||
onTap: () => context.router.push(ProductRoute()),
|
||||
),
|
||||
ProfileDivider(),
|
||||
|
||||
ProfileTile(
|
||||
icon: Icons.download_outlined,
|
||||
title: 'Download Report',
|
||||
subtitle: 'Download your sales or inventory report',
|
||||
title: context.lang.download_report,
|
||||
subtitle: context.lang.download_report_desc,
|
||||
onTap: () => context.router.push(DownloadReportRoute()),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../../application/auth/logout_form/logout_form_bloc.dart';
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
import 'profile_tile.dart';
|
||||
|
||||
class ProfileDangerZone extends StatelessWidget {
|
||||
@@ -28,27 +28,10 @@ class ProfileDangerZone extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.warning_outlined, color: AppColor.error, size: 20),
|
||||
const SpaceWidth(8),
|
||||
Text(
|
||||
'Danger Zone',
|
||||
style: AppStyle.xl.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.error,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
ProfileTile(
|
||||
icon: Icons.logout,
|
||||
title: 'Logout',
|
||||
subtitle: 'Sign out from your account',
|
||||
title: context.lang.logout,
|
||||
subtitle: context.lang.logout_desc,
|
||||
iconColor: AppColor.error,
|
||||
textColor: AppColor.error,
|
||||
onTap: () {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../router/app_router.gr.dart';
|
||||
import 'divider.dart';
|
||||
@@ -30,7 +31,7 @@ class ProfileSupport extends StatelessWidget {
|
||||
Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text(
|
||||
'Support',
|
||||
context.lang.support,
|
||||
style: AppStyle.xl.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -41,8 +42,8 @@ class ProfileSupport extends StatelessWidget {
|
||||
|
||||
ProfileTile(
|
||||
icon: Icons.help_outline,
|
||||
title: 'Help Center',
|
||||
subtitle: 'Get help and support',
|
||||
title: context.lang.help_center,
|
||||
subtitle: context.lang.help_center_desc,
|
||||
onTap: () => context.router.push(ComingSoonRoute()),
|
||||
),
|
||||
|
||||
@@ -60,8 +61,8 @@ class ProfileSupport extends StatelessWidget {
|
||||
|
||||
ProfileTile(
|
||||
icon: Icons.info_outline,
|
||||
title: 'About',
|
||||
subtitle: 'App version and information',
|
||||
title: context.lang.about,
|
||||
subtitle: context.lang.about_desc,
|
||||
onTap: () => context.router.push(AboutAppRoute()),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../components/appbar/appbar.dart';
|
||||
import 'widgets/purchase_tile.dart';
|
||||
@@ -93,7 +94,7 @@ class _PurchasePageState extends State<PurchasePage>
|
||||
elevation: 0,
|
||||
backgroundColor: AppColor.primary,
|
||||
|
||||
flexibleSpace: CustomAppBar(title: 'Pembelian'),
|
||||
flexibleSpace: CustomAppBar(title: context.lang.purchase),
|
||||
),
|
||||
|
||||
// Stats Cards
|
||||
@@ -105,7 +106,7 @@ class _PurchasePageState extends State<PurchasePage>
|
||||
children: [
|
||||
Expanded(
|
||||
child: PurchaseStatCard(
|
||||
title: 'Total Pembelian',
|
||||
title: context.lang.total_purchase,
|
||||
value: 'Rp 8.340.000',
|
||||
icon: LineIcons.shoppingCart,
|
||||
iconColor: AppColor.success,
|
||||
@@ -115,8 +116,8 @@ class _PurchasePageState extends State<PurchasePage>
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: PurchaseStatCard(
|
||||
title: 'Pending Order',
|
||||
value: '3 Orders',
|
||||
title: context.lang.pending_order,
|
||||
value: '3 ${context.lang.orders}',
|
||||
icon: LineIcons.clock,
|
||||
iconColor: AppColor.warning,
|
||||
cardAnimation: cardAnimation,
|
||||
@@ -139,7 +140,7 @@ class _PurchasePageState extends State<PurchasePage>
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Riwayat Pembelian',
|
||||
context.lang.history_purchase,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
@@ -154,7 +155,7 @@ class _PurchasePageState extends State<PurchasePage>
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
'${purchaseData.length} Orders',
|
||||
'${purchaseData.length} ${context.lang.orders}',
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
),
|
||||
@@ -220,20 +221,6 @@ class _PurchasePageState extends State<PurchasePage>
|
||||
const SliverToBoxAdapter(child: SizedBox(height: 80)),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
// Navigate to create new purchase
|
||||
},
|
||||
backgroundColor: AppColor.secondary,
|
||||
icon: const Icon(LineIcons.plus, color: AppColor.textWhite),
|
||||
label: Text(
|
||||
'Buat Pembelian',
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
|
||||
class PurchaseTile extends StatelessWidget {
|
||||
@@ -219,7 +220,7 @@ class PurchaseTile extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${purchase['items']} items',
|
||||
'${purchase['items']} ${context.lang.items}',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.secondary,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -241,7 +242,7 @@ class PurchaseTile extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Total Pembelian',
|
||||
context.lang.total_purchase,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import '../../../application/analytic/dashboard_analytic_loader/dashboard_analytic_loader_bloc.dart';
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../../injection.dart';
|
||||
import '../../components/appbar/appbar.dart';
|
||||
import '../../components/button/button.dart';
|
||||
import '../../components/field/date_range_picker_field.dart';
|
||||
import '../../components/spacer/spacer.dart';
|
||||
import 'widgets/payment_method.dart';
|
||||
@@ -117,17 +116,17 @@ class _ReportPageState extends State<ReportPage> with TickerProviderStateMixin {
|
||||
backgroundColor: AppColor.primary,
|
||||
centerTitle: false,
|
||||
flexibleSpace: CustomAppBar(
|
||||
title: 'Laporan',
|
||||
title: context.lang.report,
|
||||
isBack: false,
|
||||
),
|
||||
actions: [
|
||||
ActionIconButton(
|
||||
onTap: () {},
|
||||
icon: LineIcons.download,
|
||||
),
|
||||
ActionIconButton(onTap: () {}, icon: LineIcons.filter),
|
||||
SpaceWidth(8),
|
||||
],
|
||||
// actions: [
|
||||
// ActionIconButton(
|
||||
// onTap: () {},
|
||||
// icon: LineIcons.download,
|
||||
// ),
|
||||
// ActionIconButton(onTap: () {}, icon: LineIcons.filter),
|
||||
// SpaceWidth(8),
|
||||
// ],
|
||||
),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
|
||||
@@ -57,7 +57,7 @@ class ReportPaymentMethod extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Payment Methods',
|
||||
context.lang.payment_methods,
|
||||
style: AppStyle.xl.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
@@ -65,7 +65,7 @@ class ReportPaymentMethod extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Revenue breakdown by payment method',
|
||||
context.lang.payment_methods_desc,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
@@ -80,7 +80,7 @@ class ReportPaymentMethod extends StatelessWidget {
|
||||
|
||||
// Payment Method List
|
||||
if (paymentMethods.isEmpty)
|
||||
_buildEmptyState()
|
||||
_buildEmptyState(context)
|
||||
else
|
||||
ListView.separated(
|
||||
shrinkWrap: true,
|
||||
@@ -190,12 +190,12 @@ class ReportPaymentMethod extends StatelessWidget {
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Stats row with better spacing
|
||||
_buildStatsSection(method),
|
||||
_buildStatsSection(context, method),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Enhanced progress bar section
|
||||
_buildProgressSection(method, index),
|
||||
_buildProgressSection(context, method, index),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -410,7 +410,10 @@ class ReportPaymentMethod extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatsSection(DashboardPaymentMethod method) {
|
||||
Widget _buildStatsSection(
|
||||
BuildContext context,
|
||||
DashboardPaymentMethod method,
|
||||
) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
@@ -426,7 +429,7 @@ class ReportPaymentMethod extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Total Revenue',
|
||||
context.lang.total_revenue,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -457,7 +460,7 @@ class ReportPaymentMethod extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'Orders',
|
||||
context.lang.orders,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -480,7 +483,11 @@ class ReportPaymentMethod extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProgressSection(DashboardPaymentMethod method, int index) {
|
||||
Widget _buildProgressSection(
|
||||
BuildContext context,
|
||||
DashboardPaymentMethod method,
|
||||
int index,
|
||||
) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -488,7 +495,7 @@ class ReportPaymentMethod extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Revenue Share',
|
||||
context.lang.revenue_share,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -560,11 +567,10 @@ class ReportPaymentMethod extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState() {
|
||||
Widget _buildEmptyState(BuildContext context) {
|
||||
return EmptyWidget(
|
||||
title: 'No Payment Methods',
|
||||
message:
|
||||
'Payment method data will appear here once transactions are made',
|
||||
title: context.lang.no_payment_methods,
|
||||
message: context.lang.no_payment_methods_desc,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class ReportQuickStats extends StatelessWidget {
|
||||
return Transform.scale(
|
||||
scale: value,
|
||||
child: ReportStatTile(
|
||||
title: 'Total Orders',
|
||||
title: context.lang.total_orders,
|
||||
value: overview.totalOrders.toString(),
|
||||
icon: Icons.receipt_long,
|
||||
color: AppColor.info,
|
||||
@@ -43,7 +43,7 @@ class ReportQuickStats extends StatelessWidget {
|
||||
return Transform.scale(
|
||||
scale: value,
|
||||
child: ReportStatTile(
|
||||
title: 'Average Order',
|
||||
title: context.lang.average_price,
|
||||
value: overview.averageOrderValue
|
||||
.round()
|
||||
.currencyFormatRp,
|
||||
@@ -70,7 +70,7 @@ class ReportQuickStats extends StatelessWidget {
|
||||
return Transform.scale(
|
||||
scale: value,
|
||||
child: ReportStatTile(
|
||||
title: 'Customers',
|
||||
title: context.lang.customer,
|
||||
value: overview.totalCustomers.toString(),
|
||||
icon: Icons.people,
|
||||
color: AppColor.success,
|
||||
@@ -89,7 +89,8 @@ class ReportQuickStats extends StatelessWidget {
|
||||
return Transform.scale(
|
||||
scale: value,
|
||||
child: ReportStatTile(
|
||||
title: 'Void + Refund',
|
||||
title:
|
||||
'${context.lang.void_text} + ${context.lang.refund}',
|
||||
value: (overview.voidedOrders + overview.refundedOrders)
|
||||
.toString(),
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ class ReportRevenueSummary extends StatelessWidget {
|
||||
),
|
||||
const SpaceWidth(12),
|
||||
Text(
|
||||
'Total Pendapatan',
|
||||
context.lang.total_revenue,
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
||||
@@ -37,7 +37,7 @@ class ReportSales extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Sales Chart',
|
||||
context.lang.sales_chart,
|
||||
style: AppStyle.xxl.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -46,8 +46,8 @@ class ReportSales extends StatelessWidget {
|
||||
const SpaceHeight(4),
|
||||
Text(
|
||||
salesData.isEmpty
|
||||
? 'No data available'
|
||||
: '${salesData.length} days overview',
|
||||
? context.lang.no_data_available
|
||||
: context.lang.total_days_overview(salesData.length),
|
||||
style: AppStyle.md.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
],
|
||||
@@ -71,13 +71,13 @@ class ReportSales extends StatelessWidget {
|
||||
|
||||
// Sales Summary Cards
|
||||
if (salesData.isNotEmpty) ...[
|
||||
_buildSalesSummary(),
|
||||
_buildSalesSummary(context),
|
||||
const SpaceHeight(20),
|
||||
],
|
||||
|
||||
// Chart Container
|
||||
salesData.isEmpty
|
||||
? _buildEmptyChart()
|
||||
? _buildEmptyChart(context)
|
||||
: Container(
|
||||
height: 300,
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -105,14 +105,16 @@ class ReportSales extends StatelessWidget {
|
||||
if (salesData.isNotEmpty)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [_buildLegendItem('Sales Data', AppColor.primary)],
|
||||
children: [
|
||||
_buildLegendItem(context.lang.sales_data, AppColor.primary),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSalesSummary() {
|
||||
Widget _buildSalesSummary(BuildContext context) {
|
||||
final totalSales = salesData.fold<int>(0, (sum, item) => sum + item.sales);
|
||||
final totalOrders = salesData.fold<int>(
|
||||
0,
|
||||
@@ -137,7 +139,7 @@ class ReportSales extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildSummaryItem(
|
||||
'Total Sales',
|
||||
context.lang.total_sales,
|
||||
totalSales.currencyFormatRp,
|
||||
Icons.attach_money,
|
||||
AppColor.success,
|
||||
@@ -151,7 +153,7 @@ class ReportSales extends StatelessWidget {
|
||||
),
|
||||
Expanded(
|
||||
child: _buildSummaryItem(
|
||||
'Net Sales',
|
||||
context.lang.net_sales,
|
||||
totalNetSales.currencyFormatRp,
|
||||
Icons.trending_up,
|
||||
AppColor.primary,
|
||||
@@ -164,7 +166,7 @@ class ReportSales extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildSummaryItem(
|
||||
'Total Orders',
|
||||
context.lang.total_orders,
|
||||
totalOrders.toString(),
|
||||
Icons.shopping_cart,
|
||||
AppColor.info,
|
||||
@@ -178,7 +180,7 @@ class ReportSales extends StatelessWidget {
|
||||
),
|
||||
Expanded(
|
||||
child: _buildSummaryItem(
|
||||
'Total Items',
|
||||
context.lang.total_items,
|
||||
totalItems.toString(),
|
||||
Icons.inventory,
|
||||
AppColor.warning,
|
||||
@@ -204,11 +206,15 @@ class ReportSales extends StatelessWidget {
|
||||
children: [
|
||||
Icon(icon, size: 16, color: color),
|
||||
const SpaceWidth(6),
|
||||
Text(
|
||||
label,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -395,10 +401,10 @@ class ReportSales extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyChart() {
|
||||
Widget _buildEmptyChart(BuildContext context) {
|
||||
return EmptyWidget(
|
||||
title: 'No Sales Data',
|
||||
message: 'Sales data will appear here once transactions are recorded',
|
||||
title: context.lang.no_sales_data,
|
||||
message: context.lang.no_sales_data_desc,
|
||||
emptyIcon: Icons.show_chart,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class ReportTopProduct extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Produk Terlaris',
|
||||
context.lang.best_selling_products,
|
||||
style: AppStyle.xxl.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -42,7 +42,7 @@ class ReportTopProduct extends StatelessWidget {
|
||||
),
|
||||
const SpaceHeight(4),
|
||||
Text(
|
||||
'Ranking penjualan tertinggi',
|
||||
context.lang.highest_sales_ranking,
|
||||
style: AppStyle.md.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
],
|
||||
@@ -66,7 +66,11 @@ class ReportTopProduct extends StatelessWidget {
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return _buildEnhancedProductItem(products[index], index + 1);
|
||||
return _buildEnhancedProductItem(
|
||||
context,
|
||||
products[index],
|
||||
index + 1,
|
||||
);
|
||||
},
|
||||
itemCount: products.length,
|
||||
),
|
||||
@@ -75,7 +79,11 @@ class ReportTopProduct extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEnhancedProductItem(DashboardTopProduct product, int rank) {
|
||||
Widget _buildEnhancedProductItem(
|
||||
BuildContext context,
|
||||
DashboardTopProduct product,
|
||||
int rank,
|
||||
) {
|
||||
final isFirst = rank == 1;
|
||||
final isTopThree = rank <= 3;
|
||||
|
||||
@@ -320,7 +328,7 @@ class ReportTopProduct extends StatelessWidget {
|
||||
const SpaceWidth(3),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'Revenue',
|
||||
context.lang.revenue,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -364,7 +372,7 @@ class ReportTopProduct extends StatelessWidget {
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
'Avg. Price',
|
||||
context.lang.average_price,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -423,7 +431,7 @@ class ReportTopProduct extends StatelessWidget {
|
||||
const SpaceWidth(3),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'Sold',
|
||||
context.lang.sold,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -465,7 +473,7 @@ class ReportTopProduct extends StatelessWidget {
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
'Orders',
|
||||
context.lang.orders,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -543,7 +551,9 @@ class ReportTopProduct extends StatelessWidget {
|
||||
const SpaceWidth(5),
|
||||
Flexible(
|
||||
child: Text(
|
||||
isFirst ? 'Best Seller' : 'Top Performer',
|
||||
isFirst
|
||||
? context.lang.best_seller
|
||||
: context.lang.top_performer,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: isFirst
|
||||
? AppColor.warning
|
||||
|
||||
@@ -97,7 +97,7 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
floating: false,
|
||||
pinned: true,
|
||||
backgroundColor: AppColor.primary,
|
||||
flexibleSpace: CustomAppBar(title: 'Penjualan'),
|
||||
flexibleSpace: CustomAppBar(title: context.lang.sales),
|
||||
),
|
||||
|
||||
// Date Range Header
|
||||
@@ -138,7 +138,7 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Summary',
|
||||
context.lang.summary,
|
||||
style: AppStyle.xxl.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColor.textPrimary,
|
||||
@@ -177,7 +177,7 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: Text(
|
||||
'Daily Breakdown',
|
||||
context.lang.daily_breakdown,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -412,7 +412,7 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildSummaryCard(
|
||||
'Total Sales',
|
||||
context.lang.total_sales,
|
||||
state.sales.summary.totalSales.currencyFormatRp,
|
||||
Icons.trending_up,
|
||||
AppColor.success,
|
||||
@@ -422,7 +422,7 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
SpaceWidth(12),
|
||||
Expanded(
|
||||
child: _buildSummaryCard(
|
||||
'Total Orders',
|
||||
context.lang.total_orders,
|
||||
state.sales.summary.totalOrders.toString(),
|
||||
Icons.shopping_cart,
|
||||
AppColor.info,
|
||||
@@ -446,7 +446,7 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildSummaryCard(
|
||||
'Avg Order Value',
|
||||
context.lang.average_price,
|
||||
state.sales.summary.averageOrderValue
|
||||
.round()
|
||||
.currencyFormatRp,
|
||||
@@ -458,7 +458,7 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
SpaceWidth(12),
|
||||
Expanded(
|
||||
child: _buildSummaryCard(
|
||||
'Total Items',
|
||||
context.lang.total_items,
|
||||
state.sales.summary.totalItems.toString(),
|
||||
Icons.inventory,
|
||||
AppColor.primary,
|
||||
@@ -530,7 +530,7 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Net Sales',
|
||||
context.lang.net_sales,
|
||||
style: TextStyle(
|
||||
color: AppColor.textWhite.withOpacity(0.9),
|
||||
fontSize: 14,
|
||||
@@ -668,7 +668,7 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
'${dailySale.orders} orders',
|
||||
'${dailySale.orders} ${context.lang.orders}',
|
||||
style: TextStyle(
|
||||
color: AppColor.info,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -683,21 +683,21 @@ class _SalesPageState extends State<SalesPage> with TickerProviderStateMixin {
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildDetailItem(
|
||||
'Items',
|
||||
context.lang.items,
|
||||
'${dailySale.items}',
|
||||
Icons.inventory_2,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildDetailItem(
|
||||
'Tax',
|
||||
context.lang.tax,
|
||||
dailySale.tax.currencyFormatRp,
|
||||
Icons.receipt,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _buildDetailItem(
|
||||
'Discount',
|
||||
context.lang.discount,
|
||||
dailySale.discount.currencyFormatRp,
|
||||
Icons.local_offer,
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../components/appbar/appbar.dart';
|
||||
|
||||
@@ -213,7 +214,7 @@ class _SchedulePageState extends State<SchedulePage>
|
||||
floating: false,
|
||||
pinned: true,
|
||||
backgroundColor: AppColor.primary,
|
||||
flexibleSpace: CustomAppBar(title: 'Jadwal'),
|
||||
flexibleSpace: CustomAppBar(title: context.lang.schedule),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user