mirror of
https://codeberg.org/likwid/likwid.git
synced 2026-02-09 21:13:09 +00:00
237 lines
5.8 KiB
Text
237 lines
5.8 KiB
Text
|
|
---
|
||
|
|
export const prerender = false;
|
||
|
|
import Layout from '../../../../layouts/Layout.astro';
|
||
|
|
import { API_BASE as apiBase } from '../../../../lib/api';
|
||
|
|
const { slug } = Astro.params;
|
||
|
|
---
|
||
|
|
|
||
|
|
<Layout title="Proposals">
|
||
|
|
<section class="proposals-page">
|
||
|
|
<div class="header-row">
|
||
|
|
<div>
|
||
|
|
<h1>Proposals</h1>
|
||
|
|
<p class="subtitle">Proposals and decisions</p>
|
||
|
|
</div>
|
||
|
|
<a href={`/communities/${slug}/proposals/new`} class="btn-create" id="create-btn" style="display: none;">+ New Proposal</a>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="filters">
|
||
|
|
<input type="text" id="search-input" placeholder="Search proposals..." />
|
||
|
|
<select id="status-filter">
|
||
|
|
<option value="">All Statuses</option>
|
||
|
|
<option value="draft">Draft</option>
|
||
|
|
<option value="discussion">Discussion</option>
|
||
|
|
<option value="voting">Voting</option>
|
||
|
|
<option value="closed">Closed</option>
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div id="proposals-list" class="list">
|
||
|
|
<p class="loading">Loading proposals...</p>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
</Layout>
|
||
|
|
|
||
|
|
<script define:vars={{ slug, apiBase }}>
|
||
|
|
let allProposals = [];
|
||
|
|
|
||
|
|
async function loadProposals() {
|
||
|
|
const container = document.getElementById('proposals-list');
|
||
|
|
if (!container) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const commRes = await fetch(`${apiBase}/api/communities`);
|
||
|
|
const communities = await commRes.json();
|
||
|
|
const community = communities.find(c => c.slug === slug);
|
||
|
|
|
||
|
|
if (!community) {
|
||
|
|
container.innerHTML = '<div class="error">Community not found</div>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const res = await fetch(`${apiBase}/api/communities/${community.id}/proposals`);
|
||
|
|
allProposals = await res.json();
|
||
|
|
renderProposals(allProposals);
|
||
|
|
} catch (error) {
|
||
|
|
container.innerHTML = '<div class="error"><p>Failed to load proposals.</p></div>';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderProposals(proposals) {
|
||
|
|
const container = document.getElementById('proposals-list');
|
||
|
|
if (!container) return;
|
||
|
|
|
||
|
|
if (proposals.length === 0) {
|
||
|
|
container.innerHTML = '<div class="empty"><p>No proposals found.</p></div>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
container.innerHTML = proposals.map(p => `
|
||
|
|
<a href="/proposals/${p.id}" class="proposal-card">
|
||
|
|
<div class="proposal-header">
|
||
|
|
<h3>${p.title}</h3>
|
||
|
|
<span class="status status-${p.status}">${p.status}</span>
|
||
|
|
</div>
|
||
|
|
<p class="description">${p.description.substring(0, 150)}${p.description.length > 150 ? '...' : ''}</p>
|
||
|
|
</a>
|
||
|
|
`).join('');
|
||
|
|
}
|
||
|
|
|
||
|
|
function filterProposals() {
|
||
|
|
const searchInput = document.getElementById('search-input');
|
||
|
|
const statusFilter = document.getElementById('status-filter');
|
||
|
|
|
||
|
|
const query = searchInput?.value.toLowerCase().trim() || '';
|
||
|
|
const status = statusFilter?.value || '';
|
||
|
|
|
||
|
|
let filtered = allProposals;
|
||
|
|
|
||
|
|
if (query) {
|
||
|
|
filtered = filtered.filter(p =>
|
||
|
|
p.title.toLowerCase().includes(query) ||
|
||
|
|
p.description.toLowerCase().includes(query)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (status) {
|
||
|
|
filtered = filtered.filter(p => p.status === status);
|
||
|
|
}
|
||
|
|
|
||
|
|
renderProposals(filtered);
|
||
|
|
}
|
||
|
|
|
||
|
|
loadProposals();
|
||
|
|
|
||
|
|
document.getElementById('search-input')?.addEventListener('input', filterProposals);
|
||
|
|
document.getElementById('status-filter')?.addEventListener('change', filterProposals);
|
||
|
|
|
||
|
|
const token = localStorage.getItem('token');
|
||
|
|
const createBtn = document.getElementById('create-btn');
|
||
|
|
if (token && createBtn) {
|
||
|
|
createBtn.style.display = 'block';
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style>
|
||
|
|
.proposals-page {
|
||
|
|
padding: 2rem 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.header-row {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: flex-start;
|
||
|
|
margin-bottom: 2rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
h1 {
|
||
|
|
font-size: 2.5rem;
|
||
|
|
margin-bottom: 0.5rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.subtitle {
|
||
|
|
color: var(--color-text-muted);
|
||
|
|
}
|
||
|
|
|
||
|
|
.btn-create {
|
||
|
|
background: var(--color-primary);
|
||
|
|
color: var(--color-on-primary);
|
||
|
|
padding: 0.75rem 1.5rem;
|
||
|
|
border-radius: 8px;
|
||
|
|
font-weight: 600;
|
||
|
|
}
|
||
|
|
|
||
|
|
.btn-create:hover {
|
||
|
|
background: var(--color-primary-hover);
|
||
|
|
color: var(--color-on-primary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.filters {
|
||
|
|
display: flex;
|
||
|
|
gap: 1rem;
|
||
|
|
margin-bottom: 1.5rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.filters input, .filters select {
|
||
|
|
padding: 0.75rem 1rem;
|
||
|
|
border: 1px solid var(--color-border);
|
||
|
|
border-radius: 8px;
|
||
|
|
background: var(--color-surface);
|
||
|
|
color: var(--color-text);
|
||
|
|
font-size: 1rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.filters input {
|
||
|
|
flex: 1;
|
||
|
|
max-width: 300px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.filters select {
|
||
|
|
min-width: 150px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.filters input:focus, .filters select:focus {
|
||
|
|
outline: none;
|
||
|
|
border-color: var(--color-primary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.list {
|
||
|
|
display: grid;
|
||
|
|
gap: 1rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.proposal-card {
|
||
|
|
display: block;
|
||
|
|
background: var(--color-surface);
|
||
|
|
border: 1px solid var(--color-border);
|
||
|
|
border-radius: 12px;
|
||
|
|
padding: 1.5rem;
|
||
|
|
color: var(--color-text);
|
||
|
|
transition: border-color 0.2s;
|
||
|
|
}
|
||
|
|
|
||
|
|
.proposal-card:hover {
|
||
|
|
border-color: var(--color-primary);
|
||
|
|
}
|
||
|
|
|
||
|
|
.proposal-header {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: center;
|
||
|
|
margin-bottom: 0.5rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.proposal-card h3 {
|
||
|
|
font-size: 1.25rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.status {
|
||
|
|
font-size: 0.75rem;
|
||
|
|
padding: 0.25rem 0.75rem;
|
||
|
|
border-radius: 999px;
|
||
|
|
text-transform: uppercase;
|
||
|
|
font-weight: 600;
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-draft { background: var(--color-neutral-muted); color: var(--color-on-primary); }
|
||
|
|
.status-discussion { background: var(--color-info); color: var(--color-on-primary); }
|
||
|
|
.status-voting { background: var(--color-success); color: var(--color-on-primary); }
|
||
|
|
.status-closed { background: var(--color-neutral); color: var(--color-on-primary); }
|
||
|
|
|
||
|
|
.description {
|
||
|
|
color: var(--color-text-muted);
|
||
|
|
font-size: 0.875rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.loading, .empty, .error {
|
||
|
|
text-align: center;
|
||
|
|
padding: 3rem;
|
||
|
|
color: var(--color-text-muted);
|
||
|
|
}
|
||
|
|
|
||
|
|
.hint {
|
||
|
|
font-size: 0.875rem;
|
||
|
|
margin-top: 0.5rem;
|
||
|
|
}
|
||
|
|
</style>
|