diff --git a/lib/view/directory/manage_bucket_screen.dart b/lib/view/directory/manage_bucket_screen.dart index e38fc8f..31745ea 100644 --- a/lib/view/directory/manage_bucket_screen.dart +++ b/lib/view/directory/manage_bucket_screen.dart @@ -23,7 +23,17 @@ class _ManageBucketsScreenState extends State { final ManageBucketController manageBucketController = Get.put(ManageBucketController()); final ProjectController projectController = Get.find(); + final Map _expandedMap = {}; + final TextEditingController searchController = TextEditingController(); + String searchText = ''; + + void _clearSearch() { + searchController.clear(); + setState(() { + searchText = ''; + }); + } @override Widget build(BuildContext context) { @@ -55,28 +65,26 @@ class _ManageBucketsScreenState extends State { MyText.titleLarge('Manage Buckets', fontWeight: 700, color: Colors.black), MySpacing.height(2), - GetBuilder( - builder: (_) { - final projectName = - projectController.selectedProject?.name ?? - 'Select Project'; - return Row( - children: [ - const Icon(Icons.work_outline, - size: 14, color: Colors.grey), - MySpacing.width(4), - Expanded( - child: MyText.bodySmall( - projectName, - fontWeight: 600, - overflow: TextOverflow.ellipsis, - color: Colors.grey[700], - ), + GetBuilder(builder: (_) { + final projectName = + projectController.selectedProject?.name ?? + 'Select Project'; + return Row( + children: [ + const Icon(Icons.work_outline, + size: 14, color: Colors.grey), + MySpacing.width(4), + Expanded( + child: MyText.bodySmall( + projectName, + fontWeight: 600, + overflow: TextOverflow.ellipsis, + color: Colors.grey[700], ), - ], - ); - }, - ), + ), + ], + ); + }), ], ), ), @@ -85,184 +93,237 @@ class _ManageBucketsScreenState extends State { ), ), ), - body: Obx(() { - final buckets = directoryController.contactBuckets; - - if (directoryController.isLoading.value || - manageBucketController.isLoading.value) { - return const Center(child: CircularProgressIndicator()); - } - - if (buckets.isEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.folder_off, size: 48, color: Colors.grey), - MySpacing.height(12), - MyText.bodyMedium("No buckets available.", - fontWeight: 600, color: Colors.grey[700]), - ], - ), - ); - } - - return ListView.separated( - padding: MySpacing.fromLTRB(16, 20, 16, 24), - itemCount: buckets.length, - separatorBuilder: (_, __) => MySpacing.height(16), - itemBuilder: (context, index) { - final bucket = buckets[index]; - final isOwner = - currentUserId != null && bucket.createdBy.id == currentUserId; - final canEdit = isOwner || - widget.permissionController - .hasPermission(Permissions.directoryAdmin) || - widget.permissionController - .hasPermission(Permissions.directoryManager); - - final isExpanded = _expandedMap[bucket.id] ?? false; - - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Padding( - padding: EdgeInsets.only(top: 4), - child: - Icon(Icons.label_outline, size: 26, color: Colors.indigo), + body: Column( + children: [ + Padding( + padding: MySpacing.xy(16, 12), + child: SizedBox( + height: 38, + child: TextField( + controller: searchController, + onChanged: (value) { + setState(() { + searchText = value.toLowerCase(); + }); + }, + decoration: InputDecoration( + contentPadding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 0), + prefixIcon: const Icon(Icons.search, + size: 18, color: Colors.grey), + suffixIcon: searchText.isNotEmpty + ? IconButton( + icon: const Icon(Icons.close, color: Colors.grey), + onPressed: _clearSearch, + ) + : null, + hintText: 'Search buckets...', + filled: true, + fillColor: Colors.white, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Colors.grey.shade300), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide(color: Colors.grey.shade300), + ), ), - MySpacing.width(12), - Expanded( + ), + ), + ), + Expanded( + child: Obx(() { + final buckets = directoryController.contactBuckets.where((bucket) { + return bucket.name.toLowerCase().contains(searchText) || + bucket.description.toLowerCase().contains(searchText) || + bucket.numberOfContacts + .toString() + .contains(searchText); + }).toList(); + + if (directoryController.isLoading.value || + manageBucketController.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } + + if (buckets.isEmpty) { + return Center( child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.folder_off, size: 48, color: Colors.grey), + MySpacing.height(12), + MyText.bodyMedium("No buckets available.", + fontWeight: 600, color: Colors.grey[700]), + ], + ), + ); + } + + return ListView.separated( + padding: MySpacing.fromLTRB(16, 0, 16, 24), + itemCount: buckets.length, + separatorBuilder: (_, __) => MySpacing.height(16), + itemBuilder: (context, index) { + final bucket = buckets[index]; + final isOwner = currentUserId != null && + bucket.createdBy.id == currentUserId; + final canEdit = isOwner || + widget.permissionController + .hasPermission(Permissions.directoryAdmin) || + widget.permissionController + .hasPermission(Permissions.directoryManager); + final isExpanded = _expandedMap[bucket.id] ?? false; + + return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - Expanded( - child: MyText.titleSmall( - bucket.name, - fontWeight: 700, - overflow: TextOverflow.ellipsis, - ), - ), - if (canEdit) - IconButton( - icon: const Icon(Icons.edit_outlined, - size: 20, color: Colors.red), - tooltip: 'View Members', - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - onPressed: () { - final matchedEmployees = manageBucketController - .allEmployees - .where((emp) => - bucket.employeeIds.contains(emp.id)) - .toList(); - TeamMembersBottomSheet.show( - context, matchedEmployees); - }, - ), - ], + const Padding( + padding: EdgeInsets.only(top: 4), + child: Icon(Icons.label_outline, + size: 26, color: Colors.indigo), ), - MySpacing.height(4), - Row( - children: [ - const Icon(Icons.contacts_outlined, - size: 14, color: Colors.grey), - MySpacing.width(4), - MyText.labelSmall( - '${bucket.numberOfContacts} contact(s)', - color: Colors.red, - fontWeight: 600, - ), - MySpacing.width(12), - GestureDetector( - onTap: () { - final matchedEmployees = manageBucketController - .allEmployees - .where((emp) => - bucket.employeeIds.contains(emp.id)) - .toList(); - TeamMembersBottomSheet.show( - context, matchedEmployees); - }, - child: Row( + MySpacing.width(12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( children: [ - const Icon(Icons.ios_share_outlined, + Expanded( + child: MyText.titleSmall( + bucket.name, + fontWeight: 700, + overflow: TextOverflow.ellipsis, + ), + ), + if (canEdit) + IconButton( + icon: const Icon(Icons.edit_outlined, + size: 20, color: Colors.red), + tooltip: 'View Members', + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + onPressed: () { + final matchedEmployees = + manageBucketController.allEmployees + .where((emp) => + bucket.employeeIds + .contains(emp.id)) + .toList(); + TeamMembersBottomSheet.show( + context, matchedEmployees); + }, + ), + ], + ), + MySpacing.height(4), + Row( + children: [ + const Icon(Icons.contacts_outlined, size: 14, color: Colors.grey), MySpacing.width(4), MyText.labelSmall( - 'Shared with', - color: Colors.indigo, + '${bucket.numberOfContacts} contact(s)', + color: Colors.red, fontWeight: 600, ), + MySpacing.width(12), + GestureDetector( + onTap: () { + final matchedEmployees = + manageBucketController.allEmployees + .where((emp) => + bucket.employeeIds + .contains(emp.id)) + .toList(); + TeamMembersBottomSheet.show( + context, matchedEmployees); + }, + child: Row( + children: [ + const Icon(Icons.ios_share_outlined, + size: 14, color: Colors.grey), + MySpacing.width(4), + MyText.labelSmall( + 'Shared with', + color: Colors.indigo, + fontWeight: 600, + ), + ], + ), + ), ], ), - ), - ], - ), - if (bucket.description.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 4), - child: LayoutBuilder( - builder: (context, constraints) { - final span = TextSpan( - text: bucket.description, - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: Colors.grey[700]), - ); - final tp = TextPainter( - text: span, - maxLines: 2, - textDirection: TextDirection.ltr, - )..layout(maxWidth: constraints.maxWidth); + if (bucket.description.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 4), + child: LayoutBuilder( + builder: (context, constraints) { + final span = TextSpan( + text: bucket.description, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: Colors.grey[700]), + ); + final tp = TextPainter( + text: span, + maxLines: 2, + textDirection: TextDirection.ltr, + )..layout(maxWidth: constraints.maxWidth); - final hasOverflow = tp.didExceedMaxLines; + final hasOverflow = tp.didExceedMaxLines; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.bodySmall( - bucket.description, - color: Colors.grey[700], - maxLines: isExpanded ? null : 2, - overflow: isExpanded - ? TextOverflow.visible - : TextOverflow.ellipsis, - ), - if (hasOverflow) - GestureDetector( - onTap: () { - setState(() { - _expandedMap[bucket.id] = !isExpanded; - }); - }, - child: Padding( - padding: const EdgeInsets.only(top: 4), - child: MyText.labelSmall( - isExpanded - ? "Show less" - : "Show more", - fontWeight: 600, - color: Colors.red, + return Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + MyText.bodySmall( + bucket.description, + color: Colors.grey[700], + maxLines: isExpanded ? null : 2, + overflow: isExpanded + ? TextOverflow.visible + : TextOverflow.ellipsis, ), - ), - ), - ], - ); - }, - ), + if (hasOverflow) + GestureDetector( + onTap: () { + setState(() { + _expandedMap[bucket.id] = + !isExpanded; + }); + }, + child: Padding( + padding: const EdgeInsets.only( + top: 4), + child: MyText.labelSmall( + isExpanded + ? "Show less" + : "Show more", + fontWeight: 600, + color: Colors.red, + ), + ), + ), + ], + ); + }, + ), + ), + ], ), + ), ], - ), - ), - ], - ); - }, - ); - }), + ); + }, + ); + }), + ), + ], + ), ); } }