mirror of
https://codeberg.org/likwid/likwid.git
synced 2026-02-10 05:23:09 +00:00
ux: improve proposals states
This commit is contained in:
parent
a946fc6b85
commit
5b5a11a96d
2 changed files with 111 additions and 6 deletions
|
|
@ -44,7 +44,7 @@ import { API_BASE as apiBase } from '../lib/api';
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="proposals-list" class="list">
|
<div id="proposals-list" class="list" aria-live="polite">
|
||||||
<div class="state-card ui-card"><p class="loading">Loading proposals...</p></div>
|
<div class="state-card ui-card"><p class="loading">Loading proposals...</p></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -78,6 +78,62 @@ import { API_BASE as apiBase } from '../lib/api';
|
||||||
container.innerHTML = html;
|
container.innerHTML = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderErrorState(message) {
|
||||||
|
const container = document.getElementById('proposals-list');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="state-card ui-card">
|
||||||
|
<p class="error">${message}</p>
|
||||||
|
<p class="hint">Check your connection and try again.</p>
|
||||||
|
<div class="state-actions">
|
||||||
|
<button type="button" class="ui-btn ui-btn-primary" id="retry-load">Retry</button>
|
||||||
|
<a class="ui-btn ui-btn-secondary" href="/communities">Browse communities</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById('retry-load')?.addEventListener('click', loadProposals);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderEmptyState(isFiltered) {
|
||||||
|
const container = document.getElementById('proposals-list');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="state-card ui-card">
|
||||||
|
<p class="empty">${isFiltered ? 'No proposals match your filters.' : 'No proposals found.'}</p>
|
||||||
|
<p class="hint">Try adjusting search, status, or sort.</p>
|
||||||
|
<div class="state-actions">
|
||||||
|
<button type="button" class="ui-btn ui-btn-secondary" id="reset-filters">Reset filters</button>
|
||||||
|
<a class="ui-btn ui-btn-primary" href="/communities">Go to communities</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById('reset-filters')?.addEventListener('click', () => {
|
||||||
|
const searchInput = document.getElementById('search-input');
|
||||||
|
const statusFilter = document.getElementById('status-filter');
|
||||||
|
const sortFilter = document.getElementById('sort-filter');
|
||||||
|
if (searchInput) searchInput.value = '';
|
||||||
|
if (statusFilter) statusFilter.value = '';
|
||||||
|
if (sortFilter) sortFilter.value = 'newest';
|
||||||
|
filterProposals();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasActiveFilters() {
|
||||||
|
const searchInput = document.getElementById('search-input');
|
||||||
|
const statusFilter = document.getElementById('status-filter');
|
||||||
|
const sortFilter = document.getElementById('sort-filter');
|
||||||
|
|
||||||
|
const query = searchInput?.value.toLowerCase().trim() || '';
|
||||||
|
const status = statusFilter?.value || '';
|
||||||
|
const sort = sortFilter?.value || 'newest';
|
||||||
|
|
||||||
|
return Boolean(query || status || sort !== 'newest');
|
||||||
|
}
|
||||||
|
|
||||||
async function loadProposals() {
|
async function loadProposals() {
|
||||||
const container = document.getElementById('proposals-list');
|
const container = document.getElementById('proposals-list');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
@ -88,7 +144,7 @@ import { API_BASE as apiBase } from '../lib/api';
|
||||||
allProposals = await res.json();
|
allProposals = await res.json();
|
||||||
renderProposals(allProposals);
|
renderProposals(allProposals);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
container.innerHTML = '<div class="error"><p>Failed to load proposals.</p></div>';
|
renderErrorState('Failed to load proposals.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,7 +153,7 @@ import { API_BASE as apiBase } from '../lib/api';
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
if (proposals.length === 0) {
|
if (proposals.length === 0) {
|
||||||
container.innerHTML = '<div class="state-card ui-card"><p class="empty">No proposals found.</p></div>';
|
renderEmptyState(hasActiveFilters());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,6 +227,14 @@ import { API_BASE as apiBase } from '../lib/api';
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.state-actions {
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.proposal-card {
|
.proposal-card {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 1.1rem;
|
padding: 1.1rem;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,24 @@ const proposalId = id ?? '';
|
||||||
let quadraticAllocations = {};
|
let quadraticAllocations = {};
|
||||||
let starRatings = {};
|
let starRatings = {};
|
||||||
|
|
||||||
|
function renderProposalErrorState(message) {
|
||||||
|
const container = document.getElementById('proposal-content');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="state-card ui-card">
|
||||||
|
<p class="error">${escapeHtml(message)}</p>
|
||||||
|
<p class="hint">It may have been deleted, or you may not have access.</p>
|
||||||
|
<div class="state-actions">
|
||||||
|
<button type="button" class="ui-btn ui-btn-primary" id="retry-proposal">Retry</button>
|
||||||
|
<a class="ui-btn ui-btn-secondary" href="/proposals">Back to proposals</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById('retry-proposal')?.addEventListener('click', loadProposal);
|
||||||
|
}
|
||||||
|
|
||||||
function escapeHtml(value) {
|
function escapeHtml(value) {
|
||||||
return String(value || '').replace(/[&<>"']/g, function(ch) {
|
return String(value || '').replace(/[&<>"']/g, function(ch) {
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
|
|
@ -407,7 +425,7 @@ const proposalId = id ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
container.innerHTML = '<div class="error">Proposal not found</div>';
|
renderProposalErrorState('Proposal not found.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -600,7 +618,12 @@ const proposalId = id ?? '';
|
||||||
const comments = await res.json();
|
const comments = await res.json();
|
||||||
|
|
||||||
if (comments.length === 0) {
|
if (comments.length === 0) {
|
||||||
container.innerHTML = '<p class="empty-comments">No comments yet. Be the first to share your thoughts!</p>';
|
container.innerHTML = `
|
||||||
|
<div class="state-card ui-card ui-card-soft">
|
||||||
|
<p class="empty">No comments yet.</p>
|
||||||
|
<p class="hint">Be the first to share your thoughts.</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -619,7 +642,17 @@ const proposalId = id ?? '';
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
container.innerHTML = '<p class="error-small">Failed to load comments</p>';
|
container.innerHTML = `
|
||||||
|
<div class="state-card ui-card ui-card-soft">
|
||||||
|
<p class="error">Failed to load comments.</p>
|
||||||
|
<p class="hint">Try again in a moment.</p>
|
||||||
|
<div class="state-actions">
|
||||||
|
<button type="button" class="ui-btn ui-btn-secondary" id="retry-comments">Retry</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById('retry-comments')?.addEventListener('click', loadComments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -659,6 +692,14 @@ const proposalId = id ?? '';
|
||||||
max-width: 880px;
|
max-width: 880px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.state-actions {
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.results-chart-host {
|
.results-chart-host {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue