UI: Replace hidden menu with visible quick action buttons on client profile
This commit is contained in:
@@ -24,77 +24,51 @@ class ClientDetailScreen extends ConsumerWidget {
|
|||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.edit),
|
icon: const Icon(Icons.edit),
|
||||||
|
tooltip: 'Edit Client',
|
||||||
onPressed: () => context.go('/clients/$clientId/edit'),
|
onPressed: () => context.go('/clients/$clientId/edit'),
|
||||||
),
|
),
|
||||||
PopupMenuButton(
|
|
||||||
itemBuilder: (context) => [
|
|
||||||
const PopupMenuItem(
|
|
||||||
value: 'email',
|
|
||||||
child: ListTile(
|
|
||||||
leading: Icon(Icons.email),
|
|
||||||
title: Text('Generate Email'),
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const PopupMenuItem(
|
|
||||||
value: 'contacted',
|
|
||||||
child: ListTile(
|
|
||||||
leading: Icon(Icons.check_circle),
|
|
||||||
title: Text('Mark Contacted'),
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const PopupMenuItem(
|
|
||||||
value: 'delete',
|
|
||||||
child: ListTile(
|
|
||||||
leading: Icon(Icons.delete, color: Colors.red),
|
|
||||||
title: Text('Delete', style: TextStyle(color: Colors.red)),
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onSelected: (value) async {
|
|
||||||
switch (value) {
|
|
||||||
case 'email':
|
|
||||||
context.go('/emails/compose?clientId=$clientId');
|
|
||||||
break;
|
|
||||||
case 'contacted':
|
|
||||||
await ref.read(apiClientProvider).markClientContacted(clientId);
|
|
||||||
ref.invalidate(clientDetailProvider(clientId));
|
|
||||||
break;
|
|
||||||
case 'delete':
|
|
||||||
final confirm = await showDialog<bool>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: const Text('Delete Client'),
|
|
||||||
content: const Text('Are you sure you want to delete this client?'),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context, false),
|
|
||||||
child: const Text('Cancel'),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context, true),
|
|
||||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
|
||||||
child: const Text('Delete'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (confirm == true) {
|
|
||||||
await ref.read(apiClientProvider).deleteClient(clientId);
|
|
||||||
if (context.mounted) {
|
|
||||||
context.go('/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: clientAsync.when(
|
body: clientAsync.when(
|
||||||
data: (client) => _ClientDetailContent(client: client),
|
data: (client) => _ClientDetailContent(
|
||||||
|
client: client,
|
||||||
|
clientId: clientId,
|
||||||
|
onMarkContacted: () async {
|
||||||
|
await ref.read(apiClientProvider).markClientContacted(clientId);
|
||||||
|
ref.invalidate(clientDetailProvider(clientId));
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Marked as contacted')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDelete: () async {
|
||||||
|
final confirm = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Delete Client'),
|
||||||
|
content: const Text('Are you sure you want to delete this client?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context, false),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context, true),
|
||||||
|
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||||
|
child: const Text('Delete'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (confirm == true) {
|
||||||
|
await ref.read(apiClientProvider).deleteClient(clientId);
|
||||||
|
if (context.mounted) {
|
||||||
|
context.go('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
error: (error, stack) => Center(
|
error: (error, stack) => Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -117,8 +91,16 @@ class ClientDetailScreen extends ConsumerWidget {
|
|||||||
|
|
||||||
class _ClientDetailContent extends StatelessWidget {
|
class _ClientDetailContent extends StatelessWidget {
|
||||||
final Map<String, dynamic> client;
|
final Map<String, dynamic> client;
|
||||||
|
final String clientId;
|
||||||
|
final VoidCallback onMarkContacted;
|
||||||
|
final VoidCallback onDelete;
|
||||||
|
|
||||||
const _ClientDetailContent({required this.client});
|
const _ClientDetailContent({
|
||||||
|
required this.client,
|
||||||
|
required this.clientId,
|
||||||
|
required this.onMarkContacted,
|
||||||
|
required this.onDelete,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -165,7 +147,36 @@ class _ClientDetailContent extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Quick Actions
|
||||||
|
Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
_ActionButton(
|
||||||
|
icon: Icons.email,
|
||||||
|
label: 'Email',
|
||||||
|
onTap: () => context.go('/emails/compose?clientId=$clientId'),
|
||||||
|
),
|
||||||
|
_ActionButton(
|
||||||
|
icon: Icons.check_circle,
|
||||||
|
label: 'Contacted',
|
||||||
|
onTap: onMarkContacted,
|
||||||
|
),
|
||||||
|
_ActionButton(
|
||||||
|
icon: Icons.delete_outline,
|
||||||
|
label: 'Delete',
|
||||||
|
color: Colors.red,
|
||||||
|
onTap: onDelete,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
// Contact info
|
// Contact info
|
||||||
_Section(
|
_Section(
|
||||||
@@ -344,3 +355,45 @@ class _InfoRow extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _ActionButton extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final String label;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
final Color? color;
|
||||||
|
|
||||||
|
const _ActionButton({
|
||||||
|
required this.icon,
|
||||||
|
required this.label,
|
||||||
|
required this.onTap,
|
||||||
|
this.color,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final effectiveColor = color ?? Theme.of(context).colorScheme.primary;
|
||||||
|
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: effectiveColor, size: 28),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
color: effectiveColor,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user