feat: gestión de amigos y mejora visual del dropdown de búsqueda

- FriendshipController: carga también amigos aceptados con su friendship
- friendships/index: tabs con 'Mis amigos' (tabla con Mensaje/Quitar) y 'Solicitudes'
- Notificaciones de solicitud abren directamente la pestaña de solicitudes (#solicitudes)
- Dropdown de búsqueda: bordes redondeados, sombra suave, margen superior, hover en items, etiqueta de sección y separadores finos

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-28 17:20:01 -06:00
parent 0273fbfddd
commit d7ae38ce27
3 changed files with 212 additions and 65 deletions
@@ -17,8 +17,12 @@
<span class="btn btn-primary"><i class="fas fa-search fa-sm"></i></span>
</div>
</div>
<div id="search-results" class="shadow bg-white rounded position-absolute w-100 d-none"
style="top:100%; left:0; z-index:9999; max-height:320px; overflow-y:auto"></div>
<div id="search-results" class="bg-white position-absolute d-none"
style="top:calc(100% + 6px); left:0; right:0; z-index:9999;
max-height:340px; overflow-y:auto;
border:1px solid rgba(0,0,0,.1); border-radius:8px;
box-shadow:0 8px 24px rgba(0,0,0,.12);
min-width:300px"></div>
</div>
<ul class="navbar-nav ml-auto">
@@ -173,6 +177,13 @@
</nav>
</header>
<style>
.search-result-item { transition: background .15s; }
.search-result-item:hover { background: #f8f9fc; }
#search-results > div:first-child { border-radius: 8px 8px 0 0; }
#search-results > div:last-child { border-radius: 0 0 8px 8px; }
</style>
<script>
// ── Mensajes en tiempo real ──────────────────────────────
function updateBadge(id, increment) {
@@ -311,32 +322,48 @@ function markAllNotifsRead() {
function renderResults(users) {
if (!users.length) {
results.innerHTML = '<div class="p-3 text-muted small text-center">Sin resultados</div>';
results.innerHTML = `
<div class="px-4 py-3 text-center">
<i class="fas fa-search text-muted mb-2 d-block" style="font-size:1.4rem"></i>
<span class="text-muted small">Sin resultados</span>
</div>`;
results.classList.remove('d-none');
return;
}
results.innerHTML = users.map(u => {
const btn = friendshipLabel(u.friendship);
return `
<div class="d-flex align-items-center px-3 py-2 border-bottom search-result-item">
<a href="${u.profile_url}" class="d-flex align-items-center flex-grow-1 text-dark text-decoration-none">
<img src="${u.avatar}" class="rounded-circle mr-2" width="36" height="36" style="object-fit:cover">
<span class="font-weight-bold small">${u.name}</span>
</a>
<div class="d-flex align-items-center" style="gap:.4rem">
<button class="btn btn-sm ${btn.cls} friend-toggle-btn"
data-user-id="${u.id}"
data-friendship-id="${u.friendship.id ?? ''}"
data-status="${u.friendship.status}"
data-is-receiver="${u.friendship.is_receiver}">
<i class="fas ${btn.icon} mr-1"></i>${btn.label}
</button>
${u.friendship.status === 'accepted'
? `<a href="${u.chat_url}" class="btn btn-sm btn-outline-primary"><i class="fas fa-comment"></i></a>`
: ''}
</div>
</div>`;
}).join('');
results.innerHTML =
`<div class="px-3 pt-2 pb-1" style="font-size:.7rem;font-weight:600;letter-spacing:.05em;color:#aaa;text-transform:uppercase">Personas</div>` +
users.map((u, i) => {
const btn = friendshipLabel(u.friendship);
const isLast = i === users.length - 1;
return `
<div class="d-flex align-items-center px-3 py-2 search-result-item"
style="${!isLast ? 'border-bottom:1px solid #f1f1f1' : ''}">
<a href="${u.profile_url}"
class="d-flex align-items-center flex-grow-1 text-dark text-decoration-none"
style="min-width:0">
<img src="${u.avatar}" class="rounded-circle flex-shrink-0 mr-3"
width="38" height="38" style="object-fit:cover">
<span class="font-weight-bold small text-truncate">${u.name}</span>
</a>
<div class="d-flex align-items-center flex-shrink-0 ml-2" style="gap:.35rem">
<button class="btn btn-sm ${btn.cls} friend-toggle-btn rounded-pill"
style="font-size:.72rem;padding:.2rem .65rem"
data-user-id="${u.id}"
data-status="${u.friendship.status}"
data-is-receiver="${u.friendship.is_receiver}">
<i class="fas ${btn.icon} mr-1"></i>${btn.label}
</button>
${u.friendship.status === 'accepted'
? `<a href="${u.chat_url}"
class="btn btn-sm btn-outline-primary rounded-pill"
style="font-size:.72rem;padding:.2rem .55rem"
title="Enviar mensaje">
<i class="fas fa-comment"></i>
</a>`
: ''}
</div>
</div>`;
}).join('');
results.classList.remove('d-none');
// Bind toggle buttons