feat: Add filtering and searching to governance proposals page

- Added filtering of proposals by status (Draft, Active, Approved, Rejected, Cancelled).
- Added searching of proposals by title and description.
- Improved UI to persist filter and search values.
- Added a "No proposals found" message for better UX.
This commit is contained in:
Mahmoud-Emad 2025-05-22 15:47:11 +03:00
parent 67b80f237d
commit 4659697ae2
2 changed files with 66 additions and 9 deletions

View File

@ -133,7 +133,11 @@ impl GovernanceController {
}
/// Handles the proposal list page route
pub async fn proposals(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
pub async fn proposals(
query: web::Query<ProposalQuery>,
tmpl: web::Data<Tera>,
session: Session
) -> Result<impl Responder> {
let mut ctx = tera::Context::new();
ctx.insert("active_page", "governance");
ctx.insert("active_tab", "proposals");
@ -144,15 +148,48 @@ impl GovernanceController {
}
// Get proposals from the database
let proposals = match crate::db::proposals::get_proposals() {
let mut proposals = match crate::db::proposals::get_proposals() {
Ok(props) => props,
Err(e) => {
ctx.insert("error", &format!("Failed to load proposals: {}", e));
vec![]
}
};
// Filter proposals by status if provided
if let Some(status_filter) = &query.status {
if !status_filter.is_empty() {
proposals = proposals
.into_iter()
.filter(|p| {
let proposal_status = format!("{:?}", p.status);
proposal_status == *status_filter
})
.collect();
}
}
// Filter by search term if provided (title or description)
if let Some(search_term) = &query.search {
if !search_term.is_empty() {
let search_term = search_term.to_lowercase();
proposals = proposals
.into_iter()
.filter(|p| {
p.title.to_lowercase().contains(&search_term) ||
p.description.to_lowercase().contains(&search_term)
})
.collect();
}
}
// Add the filtered proposals to the context
ctx.insert("proposals", &proposals);
// Add the filter values back to the context for form persistence
ctx.insert("status_filter", &query.status);
ctx.insert("search_filter", &query.search);
render_template(&tmpl, "governance/proposals.html", &ctx)
}
@ -597,6 +634,13 @@ pub struct VoteForm {
pub comment: Option<String>,
}
/// Query parameters for filtering proposals
#[derive(Debug, Deserialize)]
pub struct ProposalQuery {
pub status: Option<String>,
pub search: Option<String>,
}
/// Represents statistics for the governance dashboard
#[derive(Debug, Serialize)]
pub struct GovernanceStats {

View File

@ -58,18 +58,18 @@
<div class="col-md-4">
<label for="status" class="form-label">Status</label>
<select class="form-select" id="status" name="status">
<option value="">All Statuses</option>
<option value="Draft">Draft</option>
<option value="Active">Active</option>
<option value="Approved">Approved</option>
<option value="Rejected">Rejected</option>
<option value="Cancelled">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">
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>
@ -89,6 +89,7 @@
<a href="/governance/create" class="btn btn-sm btn-primary">Create New Proposal</a>
</div>
<div class="card-body">
{% if proposals and proposals|length > 0 %}
<div class="table-responsive">
<table class="table table-hover">
<thead>
@ -129,6 +130,18 @@
{% 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>
{% endif %}
<a href="/governance/create" class="btn btn-primary mt-3">Create New Proposal</a>
</div>
{% endif %}
</div>
</div>
</div>