feat: Enhance governance dashboard with activity tracking

- Add governance activity tracker to record user actions.
- Display recent activities on the governance dashboard.
- Add a dedicated page to view all governance activities.
- Improve header information and styling across governance pages.
- Track proposal creation and voting activities.
This commit is contained in:
Mahmoud-Emad
2025-05-25 16:02:34 +03:00
parent d12a082ca1
commit 70ca9f1605
14 changed files with 1050 additions and 458 deletions

View File

@@ -0,0 +1,18 @@
<!-- Governance Page Header -->
<div class="row mb-4">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center">
<div>
<h1 class="h3 mb-1">{{ page_title }}</h1>
<p class="text-muted mb-0">{{ page_description }}</p>
</div>
{% if show_create_button %}
<div>
<a href="/governance/create" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Create Proposal
</a>
</div>
{% endif %}
</div>
</div>
</div>

View File

@@ -0,0 +1,32 @@
<!-- Governance Navigation Tabs -->
<div class="row mb-4">
<div class="col-12">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link {% if active_tab == 'dashboard' %}active{% endif %}" href="/governance">
<i class="bi bi-house"></i> Dashboard
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if active_tab == 'proposals' %}active{% endif %}" href="/governance/proposals">
<i class="bi bi-file-text"></i> All Proposals
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if active_tab == 'create' %}active{% endif %}" href="/governance/create">
<i class="bi bi-plus-circle"></i> Create Proposal
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if active_tab == 'my-votes' %}active{% endif %}" href="/governance/my-votes">
<i class="bi bi-check-circle"></i> My Votes
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if active_tab == 'activities' %}active{% endif %}" href="/governance/activities">
<i class="bi bi-activity"></i> All Activities
</a>
</li>
</ul>
</div>
</div>

View File

@@ -0,0 +1,118 @@
{% extends "base.html" %}
{% block title %}All Governance Activities{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<!-- Header -->
{% include "governance/_header.html" %}
<!-- Navigation Tabs -->
{% include "governance/_tabs.html" %}
<!-- Activities List -->
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="bi bi-activity"></i> Governance Activity History
</h5>
</div>
<div class="card-body">
{% if activities %}
<div class="row">
<div class="col-12">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th width="50">Type</th>
<th>User</th>
<th>Action</th>
<th>Proposal</th>
<th width="150">Date</th>
</tr>
</thead>
<tbody>
{% for activity in activities %}
<tr>
<td>
<i class="{{ activity.icon }}"></i>
</td>
<td>
<strong>{{ activity.user }}</strong>
</td>
<td>
{{ activity.action }}
</td>
<td>
<a href="/governance/proposals/{{ activity.proposal_id }}"
class="text-decoration-none">
{{ activity.proposal_title }}
</a>
</td>
<td>
<small class="text-muted">
{{ activity.timestamp | date(format="%Y-%m-%d %H:%M") }}
</small>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% else %}
<div class="text-center py-5">
<i class="bi bi-activity display-1 text-muted"></i>
<h4 class="mt-3">No Activities Yet</h4>
<p class="text-muted">
Governance activities will appear here as users create proposals and cast votes.
</p>
<a href="/governance/create" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Create First Proposal
</a>
</div>
{% endif %}
</div>
</div>
<!-- Activity Statistics -->
{% if activities %}
<div class="row mt-4">
<div class="col-md-4">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">{{ activities | length }}</h5>
<p class="card-text text-muted">Total Activities</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">
<i class="bi bi-activity text-primary"></i>
</h5>
<p class="card-text text-muted">Activity Timeline</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">
<i class="bi bi-people text-success"></i>
</h5>
<p class="card-text text-muted">Community Engagement</p>
</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -4,25 +4,11 @@
{% block content %}
<div class="container-fluid">
<!-- Header -->
{% include "governance/_header.html" %}
<!-- Navigation Tabs -->
<div class="row mb-4">
<div class="col-12">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link" href="/governance">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/governance/proposals">All Proposals</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/governance/my-votes">My Votes</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/governance/create">Create Proposal</a>
</li>
</ul>
</div>
</div>
{% include "governance/_tabs.html" %}
<!-- Info Alert -->
<div class="row">

View File

@@ -3,25 +3,11 @@
{% block title %}Governance Dashboard{% endblock %}
{% block content %}
<!-- Header -->
{% include "governance/_header.html" %}
<!-- Navigation Tabs -->
<div class="row mb-3">
<div class="col-12">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link active" href="/governance">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/governance/proposals">All Proposals</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/governance/my-votes">My Votes</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/governance/create">Create Proposal</a>
</li>
</ul>
</div>
</div>
{% include "governance/_tabs.html" %}
<!-- Info Alert -->
<div class="row mb-2">
@@ -159,7 +145,7 @@
</div>
</div>
<div class="card-footer text-center">
<a href="/governance/proposals" class="btn btn-sm btn-outline-info">View All Activity</a>
<a href="/governance/activities" class="btn btn-sm btn-outline-info">View All Activities</a>
</div>
</div>
</div>

View File

@@ -3,25 +3,11 @@
{% block title %}My Votes - Governance Dashboard{% endblock %}
{% block content %}
<!-- Header -->
{% include "governance/_header.html" %}
<!-- Navigation Tabs -->
<div class="row mb-4">
<div class="col-12">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link" href="/governance">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/governance/proposals">All Proposals</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/governance/my-votes">My Votes</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/governance/create">Create Proposal</a>
</li>
</ul>
</div>
</div>
{% include "governance/_tabs.html" %}
<!-- Info Alert -->
<div class="row">

View File

@@ -35,6 +35,12 @@
{% block content %}
<div class="container-fluid">
<!-- Header -->
{% include "governance/_header.html" %}
<!-- Navigation Tabs -->
{% include "governance/_tabs.html" %}
<div class="row mb-4">
<div class="col-12">
<nav aria-label="breadcrumb">
@@ -194,7 +200,8 @@
{% if proposal.status == "Active" and user and user.id %}
<div class="mt-auto">
<h6 class="border-bottom pb-2 mb-3"><i class="bi bi-check2-square me-2"></i>Cast Your Vote</h6>
<form action="/governance/proposals/{{ proposal.base_data.id }}/vote" method="post">
<form action="/governance/proposals/{{ proposal.base_data.id }}/vote" method="post"
id="voteForm">
<div class="mb-3">
<div class="d-flex gap-2 mb-2">
<div class="form-check">
@@ -243,26 +250,8 @@
<div class="row mt-4">
<div class="col-12">
<div class="card shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center flex-wrap">
<h5 class="mb-0 mb-md-0"><i class="bi bi-list-check me-2"></i>Votes</h5>
<div class="d-flex align-items-center">
<div class="input-group input-group-sm me-2 d-none d-md-flex" style="width: 200px;">
<span class="input-group-text bg-white">
<i class="bi bi-search"></i>
</span>
<input type="text" class="form-control border-start-0" id="voteSearch"
placeholder="Search votes...">
</div>
<div class="btn-group" role="group" aria-label="Filter votes">
<button type="button" class="btn btn-sm btn-outline-primary active"
data-filter="all">All</button>
<button type="button" class="btn btn-sm btn-outline-success"
data-filter="yes">Yes</button>
<button type="button" class="btn btn-sm btn-outline-danger" data-filter="no">No</button>
<button type="button" class="btn btn-sm btn-outline-secondary"
data-filter="abstain">Abstain</button>
</div>
</div>
<div class="card-header bg-light">
<h5 class="mb-0"><i class="bi bi-list-check me-2"></i>Votes</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
@@ -372,275 +361,255 @@
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function () {
// Remove query parameters from URL without refreshing the page
if (window.location.search.includes('vote_success=true')) {
const newUrl = window.location.pathname;
window.history.replaceState({}, document.title, newUrl);
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function () {
// Remove query parameters from URL without refreshing the page
if (window.location.search.includes('vote_success=true')) {
const newUrl = window.location.pathname;
window.history.replaceState({}, document.title, newUrl);
// Auto-hide the success alert after 5 seconds
const successAlert = document.querySelector('.alert-success');
if (successAlert) {
// Auto-hide the success alert after 5 seconds
const successAlert = document.querySelector('.alert-success');
if (successAlert) {
setTimeout(function () {
successAlert.classList.remove('show');
setTimeout(function () {
successAlert.classList.remove('show');
setTimeout(function () {
successAlert.remove();
}, 500);
}, 5000);
}
successAlert.remove();
}, 500);
}, 5000);
}
}
// Pagination functionality
const rowsPerPageSelect = document.getElementById('rowsPerPage');
const paginationControls = document.getElementById('paginationControls');
const votesTableBody = document.getElementById('votesTableBody');
const startRowElement = document.getElementById('startRow');
const endRowElement = document.getElementById('endRow');
const totalRowsElement = document.getElementById('totalRows');
const prevPageBtn = document.getElementById('prevPage');
const nextPageBtn = document.getElementById('nextPage');
let currentPage = 1;
let rowsPerPage = rowsPerPageSelect ? parseInt(rowsPerPageSelect.value) : 10;
// Function to update pagination display
function updatePagination() {
if (!paginationControls) return;
// Get all rows that match the current filter
const currentFilter = document.querySelector('[data-filter].active');
const filterType = currentFilter ? currentFilter.getAttribute('data-filter') : 'all';
// Get rows that match the current filter and search term
let filteredRows = Array.from(voteRows);
if (filterType !== 'all') {
filteredRows = filteredRows.filter(row => row.getAttribute('data-vote-type') === filterType);
}
// Vote filtering using data-filter attributes
const filterButtons = document.querySelectorAll('[data-filter]');
const voteRows = document.querySelectorAll('.vote-row');
const searchInput = document.getElementById('voteSearch');
// Filter votes by type
filterButtons.forEach(button => {
button.addEventListener('click', function () {
// Update active button
filterButtons.forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
// Reset to first page and update pagination
currentPage = 1;
updatePagination();
});
});
// Search functionality
if (searchInput) {
searchInput.addEventListener('input', function () {
const searchTerm = this.value.toLowerCase();
voteRows.forEach(row => {
const voterName = row.querySelector('td:first-child').textContent.toLowerCase();
const comment = row.querySelector('td:nth-child(3)').textContent.toLowerCase();
if (voterName.includes(searchTerm) || comment.includes(searchTerm)) {
row.style.display = '';
} else {
row.style.display = 'none';
}
});
// Reset pagination after search
currentPage = 1;
updatePagination();
// Apply search filter if there's a search term
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
if (searchTerm) {
filteredRows = filteredRows.filter(row => {
const voterName = row.querySelector('td:first-child').textContent.toLowerCase();
const comment = row.querySelector('td:nth-child(3)').textContent.toLowerCase();
return voterName.includes(searchTerm) || comment.includes(searchTerm);
});
}
// Pagination functionality
const rowsPerPageSelect = document.getElementById('rowsPerPage');
const paginationControls = document.getElementById('paginationControls');
const votesTableBody = document.getElementById('votesTableBody');
const startRowElement = document.getElementById('startRow');
const endRowElement = document.getElementById('endRow');
const totalRowsElement = document.getElementById('totalRows');
const prevPageBtn = document.getElementById('prevPage');
const nextPageBtn = document.getElementById('nextPage');
const totalRows = filteredRows.length;
let currentPage = 1;
let rowsPerPage = rowsPerPageSelect ? parseInt(rowsPerPageSelect.value) : 10;
// Calculate total pages
const totalPages = Math.max(1, Math.ceil(totalRows / rowsPerPage));
// Function to update pagination display
function updatePagination() {
if (!paginationControls) return;
// Get all rows that match the current filter
const currentFilter = document.querySelector('[data-filter].active');
const filterType = currentFilter ? currentFilter.getAttribute('data-filter') : 'all';
// Get rows that match the current filter and search term
let filteredRows = Array.from(voteRows);
if (filterType !== 'all') {
filteredRows = filteredRows.filter(row => row.getAttribute('data-vote-type') === filterType);
}
// Apply search filter if there's a search term
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
if (searchTerm) {
filteredRows = filteredRows.filter(row => {
const voterName = row.querySelector('td:first-child').textContent.toLowerCase();
const comment = row.querySelector('td:nth-child(3)').textContent.toLowerCase();
return voterName.includes(searchTerm) || comment.includes(searchTerm);
});
}
const totalRows = filteredRows.length;
// Calculate total pages
const totalPages = Math.max(1, Math.ceil(totalRows / rowsPerPage));
// Ensure current page is valid
if (currentPage > totalPages) {
currentPage = totalPages;
}
// Update pagination controls
if (paginationControls) {
// Clear existing page links (except prev/next)
const pageLinks = paginationControls.querySelectorAll('li:not(#prevPage):not(#nextPage)');
pageLinks.forEach(link => link.remove());
// Add new page links
const maxVisiblePages = 5;
let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
// Adjust if we're near the end
if (endPage - startPage + 1 < maxVisiblePages && startPage > 1) {
startPage = Math.max(1, endPage - maxVisiblePages + 1);
}
// Insert page links before the next button
const nextPageElement = document.getElementById('nextPage');
for (let i = startPage; i <= endPage; i++) {
const li = document.createElement('li');
li.className = `page-item ${i === currentPage ? 'active' : ''}`;
const a = document.createElement('a');
a.className = 'page-link';
a.href = '#';
a.textContent = i;
a.addEventListener('click', function (e) {
e.preventDefault();
currentPage = i;
updatePagination();
});
li.appendChild(a);
paginationControls.insertBefore(li, nextPageElement);
}
// Update prev/next buttons
prevPageBtn.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
nextPageBtn.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
}
// Show current page
showCurrentPage();
// Ensure current page is valid
if (currentPage > totalPages) {
currentPage = totalPages;
}
// Function to show current page
function showCurrentPage() {
if (!votesTableBody) return;
// Get all rows that match the current filter
const currentFilter = document.querySelector('[data-filter].active');
const filterType = currentFilter ? currentFilter.getAttribute('data-filter') : 'all';
// Get rows that match the current filter and search term
let filteredRows = Array.from(voteRows);
if (filterType !== 'all') {
filteredRows = filteredRows.filter(row => row.getAttribute('data-vote-type') === filterType);
}
// Apply search filter if there's a search term
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
if (searchTerm) {
filteredRows = filteredRows.filter(row => {
const voterName = row.querySelector('td:first-child').textContent.toLowerCase();
const comment = row.querySelector('td:nth-child(3)').textContent.toLowerCase();
return voterName.includes(searchTerm) || comment.includes(searchTerm);
});
}
// Hide all rows first
voteRows.forEach(row => row.style.display = 'none');
// Calculate pagination
const totalRows = filteredRows.length;
const totalPages = Math.max(1, Math.ceil(totalRows / rowsPerPage));
// Ensure current page is valid
if (currentPage > totalPages) {
currentPage = totalPages;
}
// Show only rows for current page
const start = (currentPage - 1) * rowsPerPage;
const end = start + rowsPerPage;
filteredRows.slice(start, end).forEach(row => row.style.display = '');
// Update pagination info
if (startRowElement && endRowElement && totalRowsElement) {
startRowElement.textContent = totalRows > 0 ? start + 1 : 0;
endRowElement.textContent = Math.min(end, totalRows);
totalRowsElement.textContent = totalRows;
}
}
// Event listeners for pagination
if (prevPageBtn) {
prevPageBtn.addEventListener('click', function (e) {
e.preventDefault();
if (currentPage > 1) {
currentPage--;
updatePagination();
}
});
}
if (nextPageBtn) {
nextPageBtn.addEventListener('click', function (e) {
e.preventDefault();
// Get all rows that match the current filter
const currentFilter = document.querySelector('[data-filter].active');
const filterType = currentFilter ? currentFilter.getAttribute('data-filter') : 'all';
// Get rows that match the current filter and search term
let filteredRows = Array.from(voteRows);
if (filterType !== 'all') {
filteredRows = filteredRows.filter(row => row.getAttribute('data-vote-type') === filterType);
}
// Apply search filter if there's a search term
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
if (searchTerm) {
filteredRows = filteredRows.filter(row => {
const voterName = row.querySelector('td:first-child').textContent.toLowerCase();
const comment = row.querySelector('td:nth-child(3)').textContent.toLowerCase();
return voterName.includes(searchTerm) || comment.includes(searchTerm);
});
}
const totalRows = filteredRows.length;
const totalPages = Math.max(1, Math.ceil(totalRows / rowsPerPage));
if (currentPage < totalPages) {
currentPage++;
updatePagination();
}
});
}
if (rowsPerPageSelect) {
rowsPerPageSelect.addEventListener('change', function () {
rowsPerPage = parseInt(this.value);
currentPage = 1; // Reset to first page
updatePagination();
});
}
// Initialize pagination
// Update pagination controls
if (paginationControls) {
updatePagination();
// Clear existing page links (except prev/next)
const pageLinks = paginationControls.querySelectorAll('li:not(#prevPage):not(#nextPage)');
pageLinks.forEach(link => link.remove());
// Add new page links
const maxVisiblePages = 5;
let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
// Adjust if we're near the end
if (endPage - startPage + 1 < maxVisiblePages && startPage > 1) {
startPage = Math.max(1, endPage - maxVisiblePages + 1);
}
// Insert page links before the next button
const nextPageElement = document.getElementById('nextPage');
for (let i = startPage; i <= endPage; i++) {
const li = document.createElement('li');
li.className = `page-item ${i === currentPage ? 'active' : ''}`;
const a = document.createElement('a');
a.className = 'page-link';
a.href = '#';
a.textContent = i;
a.addEventListener('click', function (e) {
e.preventDefault();
currentPage = i;
updatePagination();
});
li.appendChild(a);
paginationControls.insertBefore(li, nextPageElement);
}
// Update prev/next buttons
prevPageBtn.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
nextPageBtn.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
}
// Initialize tooltips for all elements with title attributes
const tooltipElements = document.querySelectorAll('[title]');
if (tooltipElements.length > 0) {
[].slice.call(tooltipElements).map(function (el) {
return new bootstrap.Tooltip(el);
// Show current page
showCurrentPage();
}
// Function to show current page
function showCurrentPage() {
if (!votesTableBody) return;
// Get all rows that match the current filter
const currentFilter = document.querySelector('[data-filter].active');
const filterType = currentFilter ? currentFilter.getAttribute('data-filter') : 'all';
// Get rows that match the current filter and search term
let filteredRows = Array.from(voteRows);
if (filterType !== 'all') {
filteredRows = filteredRows.filter(row => row.getAttribute('data-vote-type') === filterType);
}
// Apply search filter if there's a search term
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
if (searchTerm) {
filteredRows = filteredRows.filter(row => {
const voterName = row.querySelector('td:first-child').textContent.toLowerCase();
const comment = row.querySelector('td:nth-child(3)').textContent.toLowerCase();
return voterName.includes(searchTerm) || comment.includes(searchTerm);
});
}
});
</script>
{% endblock scripts %}
{% endblock content %}
// Hide all rows first
voteRows.forEach(row => row.style.display = 'none');
// Calculate pagination
const totalRows = filteredRows.length;
const totalPages = Math.max(1, Math.ceil(totalRows / rowsPerPage));
// Ensure current page is valid
if (currentPage > totalPages) {
currentPage = totalPages;
}
// Show only rows for current page
const start = (currentPage - 1) * rowsPerPage;
const end = start + rowsPerPage;
filteredRows.slice(start, end).forEach(row => row.style.display = '');
// Update pagination info
if (startRowElement && endRowElement && totalRowsElement) {
startRowElement.textContent = totalRows > 0 ? start + 1 : 0;
endRowElement.textContent = Math.min(end, totalRows);
totalRowsElement.textContent = totalRows;
}
}
// Event listeners for pagination
if (prevPageBtn) {
prevPageBtn.addEventListener('click', function (e) {
e.preventDefault();
if (currentPage > 1) {
currentPage--;
updatePagination();
}
});
}
if (nextPageBtn) {
nextPageBtn.addEventListener('click', function (e) {
e.preventDefault();
// Get all rows that match the current filter
const currentFilter = document.querySelector('[data-filter].active');
const filterType = currentFilter ? currentFilter.getAttribute('data-filter') : 'all';
// Get rows that match the current filter and search term
let filteredRows = Array.from(voteRows);
if (filterType !== 'all') {
filteredRows = filteredRows.filter(row => row.getAttribute('data-vote-type') === filterType);
}
// Apply search filter if there's a search term
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
if (searchTerm) {
filteredRows = filteredRows.filter(row => {
const voterName = row.querySelector('td:first-child').textContent.toLowerCase();
const comment = row.querySelector('td:nth-child(3)').textContent.toLowerCase();
return voterName.includes(searchTerm) || comment.includes(searchTerm);
});
}
const totalRows = filteredRows.length;
const totalPages = Math.max(1, Math.ceil(totalRows / rowsPerPage));
if (currentPage < totalPages) {
currentPage++;
updatePagination();
}
});
}
if (rowsPerPageSelect) {
rowsPerPageSelect.addEventListener('change', function () {
rowsPerPage = parseInt(this.value);
currentPage = 1; // Reset to first page
updatePagination();
});
}
// Initialize pagination (but don't interfere with filtering)
if (paginationControls) {
// Only initialize pagination if there are many votes
// The filtering will handle showing/hiding rows
console.log('Pagination controls available but not interfering with filtering');
}
// Initialize tooltips for all elements with title attributes
const tooltipElements = document.querySelectorAll('[title]');
if (tooltipElements.length > 0) {
[].slice.call(tooltipElements).map(function (el) {
return new bootstrap.Tooltip(el);
});
}
// Add debugging for vote form
const voteForm = document.getElementById('voteForm');
if (voteForm) {
console.log('Vote form found:', voteForm);
voteForm.addEventListener('submit', function (e) {
console.log('Vote form submitted');
const formData = new FormData(voteForm);
console.log('Form data:', Object.fromEntries(formData));
});
} else {
console.log('Vote form not found');
}
// Debug logging
console.log('Filter buttons found:', filterButtons.length);
console.log('Vote rows found:', voteRows.length);
console.log('Search input found:', searchInput ? 'Yes' : 'No');
});
</script>
{% endblock %}

View File

@@ -3,6 +3,12 @@
{% block title %}Proposals - Governance Dashboard{% endblock %}
{% block content %}
<!-- Header -->
{% include "governance/_header.html" %}
<!-- Navigation Tabs -->
{% include "governance/_tabs.html" %}
<!-- Success message if present -->
{% if success %}
<div class="row mb-4">
@@ -15,26 +21,6 @@
</div>
{% endif %}
<!-- Navigation Tabs -->
<div class="row mb-3">
<div class="col-12">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link" href="/governance">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/governance/proposals">All Proposals</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/governance/my-votes">My Votes</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/governance/create">Create Proposal</a>
</li>
</ul>
</div>
</div>
<div class="col-12">
<div class="alert alert-info alert-dismissible fade show">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
@@ -58,18 +44,23 @@
<div class="col-md-4">
<label for="status" class="form-label">Status</label>
<select class="form-select" id="status" name="status">
<option value="" {% if not status_filter or status_filter == "" %}selected{% endif %}>All Statuses</option>
<option value="Draft" {% if status_filter == "Draft" %}selected{% endif %}>Draft</option>
<option value="Active" {% if status_filter == "Active" %}selected{% endif %}>Active</option>
<option value="Approved" {% if status_filter == "Approved" %}selected{% endif %}>Approved</option>
<option value="Rejected" {% if status_filter == "Rejected" %}selected{% endif %}>Rejected</option>
<option value="Cancelled" {% if status_filter == "Cancelled" %}selected{% endif %}>Cancelled</option>
<option value="" {% if not status_filter or status_filter=="" %}selected{% endif %}>All
Statuses</option>
<option value="Draft" {% if status_filter=="Draft" %}selected{% endif %}>Draft</option>
<option value="Active" {% if status_filter=="Active" %}selected{% endif %}>Active</option>
<option value="Approved" {% if status_filter=="Approved" %}selected{% endif %}>Approved
</option>
<option value="Rejected" {% if status_filter=="Rejected" %}selected{% endif %}>Rejected
</option>
<option value="Cancelled" {% if status_filter=="Cancelled" %}selected{% endif %}>Cancelled
</option>
</select>
</div>
<div class="col-md-6">
<label for="search" class="form-label">Search</label>
<input type="text" class="form-control" id="search" name="search"
placeholder="Search by title or description" value="{% if search_filter %}{{ search_filter }}{% endif %}">
placeholder="Search by title or description"
value="{% if search_filter %}{{ search_filter }}{% endif %}">
</div>
<div class="col-md-2 d-flex align-items-end">
<button type="submit" class="btn btn-primary w-100">Filter</button>
@@ -130,21 +121,22 @@
{% endfor %}
</tbody>
</table>
{% else %}
<div class="alert alert-info text-center py-5">
<i class="bi bi-info-circle fs-1 mb-3"></i>
<h5>No proposals found</h5>
{% if status_filter or search_filter %}
<p>No proposals match your current filter criteria. Try adjusting your filters or <a href="/governance/proposals" class="alert-link">view all proposals</a>.</p>
{% else %}
<p>There are no proposals in the system yet.</p>
<div class="alert alert-info text-center py-5">
<i class="bi bi-info-circle fs-1 mb-3"></i>
<h5>No proposals found</h5>
{% if status_filter or search_filter %}
<p>No proposals match your current filter criteria. Try adjusting your filters or <a
href="/governance/proposals" class="alert-link">view all proposals</a>.</p>
{% else %}
<p>There are no proposals in the system yet.</p>
{% endif %}
<a href="/governance/create" class="btn btn-primary mt-3">Create New Proposal</a>
</div>
{% endif %}
<a href="/governance/create" class="btn btn-primary mt-3">Create New Proposal</a>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}