923 lines
28 KiB
Dart
923 lines
28 KiB
Dart
/// Page: Invoice Detail Page
|
|
///
|
|
/// Displays invoice detail following html/invoice-detail.html design.
|
|
library;
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:worker/core/widgets/loading_indicator.dart';
|
|
import 'package:share_plus/share_plus.dart';
|
|
import 'package:worker/core/constants/ui_constants.dart';
|
|
import 'package:worker/core/utils/extensions.dart';
|
|
import 'package:worker/features/invoices/domain/entities/invoice.dart';
|
|
import 'package:worker/features/invoices/presentation/providers/invoices_provider.dart';
|
|
|
|
/// Invoice Detail Page
|
|
///
|
|
/// Features:
|
|
/// - Invoice header with status
|
|
/// - Seller and buyer information (2-column grid)
|
|
/// - Product list table with unit price
|
|
/// - Invoice summary (total, discount, grand total)
|
|
/// - Share and contact support actions
|
|
class InvoiceDetailPage extends ConsumerWidget {
|
|
const InvoiceDetailPage({
|
|
super.key,
|
|
required this.invoiceId,
|
|
});
|
|
|
|
final String invoiceId;
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final invoiceAsync = ref.watch(invoiceDetailProvider(invoiceId));
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
|
|
return Scaffold(
|
|
backgroundColor: colorScheme.surfaceContainerLowest,
|
|
appBar: AppBar(
|
|
leading: IconButton(
|
|
icon: Icon(Icons.arrow_back, color: colorScheme.onSurface),
|
|
onPressed: () => context.pop(),
|
|
),
|
|
title: Text(
|
|
'Chi tiết Hóa đơn',
|
|
style: TextStyle(
|
|
color: colorScheme.onSurface,
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
elevation: AppBarSpecs.elevation,
|
|
backgroundColor: colorScheme.surface,
|
|
centerTitle: false,
|
|
actions: [
|
|
IconButton(
|
|
icon: FaIcon(
|
|
FontAwesomeIcons.shareNodes,
|
|
size: 20,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
onPressed: () => _shareInvoice(context),
|
|
),
|
|
const SizedBox(width: AppSpacing.sm),
|
|
],
|
|
),
|
|
body: invoiceAsync.when(
|
|
data: (invoice) => _buildContent(context, invoice),
|
|
loading: () => const CustomLoadingIndicator(),
|
|
error: (error, stack) => _buildErrorState(context, ref, error),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildContent(BuildContext context, Invoice invoice) {
|
|
return SingleChildScrollView(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
children: [
|
|
// Invoice Header Card
|
|
_buildHeaderCard(context, invoice),
|
|
const SizedBox(height: 16),
|
|
|
|
// Products Section
|
|
_buildProductsCard(context, invoice),
|
|
const SizedBox(height: 16),
|
|
|
|
// Action Button
|
|
_buildActionButton(context),
|
|
const SizedBox(height: 40),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Build invoice header card
|
|
Widget _buildHeaderCard(BuildContext context, Invoice invoice) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
color: colorScheme.surface,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: colorScheme.outline.withValues(alpha: 0.1),
|
|
width: 1.5,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: colorScheme.shadow.withValues(alpha: 0.08),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
children: [
|
|
// Invoice Header Section (centered)
|
|
Container(
|
|
padding: const EdgeInsets.only(bottom: 24),
|
|
decoration: BoxDecoration(
|
|
border: Border(
|
|
bottom: BorderSide(
|
|
color: colorScheme.outlineVariant,
|
|
width: 2,
|
|
),
|
|
),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
// Invoice Icon
|
|
Container(
|
|
width: 80,
|
|
height: 80,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
colors: [
|
|
colorScheme.primary,
|
|
colorScheme.primary.withValues(alpha: 0.8),
|
|
],
|
|
),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: const Center(
|
|
child: FaIcon(
|
|
FontAwesomeIcons.fileInvoiceDollar,
|
|
size: 32,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Title
|
|
Text(
|
|
'HÓA ĐƠN GTGT',
|
|
style: TextStyle(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w700,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
|
|
// Invoice Number
|
|
Text(
|
|
'#${invoice.name}',
|
|
style: TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w600,
|
|
color: colorScheme.primary,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
// Status Badge
|
|
_StatusBadge(
|
|
status: invoice.status,
|
|
statusColor: invoice.statusColor,
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Invoice Meta Info
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
_MetaItem(label: 'Ngày xuất:', value: invoice.formattedDate),
|
|
if (invoice.orderId != null) ...[
|
|
const SizedBox(width: 32),
|
|
_MetaItem(label: 'Đơn hàng:', value: '#${invoice.orderId}'),
|
|
],
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Company Information Section (2-column grid)
|
|
if (invoice.sellerInfo != null || invoice.buyerInfo != null) ...[
|
|
const SizedBox(height: 24),
|
|
_buildCompanyInfoSection(context, invoice),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Build company info section (seller and buyer) - 2 column grid
|
|
Widget _buildCompanyInfoSection(BuildContext context, Invoice invoice) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
final screenWidth = MediaQuery.of(context).size.width;
|
|
final isWideScreen = screenWidth > 600;
|
|
|
|
if (isWideScreen) {
|
|
// 2-column grid for wide screens
|
|
return Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Seller Info
|
|
if (invoice.sellerInfo != null)
|
|
Expanded(
|
|
child: _CompanyInfoBlock(
|
|
icon: FontAwesomeIcons.building,
|
|
iconColor: colorScheme.primary,
|
|
title: 'Đơn vị bán hàng',
|
|
lines: _buildSellerInfoLines(invoice),
|
|
),
|
|
),
|
|
|
|
if (invoice.sellerInfo != null && invoice.buyerInfo != null)
|
|
const SizedBox(width: 24),
|
|
|
|
// Buyer Info
|
|
if (invoice.buyerInfo != null)
|
|
Expanded(
|
|
child: _CompanyInfoBlock(
|
|
icon: FontAwesomeIcons.userTie,
|
|
iconColor: Colors.green.shade600,
|
|
title: 'Đơn vị mua hàng',
|
|
lines: _buildBuyerInfoLines(invoice),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
} else {
|
|
// Single column for narrow screens
|
|
return Column(
|
|
children: [
|
|
// Seller Info
|
|
if (invoice.sellerInfo != null)
|
|
_CompanyInfoBlock(
|
|
icon: FontAwesomeIcons.building,
|
|
iconColor: colorScheme.primary,
|
|
title: 'Đơn vị bán hàng',
|
|
lines: _buildSellerInfoLines(invoice),
|
|
),
|
|
|
|
if (invoice.sellerInfo != null && invoice.buyerInfo != null)
|
|
const SizedBox(height: 24),
|
|
|
|
// Buyer Info
|
|
if (invoice.buyerInfo != null)
|
|
_CompanyInfoBlock(
|
|
icon: FontAwesomeIcons.userTie,
|
|
iconColor: Colors.green.shade600,
|
|
title: 'Đơn vị mua hàng',
|
|
lines: _buildBuyerInfoLines(invoice),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
List<_InfoLine> _buildSellerInfoLines(Invoice invoice) {
|
|
return [
|
|
if (invoice.sellerInfo!.companyName != null)
|
|
_InfoLine(label: 'Công ty', value: invoice.sellerInfo!.companyName!),
|
|
if (invoice.sellerInfo!.taxCode != null)
|
|
_InfoLine(label: 'Mã số thuế', value: invoice.sellerInfo!.taxCode!),
|
|
if (invoice.sellerInfo!.fullAddress.isNotEmpty)
|
|
_InfoLine(label: 'Địa chỉ', value: invoice.sellerInfo!.fullAddress),
|
|
if (invoice.sellerInfo!.phone != null)
|
|
_InfoLine(label: 'Điện thoại', value: invoice.sellerInfo!.phone!),
|
|
if (invoice.sellerInfo!.email != null)
|
|
_InfoLine(label: 'Email', value: invoice.sellerInfo!.email!),
|
|
];
|
|
}
|
|
|
|
List<_InfoLine> _buildBuyerInfoLines(Invoice invoice) {
|
|
return [
|
|
if (invoice.buyerInfo!.name != null)
|
|
_InfoLine(label: 'Người mua hàng', value: invoice.buyerInfo!.name!),
|
|
if (invoice.customerName != null)
|
|
_InfoLine(label: 'Tên đơn vị', value: invoice.customerName!),
|
|
if (invoice.buyerInfo!.taxCode != null)
|
|
_InfoLine(label: 'Mã số thuế', value: invoice.buyerInfo!.taxCode!),
|
|
if (invoice.buyerInfo!.fullAddress.isNotEmpty)
|
|
_InfoLine(label: 'Địa chỉ', value: invoice.buyerInfo!.fullAddress),
|
|
if (invoice.buyerInfo!.phone != null)
|
|
_InfoLine(label: 'Điện thoại', value: invoice.buyerInfo!.phone!),
|
|
if (invoice.buyerInfo!.email != null)
|
|
_InfoLine(label: 'Email', value: invoice.buyerInfo!.email!),
|
|
];
|
|
}
|
|
|
|
/// Build products card
|
|
Widget _buildProductsCard(BuildContext context, Invoice invoice) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
color: colorScheme.surface,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: colorScheme.outline.withValues(alpha: 0.1),
|
|
width: 1.5,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: colorScheme.shadow.withValues(alpha: 0.08),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Section Title
|
|
Row(
|
|
children: [
|
|
FaIcon(
|
|
FontAwesomeIcons.boxOpen,
|
|
size: 18,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'Chi tiết hàng hóa',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w700,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Products Table
|
|
if (invoice.items != null && invoice.items!.isNotEmpty)
|
|
_buildProductsTable(context, invoice)
|
|
else
|
|
Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Text(
|
|
'Không có thông tin sản phẩm',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 20),
|
|
|
|
// Invoice Summary
|
|
_buildInvoiceSummary(context, invoice),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Build products table - with Đơn giá column
|
|
Widget _buildProductsTable(BuildContext context, Invoice invoice) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
|
|
return Column(
|
|
children: [
|
|
// Table Header
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: colorScheme.surface,
|
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(8)),
|
|
border: Border(
|
|
bottom: BorderSide(
|
|
color: colorScheme.outlineVariant,
|
|
width: 2,
|
|
),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
// # column
|
|
SizedBox(
|
|
width: 32,
|
|
child: Text(
|
|
'#',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w600,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
),
|
|
),
|
|
// Tên hàng hóa column
|
|
Expanded(
|
|
flex: 3,
|
|
child: Text(
|
|
'Tên hàng hóa',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w600,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
),
|
|
),
|
|
// Số lượng column
|
|
SizedBox(
|
|
width: 55,
|
|
child: Text(
|
|
'SL',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w600,
|
|
color: colorScheme.onSurfaceVariant,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
// Đơn giá column
|
|
SizedBox(
|
|
width: 80,
|
|
child: Text(
|
|
'Đơn giá',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w600,
|
|
color: colorScheme.onSurfaceVariant,
|
|
),
|
|
textAlign: TextAlign.right,
|
|
),
|
|
),
|
|
// Thành tiền column
|
|
SizedBox(
|
|
width: 90,
|
|
child: Text(
|
|
'Thành tiền',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w600,
|
|
color: colorScheme.onSurfaceVariant,
|
|
),
|
|
textAlign: TextAlign.right,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Table Body
|
|
...invoice.items!.asMap().entries.map((entry) {
|
|
final index = entry.key;
|
|
final item = entry.value;
|
|
return Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
border: Border(
|
|
bottom: BorderSide(color: colorScheme.outlineVariant.withValues(alpha: 0.5)),
|
|
),
|
|
),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// # column
|
|
SizedBox(
|
|
width: 32,
|
|
child: Text(
|
|
'${index + 1}',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
),
|
|
),
|
|
// Tên hàng hóa column
|
|
Expanded(
|
|
flex: 3,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
item.itemName,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
'SKU: ${item.itemCode}',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.7),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
// Số lượng column
|
|
SizedBox(
|
|
width: 55,
|
|
child: Text(
|
|
item.qty.toStringAsFixed(item.qty.truncateToDouble() == item.qty ? 0 : 2),
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
// Đơn giá column
|
|
SizedBox(
|
|
width: 80,
|
|
child: Text(
|
|
item.rate.toVNCurrency,
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
textAlign: TextAlign.right,
|
|
),
|
|
),
|
|
// Thành tiền column
|
|
SizedBox(
|
|
width: 90,
|
|
child: Text(
|
|
item.amount.toVNCurrency,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
textAlign: TextAlign.right,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}),
|
|
],
|
|
);
|
|
}
|
|
|
|
/// Build invoice summary
|
|
Widget _buildInvoiceSummary(BuildContext context, Invoice invoice) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
|
|
return Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: colorScheme.surfaceContainerLowest,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
// Subtotal
|
|
if (invoice.total != null)
|
|
_SummaryRow(
|
|
label: 'Tổng tiền hàng:',
|
|
value: invoice.total!.toVNCurrency,
|
|
),
|
|
|
|
// Discount
|
|
if (invoice.discountAmount != null && invoice.discountAmount! > 0)
|
|
_SummaryRow(
|
|
label: 'Chiết khấu:',
|
|
value: '-${invoice.discountAmount!.toVNCurrency}',
|
|
valueColor: const Color(0xFF059669),
|
|
),
|
|
|
|
// Grand Total
|
|
Container(
|
|
padding: const EdgeInsets.only(top: 16),
|
|
margin: const EdgeInsets.only(top: 8),
|
|
decoration: BoxDecoration(
|
|
border: Border(
|
|
top: BorderSide(color: colorScheme.outlineVariant, width: 2),
|
|
),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'TỔNG THANH TOÁN:',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w700,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
),
|
|
Text(
|
|
invoice.grandTotal.toVNCurrency,
|
|
style: const TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w700,
|
|
color: Color(0xFFDC2626),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Build action button
|
|
Widget _buildActionButton(BuildContext context) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
|
|
return SizedBox(
|
|
width: double.infinity,
|
|
child: OutlinedButton.icon(
|
|
onPressed: () => _contactSupport(context),
|
|
icon: const FaIcon(FontAwesomeIcons.comments, size: 18),
|
|
label: const Text('Liên hệ hỗ trợ'),
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: colorScheme.onSurface,
|
|
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 20),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
side: BorderSide(color: colorScheme.outlineVariant, width: 2),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Build error state
|
|
Widget _buildErrorState(BuildContext context, WidgetRef ref, Object error) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
FaIcon(
|
|
FontAwesomeIcons.circleExclamation,
|
|
size: 64,
|
|
color: colorScheme.error.withValues(alpha: 0.7),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'Có lỗi xảy ra',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w600,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 32),
|
|
child: Text(
|
|
error.toString(),
|
|
style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
ElevatedButton(
|
|
onPressed: () => ref.invalidate(invoiceDetailProvider(invoiceId)),
|
|
child: const Text('Thử lại'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Share invoice
|
|
void _shareInvoice(BuildContext context) {
|
|
SharePlus.instance.share(
|
|
ShareParams(
|
|
text: 'Chi tiết hóa đơn #$invoiceId - EuroTile Worker',
|
|
subject: 'Hóa đơn #$invoiceId',
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Contact support
|
|
void _contactSupport(BuildContext context) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Hotline hỗ trợ: 1900 1234'),
|
|
duration: Duration(seconds: 3),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Status Badge Widget - with uppercase text
|
|
class _StatusBadge extends StatelessWidget {
|
|
const _StatusBadge({
|
|
required this.status,
|
|
required this.statusColor,
|
|
});
|
|
|
|
final String status;
|
|
final String statusColor;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
Color backgroundColor;
|
|
Color textColor;
|
|
|
|
switch (statusColor.toLowerCase()) {
|
|
case 'success':
|
|
backgroundColor = const Color(0xFFD1FAE5);
|
|
textColor = const Color(0xFF065F46);
|
|
case 'danger':
|
|
backgroundColor = const Color(0xFFFEF3C7);
|
|
textColor = const Color(0xFFD97706);
|
|
case 'warning':
|
|
backgroundColor = const Color(0xFFFEF3C7);
|
|
textColor = const Color(0xFFD97706);
|
|
case 'info':
|
|
backgroundColor = const Color(0xFFE0E7FF);
|
|
textColor = const Color(0xFF3730A3);
|
|
default:
|
|
backgroundColor = const Color(0xFFF3F4F6);
|
|
textColor = const Color(0xFF6B7280);
|
|
}
|
|
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: backgroundColor,
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Text(
|
|
status.toUpperCase(),
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w600,
|
|
color: textColor,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Meta Item Widget
|
|
class _MetaItem extends StatelessWidget {
|
|
const _MetaItem({
|
|
required this.label,
|
|
required this.value,
|
|
});
|
|
|
|
final String label;
|
|
final String value;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
return Column(
|
|
children: [
|
|
Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Company Info Block Widget
|
|
class _CompanyInfoBlock extends StatelessWidget {
|
|
const _CompanyInfoBlock({
|
|
required this.icon,
|
|
required this.iconColor,
|
|
required this.title,
|
|
required this.lines,
|
|
});
|
|
|
|
final IconData icon;
|
|
final Color iconColor;
|
|
final String title;
|
|
final List<_InfoLine> lines;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
FaIcon(icon, size: 16, color: iconColor),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w700,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
...lines,
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Info Line Widget
|
|
class _InfoLine extends StatelessWidget {
|
|
const _InfoLine({
|
|
required this.label,
|
|
required this.value,
|
|
});
|
|
|
|
final String label;
|
|
final String value;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 6),
|
|
child: RichText(
|
|
text: TextSpan(
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: colorScheme.onSurfaceVariant,
|
|
height: 1.6,
|
|
),
|
|
children: [
|
|
TextSpan(
|
|
text: '$label: ',
|
|
style: const TextStyle(fontWeight: FontWeight.w400),
|
|
),
|
|
TextSpan(
|
|
text: value,
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.w600,
|
|
color: colorScheme.onSurface,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Summary Row Widget
|
|
class _SummaryRow extends StatelessWidget {
|
|
const _SummaryRow({
|
|
required this.label,
|
|
required this.value,
|
|
this.valueColor,
|
|
});
|
|
|
|
final String label;
|
|
final String value;
|
|
final Color? valueColor;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 15,
|
|
color: colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w600,
|
|
color: valueColor ?? colorScheme.onSurface,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|