update order detail
This commit is contained in:
@@ -657,7 +657,7 @@ class AddressFormPage extends HookConsumerWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
value: items.containsKey(value) ? value : null,
|
||||
initialValue: items.containsKey(value) ? value : null,
|
||||
isExpanded: true,
|
||||
validator: validator,
|
||||
decoration: InputDecoration(
|
||||
@@ -796,7 +796,7 @@ class AddressFormPage extends HookConsumerWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
value: items.containsKey(value) ? value : null,
|
||||
initialValue: items.containsKey(value) ? value : null,
|
||||
isExpanded: true,
|
||||
validator: validator,
|
||||
decoration: InputDecoration(
|
||||
|
||||
@@ -453,7 +453,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
|
||||
borderRadius: BorderRadius.circular(AppRadius.card),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
@@ -759,7 +759,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
|
||||
return customerGroupsAsync.when(
|
||||
data: (groups) {
|
||||
return DropdownButtonFormField<CustomerGroup>(
|
||||
value: _selectedRole,
|
||||
initialValue: _selectedRole,
|
||||
decoration: _buildInputDecoration(
|
||||
hintText: 'Chọn vai trò',
|
||||
prefixIcon: FontAwesomeIcons.briefcase,
|
||||
@@ -831,7 +831,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
|
||||
return citiesAsync.when(
|
||||
data: (cities) {
|
||||
return DropdownButtonFormField<City>(
|
||||
value: _selectedCity,
|
||||
initialValue: _selectedCity,
|
||||
decoration: _buildInputDecoration(
|
||||
hintText: 'Chọn tỉnh/thành phố',
|
||||
prefixIcon: Icons.location_city,
|
||||
|
||||
@@ -55,7 +55,7 @@ class RoleDropdown extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DropdownButtonFormField<String>(
|
||||
value: value,
|
||||
initialValue: value,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Chọn vai trò của bạn',
|
||||
hintStyle: const TextStyle(
|
||||
|
||||
@@ -167,7 +167,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
|
||||
|
||||
// Price
|
||||
Text(
|
||||
'${currencyFormatter.format(widget.item.product.basePrice)}/${widget.item.product.unit ?? 'm²'}',
|
||||
'${currencyFormatter.format(widget.item.product.basePrice)}/m²',
|
||||
style: AppTypography.titleMedium.copyWith(
|
||||
color: AppColors.primaryBlue,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -252,7 +252,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
|
||||
|
||||
// Unit label
|
||||
Text(
|
||||
widget.item.product.unit ?? 'm²',
|
||||
'm²',
|
||||
style: AppTypography.bodySmall.copyWith(
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
@@ -273,7 +273,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
|
||||
const TextSpan(text: '(Quy đổi: '),
|
||||
TextSpan(
|
||||
text:
|
||||
'${widget.item.quantityConverted.toStringAsFixed(2)} ${widget.item.product.unit ?? 'm²'}',
|
||||
'${widget.item.quantityConverted.toStringAsFixed(2)} m²',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const TextSpan(text: ' = '),
|
||||
|
||||
@@ -31,7 +31,7 @@ class MemberCardWidget extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
color: Colors.black.withValues(alpha: 0.2),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
@@ -64,7 +64,7 @@ class MemberCardWidget extends StatelessWidget {
|
||||
Text(
|
||||
memberCard.memberType.displayName,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.8,
|
||||
@@ -79,7 +79,7 @@ class MemberCardWidget extends StatelessWidget {
|
||||
Text(
|
||||
'Valid through',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
color: Colors.white.withValues(alpha: 0.8),
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
@@ -123,7 +123,7 @@ class MemberCardWidget extends StatelessWidget {
|
||||
Text(
|
||||
'CLASS: ${memberCard.tier.displayName}',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
@@ -132,7 +132,7 @@ class MemberCardWidget extends StatelessWidget {
|
||||
Text(
|
||||
'Points: ${_formatPoints(memberCard.points)}',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
|
||||
@@ -24,8 +24,8 @@ class OrderDetailModel {
|
||||
final List<OrderItemDetailModel> items;
|
||||
final PaymentTermsInfoModel paymentTerms;
|
||||
final List<TimelineItemModel> timeline;
|
||||
final List<dynamic> payments;
|
||||
final List<dynamic> invoices;
|
||||
final List<PaymentInfoModel> payments;
|
||||
final List<InvoiceInfoModel> invoices;
|
||||
|
||||
/// Create from JSON
|
||||
factory OrderDetailModel.fromJson(Map<String, dynamic> json) {
|
||||
@@ -50,8 +50,14 @@ class OrderDetailModel {
|
||||
.map((item) =>
|
||||
TimelineItemModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList(),
|
||||
payments: json['payments'] as List<dynamic>? ?? [],
|
||||
invoices: json['invoices'] as List<dynamic>? ?? [],
|
||||
payments: (json['payments'] as List<dynamic>? ?? [])
|
||||
.map((item) =>
|
||||
PaymentInfoModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList(),
|
||||
invoices: (json['invoices'] as List<dynamic>? ?? [])
|
||||
.map((item) =>
|
||||
InvoiceInfoModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,8 +70,8 @@ class OrderDetailModel {
|
||||
'items': items.map((item) => item.toJson()).toList(),
|
||||
'payment_terms': paymentTerms.toJson(),
|
||||
'timeline': timeline.map((item) => item.toJson()).toList(),
|
||||
'payments': payments,
|
||||
'invoices': invoices,
|
||||
'payments': payments.map((item) => item.toJson()).toList(),
|
||||
'invoices': invoices.map((item) => item.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -78,8 +84,8 @@ class OrderDetailModel {
|
||||
items: items.map((item) => item.toEntity()).toList(),
|
||||
paymentTerms: paymentTerms.toEntity(),
|
||||
timeline: timeline.map((item) => item.toEntity()).toList(),
|
||||
payments: payments,
|
||||
invoices: invoices,
|
||||
payments: payments.map((item) => item.toEntity()).toList(),
|
||||
invoices: invoices.map((item) => item.toEntity()).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -96,8 +102,12 @@ class OrderDetailModel {
|
||||
timeline: entity.timeline
|
||||
.map((item) => TimelineItemModel.fromEntity(item))
|
||||
.toList(),
|
||||
payments: entity.payments,
|
||||
invoices: entity.invoices,
|
||||
payments: entity.payments
|
||||
.map((item) => PaymentInfoModel.fromEntity(item))
|
||||
.toList(),
|
||||
invoices: entity.invoices
|
||||
.map((item) => InvoiceInfoModel.fromEntity(item))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -500,3 +510,93 @@ class TimelineItemModel {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Payment Info Model
|
||||
class PaymentInfoModel {
|
||||
const PaymentInfoModel({
|
||||
required this.name,
|
||||
required this.creationDate,
|
||||
required this.amount,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String creationDate;
|
||||
final double amount;
|
||||
|
||||
factory PaymentInfoModel.fromJson(Map<String, dynamic> json) {
|
||||
return PaymentInfoModel(
|
||||
name: json['name'] as String,
|
||||
creationDate: json['creation_date'] as String,
|
||||
amount: (json['amount'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'name': name,
|
||||
'creation_date': creationDate,
|
||||
'amount': amount,
|
||||
};
|
||||
}
|
||||
|
||||
PaymentInfo toEntity() {
|
||||
return PaymentInfo(
|
||||
name: name,
|
||||
creationDate: creationDate,
|
||||
amount: amount,
|
||||
);
|
||||
}
|
||||
|
||||
factory PaymentInfoModel.fromEntity(PaymentInfo entity) {
|
||||
return PaymentInfoModel(
|
||||
name: entity.name,
|
||||
creationDate: entity.creationDate,
|
||||
amount: entity.amount,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoice Info Model
|
||||
class InvoiceInfoModel {
|
||||
const InvoiceInfoModel({
|
||||
required this.name,
|
||||
required this.postingDate,
|
||||
required this.grandTotal,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String postingDate;
|
||||
final double grandTotal;
|
||||
|
||||
factory InvoiceInfoModel.fromJson(Map<String, dynamic> json) {
|
||||
return InvoiceInfoModel(
|
||||
name: json['name'] as String,
|
||||
postingDate: json['posting_date'] as String,
|
||||
grandTotal: (json['grand_total'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'name': name,
|
||||
'posting_date': postingDate,
|
||||
'grand_total': grandTotal,
|
||||
};
|
||||
}
|
||||
|
||||
InvoiceInfo toEntity() {
|
||||
return InvoiceInfo(
|
||||
name: name,
|
||||
postingDate: postingDate,
|
||||
grandTotal: grandTotal,
|
||||
);
|
||||
}
|
||||
|
||||
factory InvoiceInfoModel.fromEntity(InvoiceInfo entity) {
|
||||
return InvoiceInfoModel(
|
||||
name: entity.name,
|
||||
postingDate: entity.postingDate,
|
||||
grandTotal: entity.grandTotal,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ class OrderDetail extends Equatable {
|
||||
final List<OrderItemDetail> items;
|
||||
final PaymentTermsInfo paymentTerms;
|
||||
final List<TimelineItem> timeline;
|
||||
final List<dynamic> payments; // Payment entities can be added later
|
||||
final List<dynamic> invoices; // Invoice entities can be added later
|
||||
final List<PaymentInfo> payments;
|
||||
final List<InvoiceInfo> invoices;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
@@ -219,3 +219,35 @@ class TimelineItem extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [label, value, status];
|
||||
}
|
||||
|
||||
/// Payment Info
|
||||
class PaymentInfo extends Equatable {
|
||||
const PaymentInfo({
|
||||
required this.name,
|
||||
required this.creationDate,
|
||||
required this.amount,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String creationDate;
|
||||
final double amount;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [name, creationDate, amount];
|
||||
}
|
||||
|
||||
/// Invoice Info
|
||||
class InvoiceInfo extends Equatable {
|
||||
const InvoiceInfo({
|
||||
required this.name,
|
||||
required this.postingDate,
|
||||
required this.grandTotal,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String postingDate;
|
||||
final double grandTotal;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [name, postingDate, grandTotal];
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:intl/intl.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/enums/status_color.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
import 'package:worker/core/utils/extensions.dart';
|
||||
import 'package:worker/features/orders/domain/entities/order_detail.dart';
|
||||
import 'package:worker/features/orders/presentation/providers/orders_provider.dart';
|
||||
|
||||
@@ -82,16 +83,24 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
_buildStatusTimelineCard(orderDetail),
|
||||
|
||||
// Delivery/Address Information Card
|
||||
_buildAddressInfoCard(orderDetail),
|
||||
_buildAddressInfoCard(context, orderDetail),
|
||||
|
||||
// Customer Information Card
|
||||
_buildCustomerInfoCard(orderDetail),
|
||||
// Invoice Information Card
|
||||
_buildInvoiceInfoCard(context, orderDetail),
|
||||
|
||||
// Invoices List Card
|
||||
_buildInvoicesListCard(context, orderDetail),
|
||||
|
||||
// Products List Card
|
||||
_buildProductsListCard(orderDetail),
|
||||
|
||||
// Order Summary Card
|
||||
_buildOrderSummaryCard(orderDetail),
|
||||
|
||||
// Payment History Card
|
||||
_buildPaymentHistoryCard(context, orderDetail),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -327,7 +336,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
}
|
||||
|
||||
/// Build Address Info Card
|
||||
Widget _buildAddressInfoCard(OrderDetail orderDetail) {
|
||||
Widget _buildAddressInfoCard(BuildContext context, OrderDetail orderDetail) {
|
||||
final order = orderDetail.order;
|
||||
final shippingAddress = orderDetail.shippingAddress;
|
||||
final dateFormatter = DateFormat('dd/MM/yyyy');
|
||||
@@ -341,15 +350,15 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
const Row(
|
||||
children: [
|
||||
const FaIcon(
|
||||
FaIcon(
|
||||
FontAwesomeIcons.truck,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Thông tin giao hàng',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
@@ -362,86 +371,139 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Delivery Date
|
||||
_buildInfoRow(
|
||||
icon: FontAwesomeIcons.calendar,
|
||||
label: 'Ngày giao hàng',
|
||||
value: dateFormatter.format(DateTime.parse(order.deliveryDate)),
|
||||
// Address Section with Label + Button
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'Địa chỉ nhận hàng',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// TODO: Navigate to address update
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Chức năng đang phát triển')),
|
||||
);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
minimumSize: Size.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
side: const BorderSide(color: AppColors.grey100),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Cập nhật',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primaryBlue,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
_buildInfoRow(
|
||||
icon: FontAwesomeIcons.locationDot,
|
||||
label: 'Địa chỉ giao hàng',
|
||||
value:
|
||||
'${shippingAddress.addressLine1}\n${shippingAddress.wardName}, ${shippingAddress.cityName}',
|
||||
// Address Box
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
shippingAddress.addressTitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
shippingAddress.phone,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${shippingAddress.addressLine1}\n${shippingAddress.wardName}, ${shippingAddress.cityName}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildInfoRow(
|
||||
icon: FontAwesomeIcons.user,
|
||||
label: 'Người nhận',
|
||||
value: '${shippingAddress.addressTitle} - ${shippingAddress.phone}',
|
||||
// Pickup Date
|
||||
const Text(
|
||||
'Ngày lấy hàng',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
dateFormatter.format(DateTime.parse(order.deliveryDate)),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
|
||||
if (order.description.isNotEmpty) ...[ const SizedBox(height: 16),
|
||||
|
||||
// Notes
|
||||
const Text(
|
||||
'Ghi chú',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
order.description,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Info Row
|
||||
Widget _buildInfoRow({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required String value,
|
||||
Color? valueColor,
|
||||
}) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Row(
|
||||
children: [
|
||||
FaIcon(icon, size: 14, color: AppColors.grey500),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
value,
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: valueColor != null
|
||||
? FontWeight.w600
|
||||
: FontWeight.w500,
|
||||
color: valueColor ?? AppColors.grey900,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Customer Info Card
|
||||
Widget _buildCustomerInfoCard(OrderDetail orderDetail) {
|
||||
final order = orderDetail.order;
|
||||
/// Build Invoice Info Card
|
||||
Widget _buildInvoiceInfoCard(BuildContext context, OrderDetail orderDetail) {
|
||||
final billingAddress = orderDetail.billingAddress;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
elevation: 1,
|
||||
@@ -451,16 +513,146 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Title + Update Button
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const FaIcon(
|
||||
FontAwesomeIcons.user,
|
||||
const Row(
|
||||
children: [
|
||||
FaIcon(
|
||||
FontAwesomeIcons.fileInvoice,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Thông tin hóa đơn',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// TODO: Navigate to invoice update
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Chức năng đang phát triển')),
|
||||
);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
minimumSize: Size.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
side: BorderSide(color: AppColors.grey100),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Cập nhật',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primaryBlue,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Invoice Address Box
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
billingAddress.addressTitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
if (billingAddress.taxCode.isNotEmpty) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Mã số thuế: ${billingAddress.taxCode}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Số điện thoại: ${billingAddress.phone}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Email: ${billingAddress.email}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Địa chỉ: ${billingAddress.addressLine1}, ${billingAddress.wardName}, ${billingAddress.cityName}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Invoices List Card
|
||||
Widget _buildInvoicesListCard(BuildContext context, OrderDetail orderDetail) {
|
||||
final invoices = orderDetail.invoices;
|
||||
|
||||
if (invoices.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
FaIcon(
|
||||
FontAwesomeIcons.fileInvoiceDollar,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'Thông tin khách hàng',
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Hóa đơn đã xuất',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -472,54 +664,109 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildCustomerRow('Tên khách hàng:', order.customer),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
_buildCustomerRow('Số điện thoại:', billingAddress.phone),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
_buildCustomerRow('Email:', billingAddress.email),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
if (billingAddress.taxCode.isNotEmpty) ...[
|
||||
_buildCustomerRow('Mã số thuế:', billingAddress.taxCode),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
// Invoice Items (Mock data for now)
|
||||
...invoices.map((e) => _buildInvoiceItem(
|
||||
invoiceId: e.name,
|
||||
date: e.postingDate,
|
||||
amount: e.grandTotal.toVNCurrency,
|
||||
onTap: () {
|
||||
// TODO: Navigate to invoice detail
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Chức năng đang phát triển')),
|
||||
);
|
||||
},
|
||||
),),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Customer Row
|
||||
Widget _buildCustomerRow(String label, String value) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
/// Build Invoice Item
|
||||
Widget _buildInvoiceItem({
|
||||
required String invoiceId,
|
||||
required String date,
|
||||
required String amount,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [AppColors.primaryBlue, Color(0xFF1d4ed8)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Center(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.fileInvoice,
|
||||
color: Colors.white,
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
invoiceId,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Ngày xuất: $date',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4,),
|
||||
Text(
|
||||
amount,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.danger,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const FaIcon(
|
||||
FontAwesomeIcons.chevronRight,
|
||||
size: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Products List Card
|
||||
Widget _buildProductsListCard(OrderDetail orderDetail) {
|
||||
final items = orderDetail.items;
|
||||
final currencyFormatter = NumberFormat.currency(
|
||||
locale: 'vi_VN',
|
||||
symbol: 'đ',
|
||||
decimalDigits: 0,
|
||||
);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
@@ -530,15 +777,15 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
const Row(
|
||||
children: [
|
||||
const FaIcon(
|
||||
FaIcon(
|
||||
FontAwesomeIcons.box,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Sản phẩm đặt hàng',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
@@ -657,14 +904,14 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'${currencyFormatter.format(item.price)}/m²',
|
||||
'${item.price.toVNCurrency}/m²',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
currencyFormatter.format(item.totalAmount),
|
||||
item.totalAmount.toVNCurrency,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -692,11 +939,6 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
Widget _buildOrderSummaryCard(OrderDetail orderDetail) {
|
||||
final order = orderDetail.order;
|
||||
final paymentTerms = orderDetail.paymentTerms;
|
||||
final currencyFormatter = NumberFormat.currency(
|
||||
locale: 'vi_VN',
|
||||
symbol: 'đ',
|
||||
decimalDigits: 0,
|
||||
);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
@@ -728,13 +970,13 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildSummaryRow('Tổng tiền hàng:', currencyFormatter.format(order.total)),
|
||||
_buildSummaryRow('Tổng tiền hàng:', order.total.toVNCurrency),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
if (order.totalRemaining > 0) ...[
|
||||
_buildSummaryRow(
|
||||
'Còn lại:',
|
||||
currencyFormatter.format(order.totalRemaining),
|
||||
order.totalRemaining.toVNCurrency,
|
||||
valueColor: AppColors.warning,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -744,7 +986,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
_buildSummaryRow(
|
||||
'Tổng cộng:',
|
||||
currencyFormatter.format(order.grandTotal),
|
||||
order.grandTotal.toVNCurrency,
|
||||
isTotal: true,
|
||||
),
|
||||
|
||||
@@ -784,31 +1026,169 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
|
||||
if (order.description.isNotEmpty) ...[
|
||||
const Divider(height: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Order Notes
|
||||
Row(
|
||||
/// Build Payment History Card
|
||||
Widget _buildPaymentHistoryCard(BuildContext context, OrderDetail orderDetail) {
|
||||
final order = orderDetail.order;
|
||||
final payments = orderDetail.payments;
|
||||
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 16,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
FaIcon(
|
||||
FontAwesomeIcons.clockRotateLeft,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Lịch sử thanh toán',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
|
||||
...payments.map((e) => _buildPaymentItem(
|
||||
paymentId: e.name,
|
||||
date: e.creationDate,
|
||||
amount: e.amount.toVNCurrency,
|
||||
onTap: () {
|
||||
// TODO: Show payment detail modal
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Chi tiết thanh toán')),
|
||||
);
|
||||
},
|
||||
)),
|
||||
|
||||
|
||||
// Payment Summary
|
||||
Container(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(color: AppColors.grey100, width: 1),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const FaIcon(FontAwesomeIcons.noteSticky, size: 14, color: AppColors.grey500),
|
||||
const SizedBox(width: 6),
|
||||
const Text(
|
||||
'Ghi chú đơn hàng:',
|
||||
style: TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
'Còn lại:',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
order.totalRemaining.toVNCurrency,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.danger,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
order.description,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey900,
|
||||
height: 1.4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Payment Item
|
||||
Widget _buildPaymentItem({
|
||||
required String paymentId,
|
||||
required String date,
|
||||
required String amount,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFD1FAE5),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Center(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.check,
|
||||
color: Color(0xFF065F46),
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
paymentId,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
date,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
amount,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF065F46),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const FaIcon(
|
||||
FontAwesomeIcons.chevronRight,
|
||||
size: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -848,8 +1228,6 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
/// Build Action Buttons
|
||||
Widget _buildActionButtons(BuildContext context, OrderDetail orderDetail) {
|
||||
final shippingAddress = orderDetail.shippingAddress;
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
@@ -866,23 +1244,22 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
spacing: 12,
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// TODO: Navigate to payment page
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Gọi ${shippingAddress.phone}...'),
|
||||
const SnackBar(
|
||||
content: Text('Chức năng thanh toán đang phát triển'),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const FaIcon(FontAwesomeIcons.phone, size: 18),
|
||||
label: const Text('Liên hệ'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
side: const BorderSide(
|
||||
color: AppColors.grey100,
|
||||
width: 2,
|
||||
),
|
||||
foregroundColor: AppColors.grey900,
|
||||
icon: const FaIcon(FontAwesomeIcons.creditCard, size: 18),
|
||||
label: const Text('Thanh toán'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
backgroundColor: AppColors.primaryBlue,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
@@ -890,21 +1267,24 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
// TODO: Navigate to chat/support
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Chức năng đang phát triển...'),
|
||||
content: Text('Liên hệ hỗ trợ...'),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const FaIcon(FontAwesomeIcons.penToSquare, size: 18),
|
||||
label: const Text('Cập nhật'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
backgroundColor: AppColors.primaryBlue,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
icon: const FaIcon(FontAwesomeIcons.comments, size: 18),
|
||||
label: const Text('Liên hệ hỗ trợ'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
side: BorderSide(
|
||||
color: AppColors.grey100,
|
||||
width: 2,
|
||||
),
|
||||
foregroundColor: AppColors.grey900,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
|
||||
@@ -23,7 +23,7 @@ class DocumentCard extends StatelessWidget {
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
|
||||
@@ -28,7 +28,6 @@ class ProductModel extends HiveObject {
|
||||
this.specifications,
|
||||
this.itemGroupName,
|
||||
this.brand,
|
||||
this.unit,
|
||||
this.conversionOfSm,
|
||||
this.introAttributes,
|
||||
required this.isActive,
|
||||
@@ -83,10 +82,6 @@ class ProductModel extends HiveObject {
|
||||
@HiveField(10)
|
||||
final String? brand;
|
||||
|
||||
/// Unit of measurement (m2, box, piece, etc.)
|
||||
@HiveField(11)
|
||||
final String? unit;
|
||||
|
||||
/// Conversion factor for Square Meter UOM (tiles per m²)
|
||||
/// Used to calculate: Số viên = Số lượng × conversionOfSm
|
||||
@HiveField(17)
|
||||
@@ -204,7 +199,6 @@ class ProductModel extends HiveObject {
|
||||
: null,
|
||||
itemGroupName: json['item_group_name'] as String?,
|
||||
brand: json['brand'] as String?,
|
||||
unit: json['currency'] as String?, // Use currency as unit for now
|
||||
conversionOfSm: json['conversion_of_sm'] != null
|
||||
? (json['conversion_of_sm'] as num).toDouble()
|
||||
: null,
|
||||
@@ -260,7 +254,6 @@ class ProductModel extends HiveObject {
|
||||
specifications: null,
|
||||
itemGroupName: json['item_group_name'] as String?,
|
||||
brand: null, // Not provided by wishlist API
|
||||
unit: json['currency'] as String? ?? 'm²',
|
||||
conversionOfSm: json['conversion_of_sm'] != null
|
||||
? (json['conversion_of_sm'] as num).toDouble()
|
||||
: null,
|
||||
@@ -291,7 +284,6 @@ class ProductModel extends HiveObject {
|
||||
: null,
|
||||
'item_group_name': itemGroupName,
|
||||
'brand': brand,
|
||||
'unit': unit,
|
||||
'conversion_of_sm': conversionOfSm,
|
||||
'intro_attributes': introAttributes != null
|
||||
? jsonDecode(introAttributes!)
|
||||
@@ -406,7 +398,6 @@ class ProductModel extends HiveObject {
|
||||
String? specifications,
|
||||
String? itemGroupName,
|
||||
String? brand,
|
||||
String? unit,
|
||||
double? conversionOfSm,
|
||||
String? introAttributes,
|
||||
bool? isActive,
|
||||
@@ -427,7 +418,6 @@ class ProductModel extends HiveObject {
|
||||
specifications: specifications ?? this.specifications,
|
||||
itemGroupName: itemGroupName ?? this.itemGroupName,
|
||||
brand: brand ?? this.brand,
|
||||
unit: unit ?? this.unit,
|
||||
conversionOfSm: conversionOfSm ?? this.conversionOfSm,
|
||||
introAttributes: introAttributes ?? this.introAttributes,
|
||||
isActive: isActive ?? this.isActive,
|
||||
@@ -471,7 +461,6 @@ class ProductModel extends HiveObject {
|
||||
specifications: specificationsMap ?? {},
|
||||
itemGroupName: itemGroupName,
|
||||
brand: brand,
|
||||
unit: unit,
|
||||
conversionOfSm: conversionOfSm,
|
||||
introAttributes: introAttributesList,
|
||||
isActive: isActive,
|
||||
|
||||
@@ -28,7 +28,6 @@ class ProductModelAdapter extends TypeAdapter<ProductModel> {
|
||||
specifications: fields[8] as String?,
|
||||
itemGroupName: fields[9] as String?,
|
||||
brand: fields[10] as String?,
|
||||
unit: fields[11] as String?,
|
||||
conversionOfSm: (fields[17] as num?)?.toDouble(),
|
||||
introAttributes: fields[18] as String?,
|
||||
isActive: fields[12] as bool,
|
||||
@@ -42,7 +41,7 @@ class ProductModelAdapter extends TypeAdapter<ProductModel> {
|
||||
@override
|
||||
void write(BinaryWriter writer, ProductModel obj) {
|
||||
writer
|
||||
..writeByte(19)
|
||||
..writeByte(18)
|
||||
..writeByte(0)
|
||||
..write(obj.productId)
|
||||
..writeByte(1)
|
||||
@@ -65,8 +64,6 @@ class ProductModelAdapter extends TypeAdapter<ProductModel> {
|
||||
..write(obj.itemGroupName)
|
||||
..writeByte(10)
|
||||
..write(obj.brand)
|
||||
..writeByte(11)
|
||||
..write(obj.unit)
|
||||
..writeByte(12)
|
||||
..write(obj.isActive)
|
||||
..writeByte(13)
|
||||
|
||||
@@ -22,7 +22,6 @@ class Product {
|
||||
required this.specifications,
|
||||
this.itemGroupName,
|
||||
this.brand,
|
||||
this.unit,
|
||||
this.conversionOfSm,
|
||||
this.introAttributes,
|
||||
required this.isActive,
|
||||
@@ -64,9 +63,6 @@ class Product {
|
||||
/// Brand name
|
||||
final String? brand;
|
||||
|
||||
/// Unit of measurement (e.g., "m²", "viên", "hộp")
|
||||
final String? unit;
|
||||
|
||||
/// Conversion factor for Square Meter UOM (tiles per m²)
|
||||
/// Used to calculate: Số viên = Số lượng × conversionOfSm
|
||||
final double? conversionOfSm;
|
||||
@@ -154,7 +150,6 @@ class Product {
|
||||
Map<String, dynamic>? specifications,
|
||||
String? itemGroupName,
|
||||
String? brand,
|
||||
String? unit,
|
||||
double? conversionOfSm,
|
||||
List<Map<String, String>>? introAttributes,
|
||||
bool? isActive,
|
||||
@@ -175,7 +170,6 @@ class Product {
|
||||
specifications: specifications ?? this.specifications,
|
||||
itemGroupName: itemGroupName ?? this.itemGroupName,
|
||||
brand: brand ?? this.brand,
|
||||
unit: unit ?? this.unit,
|
||||
conversionOfSm: conversionOfSm ?? this.conversionOfSm,
|
||||
introAttributes: introAttributes ?? this.introAttributes,
|
||||
isActive: isActive ?? this.isActive,
|
||||
@@ -203,7 +197,6 @@ class Product {
|
||||
other.basePrice == basePrice &&
|
||||
other.itemGroupName == itemGroupName &&
|
||||
other.brand == brand &&
|
||||
other.unit == unit &&
|
||||
other.isActive == isActive &&
|
||||
other.isFeatured == isFeatured &&
|
||||
other.erpnextItemCode == erpnextItemCode;
|
||||
@@ -218,7 +211,6 @@ class Product {
|
||||
basePrice,
|
||||
itemGroupName,
|
||||
brand,
|
||||
unit,
|
||||
isActive,
|
||||
isFeatured,
|
||||
erpnextItemCode,
|
||||
|
||||
@@ -156,7 +156,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Đã thêm $_quantity ${product.unit} ${product.name} vào giỏ hàng!',
|
||||
'Đã thêm $_quantity m² ${product.name} vào giỏ hàng!',
|
||||
),
|
||||
duration: const Duration(seconds: 2),
|
||||
action: SnackBarAction(
|
||||
@@ -246,7 +246,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
right: 0,
|
||||
child: StickyActionBar(
|
||||
quantity: _quantity,
|
||||
unit: product.unit ?? 'm²',
|
||||
unit: 'm²',
|
||||
conversionOfSm: product.conversionOfSm,
|
||||
uomFromIntroAttributes: product.getIntroAttribute('UOM'),
|
||||
onIncrease: _increaseQuantity,
|
||||
|
||||
@@ -220,7 +220,7 @@ class ProductCard extends ConsumerWidget {
|
||||
|
||||
// Price
|
||||
Text(
|
||||
'${_formatPrice(product.effectivePrice)}/${product.unit}',
|
||||
'${_formatPrice(product.effectivePrice)}/m²',
|
||||
style: const TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
||||
@@ -61,7 +61,7 @@ class ProductInfoSection extends StatelessWidget {
|
||||
children: [
|
||||
// Current Price
|
||||
Text(
|
||||
'${_formatPrice(product.basePrice)}/${product.unit ?? 'm²'}',
|
||||
'${_formatPrice(product.basePrice)}/m²',
|
||||
style: const TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w700,
|
||||
|
||||
@@ -271,7 +271,7 @@ class DesignRequestCreatePage extends HookConsumerWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
value: selectedStyle.value.isEmpty
|
||||
initialValue: selectedStyle.value.isEmpty
|
||||
? null
|
||||
: selectedStyle.value,
|
||||
decoration: InputDecoration(
|
||||
@@ -365,7 +365,7 @@ class DesignRequestCreatePage extends HookConsumerWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
value: selectedBudget.value.isEmpty
|
||||
initialValue: selectedBudget.value.isEmpty
|
||||
? null
|
||||
: selectedBudget.value,
|
||||
decoration: InputDecoration(
|
||||
|
||||
Reference in New Issue
Block a user