add payments screen
This commit is contained in:
321
lib/features/orders/presentation/widgets/invoice_card.dart
Normal file
321
lib/features/orders/presentation/widgets/invoice_card.dart
Normal file
@@ -0,0 +1,321 @@
|
||||
/// Widget: Invoice Card
|
||||
///
|
||||
/// Displays invoice information in a card format.
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:worker/core/database/models/enums.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
import 'package:worker/features/orders/data/models/invoice_model.dart';
|
||||
|
||||
/// Invoice Card Widget
|
||||
///
|
||||
/// Displays invoice details in a card with status indicator and payment summary.
|
||||
class InvoiceCard extends StatelessWidget {
|
||||
/// Invoice to display
|
||||
final InvoiceModel invoice;
|
||||
|
||||
/// Tap callback
|
||||
final VoidCallback? onTap;
|
||||
|
||||
/// Payment button callback
|
||||
final VoidCallback? onPaymentTap;
|
||||
|
||||
const InvoiceCard({
|
||||
required this.invoice,
|
||||
this.onTap,
|
||||
this.onPaymentTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currencyFormatter = NumberFormat.currency(
|
||||
locale: 'vi_VN',
|
||||
symbol: 'đ',
|
||||
decimalDigits: 0,
|
||||
);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Invoice number and Order ID row
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Invoice number
|
||||
Text(
|
||||
'Mã hoá đơn #${invoice.invoiceNumber}',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
// Order ID
|
||||
if (invoice.orderId != null)
|
||||
Text(
|
||||
'Đơn hàng: #${invoice.orderId}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Status badge
|
||||
_buildStatusBadge(),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Invoice dates
|
||||
_buildDetailRow(
|
||||
'Ngày hóa đơn:',
|
||||
_formatDate(invoice.issueDate),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
|
||||
_buildDetailRow(
|
||||
'Hạn thanh toán:',
|
||||
_formatDate(invoice.dueDate),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Payment summary section
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grey50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
spacing: 2,
|
||||
children: [
|
||||
_buildPaymentRow(
|
||||
'Tổng tiền:',
|
||||
currencyFormatter.format(invoice.totalAmount),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
if (invoice.amountPaid > 0) ...[
|
||||
const SizedBox(height: 6),
|
||||
_buildPaymentRow(
|
||||
'Đã thanh toán:',
|
||||
currencyFormatter.format(invoice.amountPaid),
|
||||
valueColor: AppColors.success,
|
||||
),
|
||||
],
|
||||
const Divider(),
|
||||
if (invoice.amountRemaining > 0) ...[
|
||||
const SizedBox(height: 6),
|
||||
_buildPaymentRow(
|
||||
'Còn lại:',
|
||||
currencyFormatter.format(invoice.amountRemaining),
|
||||
valueColor: invoice.isOverdue
|
||||
? AppColors.danger
|
||||
: AppColors.warning,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Action button
|
||||
_buildActionButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build detail row
|
||||
Widget _buildDetailRow(String label, String value) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey900,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Build payment summary row
|
||||
Widget _buildPaymentRow(
|
||||
String label,
|
||||
String value, {
|
||||
Color? valueColor,
|
||||
FontWeight? fontWeight,
|
||||
}) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: fontWeight ?? FontWeight.w400,
|
||||
color: valueColor ?? AppColors.grey900,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Build status badge
|
||||
Widget _buildStatusBadge() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: _getStatusColor(invoice.status).withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: _getStatusColor(invoice.status).withValues(alpha: 0.3),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
_getStatusText(invoice.status).toUpperCase(),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: _getStatusColor(invoice.status),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build action button
|
||||
Widget _buildActionButton() {
|
||||
final isPaid = invoice.status == InvoiceStatus.paid || invoice.isPaid;
|
||||
final buttonText = isPaid ? 'Đã hoàn tất' : 'Thanh toán';
|
||||
final buttonColor = isPaid ? AppColors.success : AppColors.primaryBlue;
|
||||
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: isPaid ? null : onPaymentTap,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: buttonColor,
|
||||
disabledBackgroundColor: AppColors.grey100,
|
||||
foregroundColor: Colors.white,
|
||||
disabledForegroundColor: AppColors.grey500,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
elevation: 0,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Icon(Icons.credit_card),
|
||||
Text(
|
||||
buttonText,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Get status color
|
||||
Color _getStatusColor(InvoiceStatus status) {
|
||||
switch (status) {
|
||||
case InvoiceStatus.draft:
|
||||
return AppColors.grey500;
|
||||
case InvoiceStatus.issued:
|
||||
return const Color(0xFFF59E0B); // yellow for unpaid
|
||||
case InvoiceStatus.partiallyPaid:
|
||||
return AppColors.info; // blue for partial
|
||||
case InvoiceStatus.paid:
|
||||
return AppColors.success; // green for paid
|
||||
case InvoiceStatus.overdue:
|
||||
return AppColors.danger; // red for overdue
|
||||
case InvoiceStatus.cancelled:
|
||||
return AppColors.grey500;
|
||||
case InvoiceStatus.refunded:
|
||||
return const Color(0xFFF97316); // orange
|
||||
}
|
||||
}
|
||||
|
||||
/// Get status text in Vietnamese
|
||||
String _getStatusText(InvoiceStatus status) {
|
||||
switch (status) {
|
||||
case InvoiceStatus.draft:
|
||||
return 'Nháp';
|
||||
case InvoiceStatus.issued:
|
||||
return 'Chưa thanh toán';
|
||||
case InvoiceStatus.partiallyPaid:
|
||||
return 'Một phần';
|
||||
case InvoiceStatus.paid:
|
||||
return 'Đã thanh toán';
|
||||
case InvoiceStatus.overdue:
|
||||
return 'Quá hạn';
|
||||
case InvoiceStatus.cancelled:
|
||||
return 'Đã hủy';
|
||||
case InvoiceStatus.refunded:
|
||||
return 'Đã hoàn tiền';
|
||||
}
|
||||
}
|
||||
|
||||
/// Format date to dd/MM/yyyy
|
||||
String _formatDate(DateTime date) {
|
||||
return DateFormat('dd/MM/yyyy').format(date);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user