import 'package:flutter/material.dart'; import 'package:prosapp_web_app/models/usuario_profesional.dart'; import 'package:prosapp_web_app/providers/professionals_provider.dart'; import 'package:prosapp_web_app/providers/theme_provider.dart'; import 'package:prosapp_web_app/services/navigation_service.dart'; import 'package:provider/provider.dart'; class ProfessionalsView extends StatefulWidget { const ProfessionalsView({super.key}); @override State createState() => _ProfessionalsViewState(); } class _ProfessionalsViewState extends State { String _search = ''; @override Widget build(BuildContext context) { final provider = context.watch(); final isDark = context.watch().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 cardBg = isDark ? const Color(0xFF1E293B) : Colors.white; final border = isDark ? const Color(0xFF334155) : const Color(0xFFE5E7EB); final inputFill = isDark ? const Color(0xFF0F172A) : const Color(0xFFF1F5F9); final hintColor = isDark ? const Color(0xFF64748B) : const Color(0xFF94A3B8); return Column( children: [ // ── Barra de búsqueda ── Padding( padding: const EdgeInsets.fromLTRB(0, 0, 0, 14), child: TextField( onChanged: (v) => setState(() => _search = v), style: TextStyle( color: isDark ? Colors.white : const Color(0xFF111827), fontSize: 14, ), decoration: InputDecoration( hintText: 'Buscar por nombre, profesión o ciudad...', hintStyle: TextStyle(color: hintColor, fontSize: 13), prefixIcon: const Icon(Icons.search, color: Color(0xFF42A4EF), size: 20), suffixIcon: _search.isNotEmpty ? IconButton( icon: Icon(Icons.close, size: 18, color: hintColor), onPressed: () => setState(() => _search = ''), ) : null, filled: true, fillColor: inputFill, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: border), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: border), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: Color(0xFF42A4EF), width: 2), ), contentPadding: const EdgeInsets.symmetric(vertical: 13, horizontal: 4), ), ), ), // ── Lista / grid / estados ── Expanded( child: provider.isLoading ? const Center(child: CircularProgressIndicator()) : filtered.isEmpty ? _EmptyState(isDark: isDark, search: _search) : LayoutBuilder( builder: (context, constraints) { final w = constraints.maxWidth; final cols = w >= 1200 ? 4 : w >= 800 ? 3 : w >= 500 ? 2 : 1; return GridView.builder( padding: EdgeInsets.zero, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: cols, crossAxisSpacing: 12, mainAxisSpacing: 12, childAspectRatio: cols == 1 ? 2.4 : 0.78, ), itemCount: filtered.length, itemBuilder: (context, i) => _ProfCard( data: filtered[i], isDark: isDark, cardBg: cardBg, border: border, compact: cols == 1, onTap: () async { final result = await NavigationService .navigateToFuture( '/dashboard/calendar/${filtered[i].user.id}', ); if (context.mounted) { Navigator.pop(context, [ filtered[i], result[0] as DateTime, result[1] as TimeOfDay, ]); } }, ), ); }, ), ), ], ); } } // ── Card ───────────────────────────────────────────────────────────────────── class _ProfCard extends StatelessWidget { final UsuarioProfesional data; final bool isDark; final Color cardBg; final Color border; final bool compact; final VoidCallback onTap; const _ProfCard({ required this.data, required this.isDark, required this.cardBg, required this.border, required this.compact, required this.onTap, }); @override Widget build(BuildContext context) { final textPrimary = isDark ? Colors.white : const Color(0xFF111827); final textSecondary = isDark ? const Color(0xFF94A3B8) : const Color(0xFF6B7280); final hasPic = data.user.picture != null && data.user.picture!.isNotEmpty; final score = data.averageScore; final pm = data.professionalInfo.paymentMethods; if (compact) { // Fila horizontal en pantallas pequeñas return Material( color: cardBg, borderRadius: BorderRadius.circular(14), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(14), child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( borderRadius: BorderRadius.circular(14), border: Border.all(color: border), ), child: Row( children: [ _Avatar(hasPic: hasPic, picture: data.user.picture, name: data.user.name, size: 60), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text(data.user.name, style: TextStyle( fontSize: 15, fontWeight: FontWeight.w700, color: textPrimary)), const SizedBox(height: 3), if (data.professionalInfo.profession.isNotEmpty) Text(data.professionalInfo.profession, style: TextStyle( fontSize: 12, color: const Color(0xFF42A4EF), fontWeight: FontWeight.w500)), const SizedBox(height: 4), Row(children: [ _StarRow(score: score), const SizedBox(width: 10), if (data.user.city != null && data.user.city!.isNotEmpty) Row(children: [ Icon(Icons.location_on_outlined, size: 11, color: textSecondary), const SizedBox(width: 2), Text(data.user.city!, style: TextStyle( fontSize: 11, color: textSecondary)), ]), ]), ], ), ), Icon(Icons.chevron_right, color: textSecondary, size: 20), ], ), ), ), ); } // Tarjeta vertical (grid) return Material( color: cardBg, borderRadius: BorderRadius.circular(16), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(16), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), border: Border.all(color: border), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // ── Header con avatar ── Container( height: 90, decoration: BoxDecoration( gradient: LinearGradient( colors: isDark ? [const Color(0xFF1E3A5F), const Color(0xFF0D1B3E)] : [const Color(0xFFDBEAFE), const Color(0xFFBFDBFE)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: const BorderRadius.vertical( top: Radius.circular(16)), ), child: Stack( clipBehavior: Clip.none, children: [ // Score badge Positioned( top: 10, right: 10, child: Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4), decoration: BoxDecoration( color: Colors.black.withOpacity(0.25), borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.star_rounded, color: Colors.amber, size: 13), const SizedBox(width: 3), Text( score.toStringAsFixed(1), style: const TextStyle( color: Colors.white, fontSize: 11, fontWeight: FontWeight.w700), ), ], ), ), ), // Avatar centrado en la parte inferior del header Positioned( bottom: -28, left: 0, right: 0, child: Center( child: _Avatar( hasPic: hasPic, picture: data.user.picture, name: data.user.name, size: 56, border: true, isDark: isDark, ), ), ), ], ), ), // ── Contenido ── Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(12, 36, 12, 12), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ // Nombre Text( data.user.name, textAlign: TextAlign.center, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w700, color: textPrimary, ), ), // Profesión if (data.professionalInfo.profession.isNotEmpty) ...[ const SizedBox(height: 3), Text( data.professionalInfo.profession, textAlign: TextAlign.center, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 11, color: Color(0xFF42A4EF), fontWeight: FontWeight.w600, ), ), ], // Ciudad if (data.user.city != null && data.user.city!.isNotEmpty) ...[ const SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.location_on_outlined, size: 11, color: textSecondary), const SizedBox(width: 2), Flexible( child: Text( data.user.city!, style: TextStyle( fontSize: 11, color: textSecondary), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), ], const Spacer(), // Métodos de pago if (pm.datafono || pm.nequi || pm.transferencia) ...[ const Divider(height: 16), Wrap( alignment: WrapAlignment.center, spacing: 4, runSpacing: 4, children: [ if (pm.datafono) _PayChip( label: 'Datáfono', icon: Icons.credit_card_outlined, isDark: isDark), if (pm.nequi) _PayChip( label: 'Nequi', icon: Icons.phone_android_outlined, isDark: isDark), if (pm.transferencia) _PayChip( label: 'Transferencia', icon: Icons.account_balance_outlined, isDark: isDark), ], ), ] else Text('Sin métodos de pago', style: TextStyle( fontSize: 10, color: textSecondary)), ], ), ), ), ], ), ), ), ); } } // ── Widgets auxiliares ──────────────────────────────────────────────────────── class _Avatar extends StatelessWidget { final bool hasPic; final String? picture; final String name; final double size; final bool border; final bool isDark; const _Avatar({ required this.hasPic, required this.picture, required this.name, required this.size, this.border = false, this.isDark = false, }); @override Widget build(BuildContext context) { return Container( width: size, height: size, decoration: BoxDecoration( shape: BoxShape.circle, color: const Color(0xFF42A4EF).withOpacity(0.15), border: border ? Border.all( color: isDark ? const Color(0xFF1E293B) : Colors.white, width: 3) : null, ), child: ClipOval( child: hasPic ? FadeInImage.assetNetwork( placeholder: 'loader.gif', image: picture!, fit: BoxFit.cover, ) : Center( child: Text( name.isNotEmpty ? name[0].toUpperCase() : '?', style: TextStyle( color: const Color(0xFF42A4EF), fontSize: size * 0.38, fontWeight: FontWeight.bold, ), ), ), ), ); } } class _StarRow extends StatelessWidget { final double score; const _StarRow({required this.score}); @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.star_rounded, color: Colors.amber, size: 14), const SizedBox(width: 3), Text(score.toStringAsFixed(1), style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Colors.amber)), ], ); } } class _PayChip extends StatelessWidget { final String label; final IconData icon; final bool isDark; const _PayChip( {required this.label, required this.icon, required this.isDark}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), decoration: BoxDecoration( color: const Color(0xFF42A4EF).withOpacity(0.08), borderRadius: BorderRadius.circular(6), border: Border.all( color: const Color(0xFF42A4EF).withOpacity(0.25)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 10, color: const Color(0xFF42A4EF)), const SizedBox(width: 3), Text(label, style: const TextStyle( fontSize: 9, color: Color(0xFF42A4EF), fontWeight: FontWeight.w600)), ], ), ); } } class _EmptyState extends StatelessWidget { final bool isDark; final String search; const _EmptyState({required this.isDark, required this.search}); @override Widget build(BuildContext context) { final textPrimary = isDark ? Colors.white : const Color(0xFF111827); final textSecondary = isDark ? const Color(0xFF94A3B8) : const Color(0xFF6B7280); return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.person_search_outlined, size: 64, color: textSecondary.withOpacity(0.4)), const SizedBox(height: 16), Text( search.isNotEmpty ? 'Sin resultados para "$search"' : 'No hay profesionales disponibles', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: textPrimary), ), const SizedBox(height: 6), Text( search.isNotEmpty ? 'Intenta con otro nombre o profesión' : 'Aún no hay profesionales activos en tu ciudad', style: TextStyle(fontSize: 13, color: textSecondary), ), ], ), ); } }