382 lines
14 KiB
Dart
382 lines
14 KiB
Dart
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';
|
|
|
|
class CustomerTile extends StatelessWidget {
|
|
final Customer customer;
|
|
final VoidCallback? onTap;
|
|
final VoidCallback? onEdit;
|
|
final VoidCallback? onDelete;
|
|
|
|
const CustomerTile({
|
|
super.key,
|
|
required this.customer,
|
|
this.onTap,
|
|
this.onEdit,
|
|
this.onDelete,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: AppColor.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.06),
|
|
blurRadius: 16,
|
|
offset: const Offset(0, 4),
|
|
spreadRadius: -2,
|
|
),
|
|
],
|
|
border: Border.all(
|
|
color: customer.isActive
|
|
? AppColor.primary.withOpacity(0.15)
|
|
: Colors.grey.withOpacity(0.1),
|
|
width: 1,
|
|
),
|
|
),
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(16),
|
|
onTap: onTap,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Header Row
|
|
Row(
|
|
children: [
|
|
// Avatar
|
|
Stack(
|
|
children: [
|
|
CircleAvatar(
|
|
radius: 26,
|
|
backgroundColor: _getAvatarColor(customer.name),
|
|
child: Text(
|
|
customer.name.isNotEmpty
|
|
? customer.name[0].toUpperCase()
|
|
: '?',
|
|
style: AppStyle.lg.copyWith(
|
|
color: AppColor.white,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
// Status indicator
|
|
Positioned(
|
|
bottom: 2,
|
|
right: 2,
|
|
child: Container(
|
|
width: 16,
|
|
height: 16,
|
|
decoration: BoxDecoration(
|
|
color: customer.isActive
|
|
? AppColor.success
|
|
: AppColor.error,
|
|
shape: BoxShape.circle,
|
|
border: Border.all(
|
|
color: AppColor.white,
|
|
width: 2,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SpaceWidth(16),
|
|
|
|
// Customer Info
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Name with badges
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
customer.name.isNotEmpty
|
|
? customer.name
|
|
: 'Unknown Customer',
|
|
style: AppStyle.lg.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: AppColor.textPrimary,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
if (customer.isDefault)
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 8,
|
|
vertical: 4,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: AppColor.primary.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Text(
|
|
'DEFAULT',
|
|
style: AppStyle.xs.copyWith(
|
|
color: AppColor.primary,
|
|
fontWeight: FontWeight.bold,
|
|
letterSpacing: 0.5,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SpaceHeight(4),
|
|
|
|
// Contact info
|
|
if (customer.email.isNotEmpty) ...[
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
Icons.email_outlined,
|
|
size: 16,
|
|
color: AppColor.textSecondary,
|
|
),
|
|
const SpaceWidth(6),
|
|
Expanded(
|
|
child: Text(
|
|
customer.email,
|
|
style: AppStyle.sm.copyWith(
|
|
color: AppColor.textSecondary,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SpaceHeight(4),
|
|
],
|
|
|
|
if (customer.phone.isNotEmpty)
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
Icons.phone_outlined,
|
|
size: 16,
|
|
color: AppColor.textSecondary,
|
|
),
|
|
const SpaceWidth(6),
|
|
Text(
|
|
customer.phone,
|
|
style: AppStyle.sm.copyWith(
|
|
color: AppColor.textSecondary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
// Address section
|
|
if (customer.address.isNotEmpty) ...[
|
|
const SpaceHeight(16),
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: AppColor.background,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
Icons.location_on_outlined,
|
|
size: 18,
|
|
color: AppColor.textSecondary,
|
|
),
|
|
const SpaceWidth(8),
|
|
Expanded(
|
|
child: Text(
|
|
customer.address,
|
|
style: AppStyle.sm.copyWith(
|
|
color: AppColor.textSecondary,
|
|
),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
|
|
// Footer with status and dates
|
|
const SpaceHeight(16),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
// 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(20),
|
|
),
|
|
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
|
|
? context.lang.active
|
|
: context.lang.inactive,
|
|
style: AppStyle.sm.copyWith(
|
|
color: customer.isActive
|
|
? AppColor.success
|
|
: AppColor.error,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Created date
|
|
if (customer.createdAt.isNotEmpty)
|
|
Text(
|
|
'${context.lang.joined} ${_formatDate(context, customer.createdAt)}',
|
|
style: AppStyle.xs.copyWith(
|
|
color: AppColor.textSecondary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
// Metadata section (if has any relevant data)
|
|
if (customer.metadata.isNotEmpty && _hasRelevantMetadata()) ...[
|
|
const SpaceHeight(12),
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: AppColor.primary.withOpacity(0.05),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: AppColor.primary.withOpacity(0.1),
|
|
width: 1,
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
Icons.info_outline,
|
|
size: 16,
|
|
color: AppColor.primary,
|
|
),
|
|
const SpaceWidth(8),
|
|
Expanded(
|
|
child: Text(
|
|
_getMetadataInfo(),
|
|
style: AppStyle.xs.copyWith(
|
|
color: AppColor.primary,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Color _getAvatarColor(String name) {
|
|
final colors = [
|
|
AppColor.primary,
|
|
const Color(0xFF9C27B0),
|
|
const Color(0xFFFF9800),
|
|
const Color(0xFF607D8B),
|
|
const Color(0xFF795548),
|
|
const Color(0xFF4CAF50),
|
|
const Color(0xFF2196F3),
|
|
const Color(0xFFE91E63),
|
|
];
|
|
|
|
final index = name.hashCode.abs() % colors.length;
|
|
return colors[index];
|
|
}
|
|
|
|
String _formatDate(BuildContext context, String dateStr) {
|
|
try {
|
|
final date = DateTime.parse(dateStr);
|
|
final now = DateTime.now();
|
|
final difference = now.difference(date).inDays;
|
|
|
|
if (difference == 0) {
|
|
return 'today';
|
|
} else if (difference == 1) {
|
|
return 'yesterday';
|
|
} else if (difference < 30) {
|
|
return '${difference}d ${context.lang.ago}';
|
|
} else if (difference < 365) {
|
|
final months = (difference / 30).floor();
|
|
return '${months}mo ${context.lang.ago}';
|
|
} else {
|
|
final years = (difference / 365).floor();
|
|
return '${years}y ${context.lang.ago}';
|
|
}
|
|
} catch (e) {
|
|
return dateStr;
|
|
}
|
|
}
|
|
|
|
bool _hasRelevantMetadata() {
|
|
return customer.metadata.containsKey('notes') ||
|
|
customer.metadata.containsKey('tags') ||
|
|
customer.metadata.containsKey('source') ||
|
|
customer.metadata.containsKey('preferences');
|
|
}
|
|
|
|
String _getMetadataInfo() {
|
|
final info = <String>[];
|
|
|
|
if (customer.metadata.containsKey('notes')) {
|
|
info.add('Has notes');
|
|
}
|
|
if (customer.metadata.containsKey('tags')) {
|
|
final tags = customer.metadata['tags'];
|
|
if (tags is List && tags.isNotEmpty) {
|
|
info.add('${tags.length} tags');
|
|
}
|
|
}
|
|
if (customer.metadata.containsKey('source')) {
|
|
info.add('Source: ${customer.metadata['source']}');
|
|
}
|
|
|
|
return info.join(' • ');
|
|
}
|
|
}
|