From ef6a0327913540b3f7b315884a6bfff06c242de4 Mon Sep 17 00:00:00 2001 From: Hammer Date: Tue, 27 Jan 2026 22:35:51 +0000 Subject: [PATCH] UI: Replace hidden menu with visible quick action buttons on client profile --- .../presentation/client_detail_screen.dart | 189 +++++++++++------- 1 file changed, 121 insertions(+), 68 deletions(-) diff --git a/lib/features/clients/presentation/client_detail_screen.dart b/lib/features/clients/presentation/client_detail_screen.dart index c146d8b..02d350d 100644 --- a/lib/features/clients/presentation/client_detail_screen.dart +++ b/lib/features/clients/presentation/client_detail_screen.dart @@ -24,77 +24,51 @@ class ClientDetailScreen extends ConsumerWidget { actions: [ IconButton( icon: const Icon(Icons.edit), + tooltip: 'Edit Client', 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( - 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( - 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( + 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()), error: (error, stack) => Center( child: Column( @@ -117,8 +91,16 @@ class ClientDetailScreen extends ConsumerWidget { class _ClientDetailContent extends StatelessWidget { final Map 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 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 _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, + ), + ), + ], + ), + ), + ); + } +}