feat: void page

This commit is contained in:
efrilm
2025-08-04 20:40:25 +07:00
parent 835c834ce1
commit 80649fe082
13 changed files with 2885 additions and 135 deletions
@@ -1,6 +1,5 @@
import 'package:enaklo_pos/core/components/spaces.dart';
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart';
@@ -17,7 +16,7 @@ class SalesListOrder extends StatelessWidget {
margin: const EdgeInsets.only(top: 16),
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(16),
),
child: BlocBuilder<OrderFormBloc, OrderFormState>(
builder: (context, state) {
@@ -27,51 +26,10 @@ class SalesListOrder extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(16),
width: double.infinity,
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: AppColors.background),
),
),
child: Row(
children: [
Checkbox(
value: isAllSelected,
activeColor: AppColors.primary,
onChanged: (val) {
context.read<OrderFormBloc>().add(
OrderFormEvent.toggleSelectAll(val ?? false));
},
),
Text(
'Daftar Pembelian',
style: TextStyle(
color: AppColors.black,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
),
SpaceHeight(12),
Column(
children: List.generate(
order?.orderItems?.length ?? 0,
(index) {
final item = order!.orderItems![index];
final isSelected =
selectedItems.any((e) => e.id == item.id);
return _item(
context,
isSelected,
item,
);
},
).toList(),
),
_buildHeader(context, isAllSelected),
const SpaceHeight(8),
_buildItemsList(context, selectedItems),
const SpaceHeight(8),
],
),
);
@@ -80,96 +38,345 @@ class SalesListOrder extends StatelessWidget {
);
}
Padding _item(BuildContext context, bool isSelected, OrderItem product) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16)
.copyWith(top: 0),
child: Column(
Widget _buildHeader(BuildContext context, bool isAllSelected) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppColors.primary.withOpacity(0.1),
AppColors.primary.withOpacity(0.05),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
border: Border(
bottom: BorderSide(
color: AppColors.primary.withOpacity(0.1),
width: 1,
),
),
),
child: Row(
children: [
Align(
alignment: Alignment.centerRight,
child: Text(
product.status == "pending"
? "Pending"
: product.status == "cancelled"
? "Batal"
: product.status == 'refund'
? "Refund"
: "Selesai",
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color: product.status == "pending"
? Colors.blue
: product.status == "cancelled"
? Colors.red
: product.status == 'refund'
? Colors.red
: Colors.green,
Container(
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isAllSelected ? AppColors.primary : Colors.grey.shade300,
width: 2,
),
),
child: Checkbox(
value: isAllSelected,
activeColor: AppColors.primary,
checkColor: AppColors.white,
onChanged: (val) {
context
.read<OrderFormBloc>()
.add(OrderFormEvent.toggleSelectAll(val ?? false));
},
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: context.deviceWidth * 0.2,
child: Row(
children: [
Checkbox(
value: isSelected,
activeColor: AppColors.primary,
onChanged: (_) {
context
.read<OrderFormBloc>()
.add(OrderFormEvent.toggleItem(product));
},
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Daftar Pembelian',
style: TextStyle(
color: AppColors.black,
fontSize: 18,
fontWeight: FontWeight.w700,
letterSpacing: -0.5,
),
),
const SizedBox(height: 4),
Text(
'${order?.orderItems?.length ?? 0} item',
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: AppColors.primary.withOpacity(0.2),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.shopping_cart_outlined,
size: 16,
color: AppColors.primary,
),
const SizedBox(width: 4),
Text(
'Order',
style: TextStyle(
color: AppColors.primary,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
);
}
Widget _buildItemsList(BuildContext context, List selectedItems) {
return Column(
children: List.generate(
order?.orderItems?.length ?? 0,
(index) {
final item = order!.orderItems![index];
final isSelected = selectedItems.any((e) => e.id == item.id);
return _buildItem(context, isSelected, item, index);
},
).toList(),
);
}
Widget _buildItem(
BuildContext context, bool isSelected, OrderItem product, int index) {
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
decoration: BoxDecoration(
color:
isSelected ? AppColors.primary.withOpacity(0.05) : AppColors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected
? AppColors.primary.withOpacity(0.3)
: Colors.grey.shade200,
width: isSelected ? 2 : 1,
),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(6),
border: Border.all(
color:
isSelected ? AppColors.primary : Colors.grey.shade300,
width: 1.5,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.productName ?? '',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
if (product.productVariantName != null)
Text(
product.productVariantName ?? '',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
),
child: Checkbox(
value: isSelected,
activeColor: AppColors.primary,
checkColor: AppColors.white,
onChanged: (_) {
context
.read<OrderFormBloc>()
.add(OrderFormEvent.toggleItem(product));
},
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
product.productName ?? '',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
letterSpacing: -0.2,
),
),
),
Text(
(product.unitPrice ?? 0)
.toString()
.currencyFormatRpV2,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
_buildStatusBadge(product.status),
],
),
if (product.productVariantName != null) ...[
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(6),
),
child: Text(
product.productVariantName ?? '',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.grey.shade700,
),
),
),
],
),
],
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Harga Satuan',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 2),
Text(
(product.unitPrice ?? 0)
.toString()
.currencyFormatRpV2,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
],
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'x${product.quantity}',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.primary,
),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'Total',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 2),
Text(
(product.totalPrice ?? 0)
.toString()
.currencyFormatRpV2,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: AppColors.primary,
),
),
],
),
],
),
],
),
),
),
Text(
'X${product.quantity}',
style: const TextStyle(
fontSize: 14,
),
),
Text(
(product.totalPrice ?? 0).toString().currencyFormatRpV2,
style: const TextStyle(
fontSize: 14,
),
),
],
],
),
],
),
),
);
}
Widget _buildStatusBadge(String? status) {
Color backgroundColor;
Color textColor;
String displayText;
IconData icon;
switch (status) {
case "pending":
backgroundColor = Colors.orange.withOpacity(0.1);
textColor = Colors.orange.shade700;
displayText = "Pending";
icon = Icons.access_time;
break;
case "cancelled":
backgroundColor = Colors.red.withOpacity(0.1);
textColor = Colors.red.shade700;
displayText = "Batal";
icon = Icons.cancel_outlined;
break;
case "refund":
backgroundColor = Colors.purple.withOpacity(0.1);
textColor = Colors.purple.shade700;
displayText = "Refund";
icon = Icons.undo;
break;
default:
backgroundColor = Colors.green.withOpacity(0.1);
textColor = Colors.green.shade700;
displayText = "Selesai";
icon = Icons.check_circle_outline;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: textColor.withOpacity(0.2),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
size: 14,
color: textColor,
),
const SizedBox(width: 4),
Text(
displayText,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: textColor,
),
),
],
),