Skip to content

Commit 73346ec

Browse files
INDIGOAZULclaude
andcommitted
feat: discover open groups in Mis Grupos tab
"Rol: Todos" filter now shows public groups with available spots below user's own groups. Amber "Grupos Abiertos" divider separates sections. "Solicitar unirse" button sends join request via existing /join-pg endpoint. Public groups hidden when role/payment filters are active. CSS: gc-role-open, gc-btn-join, gc-section-divider (~30 lines). Cache: groups-page.css?v=3.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9bded3b commit 73346ec

2 files changed

Lines changed: 179 additions & 13 deletions

File tree

css/groups-page.css

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2604,6 +2604,44 @@
26042604
margin-bottom: 24px;
26052605
}
26062606

2607+
/* Empty state buttons (global - both Grupos + Tandas) */
2608+
.empty-state .btn,
2609+
.tandas-empty-state .btn {
2610+
display: inline-flex;
2611+
align-items: center;
2612+
justify-content: center;
2613+
gap: 6px;
2614+
padding: 8px 18px;
2615+
border-radius: 8px;
2616+
font-size: 0.85rem;
2617+
font-weight: 500;
2618+
cursor: pointer;
2619+
border: none;
2620+
transition: background-color 0.2s, transform 0.15s;
2621+
}
2622+
.empty-state .btn-primary,
2623+
.tandas-empty-state .btn-primary {
2624+
background: linear-gradient(135deg, #00FFFF, #0d9488);
2625+
color: #0f172a;
2626+
font-weight: 600;
2627+
}
2628+
.empty-state .btn-primary:hover,
2629+
.tandas-empty-state .btn-primary:hover {
2630+
transform: translateY(-1px);
2631+
box-shadow: 0 4px 15px rgba(0,255,255,0.25);
2632+
}
2633+
.empty-state .btn-outline,
2634+
.tandas-empty-state .btn-outline {
2635+
background: transparent;
2636+
border: 1px solid rgba(0,255,255,0.3);
2637+
color: #00FFFF;
2638+
}
2639+
.empty-state .btn-outline:hover,
2640+
.tandas-empty-state .btn-outline:hover {
2641+
background: rgba(0,255,255,0.05);
2642+
transform: translateY(-1px);
2643+
}
2644+
26072645
/* LOADING STATE */
26082646
.loading-state {
26092647
text-align: center;
@@ -3972,3 +4010,33 @@
39724010
background: rgba(0,255,255,0.1);
39734011
color: #00FFFF;
39744012
}
4013+
4014+
/* ===== Discover Open Groups (v2026-02-25) ===== */
4015+
.gc-role-open { background: rgba(245,158,11,0.15); color: #f59e0b; }
4016+
4017+
.gc-btn-join {
4018+
background: linear-gradient(135deg, #f59e0b, #d97706);
4019+
color: #0f172a; font-weight: 600; border: none;
4020+
padding: 8px 18px; border-radius: 8px; cursor: pointer;
4021+
font-size: 0.85rem;
4022+
transition: transform 0.15s, box-shadow 0.2s;
4023+
}
4024+
.gc-btn-join:hover {
4025+
transform: translateY(-1px);
4026+
box-shadow: 0 4px 15px rgba(245,158,11,0.3);
4027+
}
4028+
.gc-btn-join:disabled {
4029+
opacity: 0.6; cursor: not-allowed; transform: none;
4030+
}
4031+
4032+
.gc-section-divider {
4033+
display: flex; align-items: center; gap: 12px;
4034+
margin: 20px 0 12px; color: #f59e0b;
4035+
font-size: 0.8rem; font-weight: 600;
4036+
text-transform: uppercase; letter-spacing: 0.5px;
4037+
}
4038+
.gc-section-divider::before,
4039+
.gc-section-divider::after {
4040+
content: ''; flex: 1; height: 1px;
4041+
background: rgba(245,158,11,0.2);
4042+
}

groups-advanced-system.html

Lines changed: 111 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<link rel="stylesheet" href="css/mobile-network-switcher-fix.css">
3030
<link rel="stylesheet" href="css/mobile-wallet-connect-fix.css">
3131
<link rel="stylesheet" href="css/transaction-modal.css">
32-
<link rel="stylesheet" href="css/groups-page.css?v=2.9">
32+
<link rel="stylesheet" href="css/groups-page.css?v=3.0">
3333
<link rel="stylesheet" href="shared-components.css">
3434
<link rel="stylesheet" href="notification-system.css">
3535
<link rel="stylesheet" href="loading-styles.css">
@@ -292,6 +292,31 @@ <h1 class="main-feed-title" style="font-size:1.5rem;font-weight:700;">
292292
refreshTandas();
293293
}
294294

295+
// Non-blocking fetch of public groups for discovery
296+
(async function() {
297+
try {
298+
var pubRes = await fetch(API_BASE + '/api/groups/public-pg', {
299+
headers: { 'Authorization': 'Bearer ' + authToken }
300+
});
301+
var pubData = await pubRes.json();
302+
if (pubData.success) {
303+
window.publicGroupsData = (pubData.data.groups || []).map(function(g) {
304+
var adapted = adaptPostgreSQLGroup(g);
305+
adapted.my_role = 'open';
306+
adapted.my_payment_status = null;
307+
adapted.my_alerts = [];
308+
adapted.is_public = true;
309+
adapted.admin_name = g.admin_name || null;
310+
return adapted;
311+
});
312+
} else {
313+
window.publicGroupsData = [];
314+
}
315+
} catch(e) {
316+
window.publicGroupsData = [];
317+
}
318+
})();
319+
295320
// ✅ Check if there's a newly created group to highlight
296321
const newlyCreatedId = sessionStorage.getItem('newly_created_group_id');
297322
if (newlyCreatedId) {
@@ -496,7 +521,7 @@ <h1 class="main-feed-title" style="font-size:1.5rem;font-weight:700;">
496521
function renderGroups() {
497522
const container = document.getElementById('groupsContainer');
498523

499-
if (allGroups.length === 0) {
524+
if (allGroups.length === 0 && (!window.publicGroupsData || window.publicGroupsData.length === 0)) {
500525
container.innerHTML = '<div class="empty-state">' +
501526
'<div class="empty-state-icon"><i class="fas fa-users" style="font-size:3rem;color:#00FFFF;"></i></div>' +
502527
'<h3>Comienza tu primera tanda</h3>' +
@@ -518,13 +543,22 @@ <h1 class="main-feed-title" style="font-size:1.5rem;font-weight:700;">
518543
return;
519544
}
520545

521-
const html = `
522-
<div class="groups-grid">
523-
${filteredGroups.map(group => renderGroupCard(group)).join('')}
524-
</div>
525-
`;
546+
var myCards = '';
547+
var pubCards = '';
548+
filteredGroups.forEach(function(group) {
549+
if (group.is_public) {
550+
pubCards += renderGroupCard(group);
551+
} else {
552+
myCards += renderGroupCard(group);
553+
}
554+
});
555+
var finalHtml = '<div class="groups-grid">' + myCards;
556+
if (pubCards) {
557+
finalHtml += '</div><div class="gc-section-divider"><span>Grupos Abiertos</span></div><div class="groups-grid">' + pubCards;
558+
}
559+
finalHtml += '</div>';
526560

527-
container.innerHTML = html;
561+
container.innerHTML = finalHtml;
528562

529563
// Dispatch event for position requests to load
530564
setTimeout(() => {
@@ -672,7 +706,7 @@ <h1 class="main-feed-title" style="font-size:1.5rem;font-weight:700;">
672706
'weekly': 'Semanal', 'biweekly': 'Quincenal', 'monthly': 'Mensual'
673707
};
674708
const roleLabels = {
675-
'creator': 'Creador', 'coordinator': 'Coordinador', 'member': 'Miembro'
709+
'creator': 'Creador', 'coordinator': 'Coordinador', 'member': 'Miembro', 'open': 'Abierto'
676710
};
677711
const alertIcons = {
678712
'success': '&#10003;', 'warning': '&#9888;', 'danger': '&#10007;', 'info': '&#8505;'
@@ -749,6 +783,18 @@ <h1 class="main-feed-title" style="font-size:1.5rem;font-weight:700;">
749783
actionsHtml += '<button class="btn pr-request-btn" data-action="grp-request-extension" data-group-id="' + escapeHtml(group.id) + '">Solicitar Prorroga</button>';
750784
}
751785

786+
// Public group card overrides (discover open groups)
787+
if (group.is_public) {
788+
statusColor = '#f59e0b';
789+
roleClass = 'gc-role-open';
790+
if (group.admin_name) {
791+
var adminPart = 'Creado por: ' + escapeHtml(group.admin_name);
792+
subtitle = subtitle ? subtitle + ' &middot; ' + adminPart : adminPart;
793+
}
794+
alertsHtml = '';
795+
actionsHtml = '<button class="btn gc-btn-join" data-action="grp-join-request" data-group-id="' + escapeHtml(group.id) + '" data-group-name="' + escapeHtml(group.name) + '">Solicitar unirse</button>';
796+
}
797+
752798
return '<div class="group-card" data-group-id="' + escapeHtml(group.id) + '" data-status="' + escapeHtml(st) + '" style="border-color:' + statusColor + '">' +
753799
'<div class="gc-header">' +
754800
'<div class="gc-avatar" style="background:' + statusColor + ';color:' + (st === 'suspended' || st === 'cancelled' ? '#fff' : '#0f172a') + '">' + avatarLetter + '</div>' +
@@ -796,6 +842,16 @@ <h1 class="main-feed-title" style="font-size:1.5rem;font-weight:700;">
796842
return matchesRole && matchesPayment && matchesSearch;
797843
});
798844

845+
// Append public groups only when both filters are "Todos"
846+
if (roleFilter === 'all' && paymentFilter === 'all') {
847+
var pubGroups = (window.publicGroupsData || []).filter(function(g) {
848+
return !searchTerm || (g.name && g.name.toLowerCase().includes(searchTerm));
849+
});
850+
if (pubGroups.length > 0) {
851+
filteredGroups = filteredGroups.concat(pubGroups);
852+
}
853+
}
854+
799855
renderGroups();
800856

801857
// Also refresh tandas tab to sync turn positions
@@ -3667,10 +3723,10 @@ <h4 class="grp-sidebar-stats-title">Guia</h4>
36673723
<h3 style="color: #f8fafc; margin-bottom: 12px;">Aún no perteneces a ningún grupo</h3>
36683724
<p style="color: #9ca3af; margin-bottom: 24px;">Únete a un grupo existente o crea uno nuevo para comenzar tu primera tanda.</p>
36693725
<div style="display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;">
3670-
<button class="btn btn-primary" data-action="grp-switch-tab" data-tab="groups" style="padding: 12px 24px;">
3726+
<button class="btn btn-primary" data-action="grp-switch-tab" data-tab="groups" style="padding: 8px 18px; font-size: 0.85rem;">
36713727
<i class="fas fa-search"></i> Explorar Grupos
36723728
</button>
3673-
<button class="btn btn-outline" data-action="grp-switch-tab" data-tab="create" style="padding: 12px 24px;">
3729+
<button class="btn btn-outline" data-action="grp-switch-tab" data-tab="create" style="padding: 8px 18px; font-size: 0.85rem;">
36743730
<i class="fas fa-plus"></i> Crear Grupo
36753731
</button>
36763732
</div>
@@ -3683,7 +3739,7 @@ <h3 style="color: #f8fafc; margin-bottom: 12px;">Aún no perteneces a ningún gr
36833739
<h3 style="color: #f8fafc; margin-bottom: 12px;">Tus grupos aún no tienen tandas activas</h3>
36843740
<p style="color: #9ca3af; margin-bottom: 24px;">Estás en ${groupCount || 1} grupo(s). El coordinador debe activar la tanda para comenzar.</p>
36853741
<div style="display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;">
3686-
<button class="btn btn-primary" data-action="grp-switch-tab" data-tab="my-groups" style="padding: 12px 24px;">
3742+
<button class="btn btn-primary" data-action="grp-switch-tab" data-tab="my-groups" style="padding: 8px 18px; font-size: 0.85rem;">
36873743
<i class="fas fa-users"></i> Ver Mis Grupos
36883744
</button>
36893745
</div>
@@ -3696,7 +3752,7 @@ <h3 style="color: #f8fafc; margin-bottom: 12px;">Tus grupos aún no tienen tanda
36963752
<div class="empty-icon" style="font-size: 4rem; margin-bottom: 20px;">💰</div>
36973753
<h3 style="color: #f8fafc; margin-bottom: 12px;">No hay tandas</h3>
36983754
<p style="color: #9ca3af; margin-bottom: 24px;">${escapeHtml(stateOrMessage)}</p>
3699-
<button class="btn btn-primary" data-action="grp-switch-tab" data-tab="groups" style="padding: 12px 24px;">
3755+
<button class="btn btn-primary" data-action="grp-switch-tab" data-tab="groups" style="padding: 8px 18px; font-size: 0.85rem;">
37003756
<i class="fas fa-search"></i> Explorar Grupos
37013757
</button>
37023758
</div>
@@ -11904,6 +11960,48 @@ <h4><i class="fas fa-list-ol"></i> Orden de Turnos</h4>
1190411960
break;
1190511961

1190611962
// Filter & Retry
11963+
case 'grp-join-request':
11964+
(function() {
11965+
var joinBtn = btn;
11966+
var joinGid = joinBtn.getAttribute('data-group-id');
11967+
var joinName = joinBtn.getAttribute('data-group-name') || 'este grupo';
11968+
if (!joinGid) return;
11969+
showConfirm('Deseas solicitar unirte a ' + escapeHtml(joinName) + '?', function() {
11970+
joinBtn.disabled = true;
11971+
joinBtn.textContent = 'Enviando...';
11972+
var authTk = localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token') || '';
11973+
fetch(API_BASE + '/api/groups/' + encodeURIComponent(joinGid) + '/join-pg', {
11974+
method: 'POST',
11975+
headers: {
11976+
'Content-Type': 'application/json',
11977+
'Authorization': 'Bearer ' + authTk
11978+
},
11979+
body: JSON.stringify({})
11980+
})
11981+
.then(function(r) { return r.json(); })
11982+
.then(function(result) {
11983+
if (result.success) {
11984+
var msg = result.data && result.data.auto_approved
11985+
? 'Te has unido a ' + escapeHtml(joinName)
11986+
: 'Solicitud enviada a ' + escapeHtml(joinName) + '. El administrador la revisara';
11987+
showNotification(msg, 'success');
11988+
// Remove from public list
11989+
window.publicGroupsData = (window.publicGroupsData || []).filter(function(g) { return g.id !== joinGid; });
11990+
fetchMyGroups();
11991+
} else {
11992+
showNotification(result.error || 'Error al solicitar ingreso', 'error');
11993+
joinBtn.disabled = false;
11994+
joinBtn.textContent = 'Solicitar unirse';
11995+
}
11996+
})
11997+
.catch(function() {
11998+
showNotification('Error de conexion', 'error');
11999+
joinBtn.disabled = false;
12000+
joinBtn.textContent = 'Solicitar unirse';
12001+
});
12002+
});
12003+
})();
12004+
break;
1190712005
case 'grp-reset-filters':
1190812006
if (typeof resetFilters === 'function') resetFilters();
1190912007
break;

0 commit comments

Comments
 (0)