feat: server-side search with debounce for professionals list

- getProfessionals() now accepts optional search param sent to ?search=
- updateCity only triggers the initial load (city no longer used as filter)
- Search field debounces 450ms before firing API call
- TextEditingController added so clear button also resets the field
- Client-side filtering removed (API now handles it and returns top 7)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-12 20:18:25 -05:00
co-authored by Claude Sonnet 4.6
parent 9da159f287
commit e0982995e6
2 changed files with 32 additions and 16 deletions
+27 -9
View File
@@ -1,3 +1,4 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:prosapp_web_app/models/usuario_profesional.dart';
import 'package:prosapp_web_app/providers/professionals_provider.dart';
@@ -14,19 +15,35 @@ class ProfessionalsView extends StatefulWidget {
class _ProfessionalsViewState extends State<ProfessionalsView> {
String _search = '';
Timer? _debounce;
final _controller = TextEditingController();
@override
void dispose() {
_debounce?.cancel();
_controller.dispose();
super.dispose();
}
void _onSearchChanged(String value) {
setState(() => _search = value);
_debounce?.cancel();
_debounce = Timer(const Duration(milliseconds: 450), () {
context.read<ProfessionalsProvider>().getProfessionals(search: value);
});
}
void _clearSearch() {
_controller.clear();
_onSearchChanged('');
}
@override
Widget build(BuildContext context) {
final provider = context.watch<ProfessionalsProvider>();
final isDark = context.watch<ThemeProvider>().isDark;
final filtered = provider.professionals.where((p) {
if (_search.isEmpty) return true;
final q = _search.toLowerCase();
return p.user.name.toLowerCase().contains(q) ||
p.professionalInfo.profession.toLowerCase().contains(q) ||
(p.user.city ?? '').toLowerCase().contains(q);
}).toList();
final filtered = provider.professionals;
final cardBg = isDark ? const Color(0xFF1E293B) : Colors.white;
final border = isDark ? const Color(0xFF334155) : const Color(0xFFE5E7EB);
@@ -39,7 +56,8 @@ class _ProfessionalsViewState extends State<ProfessionalsView> {
Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 14),
child: TextField(
onChanged: (v) => setState(() => _search = v),
controller: _controller,
onChanged: _onSearchChanged,
style: TextStyle(
color: isDark ? Colors.white : const Color(0xFF111827),
fontSize: 14,
@@ -52,7 +70,7 @@ class _ProfessionalsViewState extends State<ProfessionalsView> {
suffixIcon: _search.isNotEmpty
? IconButton(
icon: Icon(Icons.close, size: 18, color: hintColor),
onPressed: () => setState(() => _search = ''),
onPressed: _clearSearch,
)
: null,
filled: true,