feat: Implement Proposals page:
- Added the create new proposal functionality - Added the list all proposals functionnality
This commit is contained in:
parent
60198dc2d4
commit
4a2f1c7282
@ -1,9 +1,10 @@
|
||||
use crate::db::proposals;
|
||||
use crate::models::governance::{Proposal, ProposalStatus, Vote, VoteType, VotingResults};
|
||||
use crate::models::governance::{Vote, VoteType, VotingResults};
|
||||
use crate::utils::render_template;
|
||||
use actix_session::Session;
|
||||
use actix_web::{HttpResponse, Responder, Result, web};
|
||||
use chrono::{Duration, Utc};
|
||||
use heromodels::models::governance::{Proposal, ProposalStatus};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use tera::Tera;
|
||||
@ -48,18 +49,24 @@ impl GovernanceController {
|
||||
let user = Self::get_user_from_session(&session).unwrap();
|
||||
ctx.insert("user", &user);
|
||||
|
||||
// Get mock proposals for the dashboard
|
||||
let proposals = Self::get_mock_proposals();
|
||||
// Get proposals from the database
|
||||
let proposals = match crate::db::proposals::get_proposals() {
|
||||
Ok(props) => props,
|
||||
Err(e) => {
|
||||
ctx.insert("error", &format!("Failed to load proposals: {}", e));
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
|
||||
// Filter for active proposals only
|
||||
let active_proposals: Vec<Proposal> = proposals
|
||||
let active_proposals: Vec<heromodels::models::Proposal> = proposals
|
||||
.into_iter()
|
||||
.filter(|p| p.status == ProposalStatus::Active)
|
||||
.filter(|p| p.status == heromodels::models::ProposalStatus::Active)
|
||||
.collect();
|
||||
|
||||
// Sort active proposals by voting end date (ascending)
|
||||
let mut sorted_active_proposals = active_proposals.clone();
|
||||
sorted_active_proposals.sort_by(|a, b| a.voting_ends_at.cmp(&b.voting_ends_at));
|
||||
sorted_active_proposals.sort_by(|a, b| a.vote_start_date.cmp(&b.vote_end_date));
|
||||
|
||||
ctx.insert("proposals", &sorted_active_proposals);
|
||||
|
||||
@ -90,8 +97,14 @@ impl GovernanceController {
|
||||
ctx.insert("user", &user);
|
||||
}
|
||||
|
||||
// Get mock proposals
|
||||
let proposals = Self::get_mock_proposals();
|
||||
// Get proposals from the database
|
||||
let proposals = match crate::db::proposals::get_proposals() {
|
||||
Ok(props) => props,
|
||||
Err(e) => {
|
||||
ctx.insert("error", &format!("Failed to load proposals: {}", e));
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
ctx.insert("proposals", &proposals);
|
||||
|
||||
render_template(&tmpl, "governance/proposals.html", &ctx)
|
||||
@ -199,10 +212,24 @@ impl GovernanceController {
|
||||
.unwrap_or(1)
|
||||
.to_string();
|
||||
|
||||
let user_name = user
|
||||
.get("username")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("Test User")
|
||||
.to_string();
|
||||
|
||||
let is_draft = _form.draft.is_some();
|
||||
let status = if is_draft {
|
||||
ProposalStatus::Draft
|
||||
} else {
|
||||
ProposalStatus::Active
|
||||
};
|
||||
match proposals::create_new_proposal(
|
||||
&user_id,
|
||||
&user_name,
|
||||
proposal_title,
|
||||
proposal_description,
|
||||
status,
|
||||
voting_start_date,
|
||||
voting_end_date,
|
||||
) {
|
||||
@ -221,8 +248,14 @@ impl GovernanceController {
|
||||
|
||||
// For now, we'll just redirect to the proposals page with a success message
|
||||
|
||||
// Get mock proposals
|
||||
let proposals = Self::get_mock_proposals();
|
||||
// Get proposals from the database
|
||||
let proposals = match crate::db::proposals::get_proposals() {
|
||||
Ok(props) => props,
|
||||
Err(e) => {
|
||||
ctx.insert("error", &format!("Failed to load proposals: {}", e));
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
ctx.insert("proposals", &proposals);
|
||||
|
||||
render_template(&tmpl, "governance/proposals.html", &ctx)
|
||||
@ -371,96 +404,98 @@ impl GovernanceController {
|
||||
fn get_mock_proposals() -> Vec<Proposal> {
|
||||
let now = Utc::now();
|
||||
vec![
|
||||
Proposal {
|
||||
id: "prop-001".to_string(),
|
||||
creator_id: 1,
|
||||
creator_name: "Ibrahim Faraji".to_string(),
|
||||
title: "Establish Zanzibar Digital Trade Hub".to_string(),
|
||||
description: "This proposal aims to create a dedicated digital trade hub within the Zanzibar Digital Freezone to facilitate international e-commerce for local businesses. The hub will provide logistics support, digital marketing services, and regulatory compliance assistance to help Zanzibar businesses reach global markets.".to_string(),
|
||||
status: ProposalStatus::Active,
|
||||
created_at: now - Duration::days(5),
|
||||
updated_at: now - Duration::days(5),
|
||||
voting_starts_at: Some(now - Duration::days(3)),
|
||||
voting_ends_at: Some(now + Duration::days(4)),
|
||||
},
|
||||
Proposal {
|
||||
id: "prop-002".to_string(),
|
||||
creator_id: 2,
|
||||
creator_name: "Amina Salim".to_string(),
|
||||
title: "ZDFZ Sustainable Tourism Framework".to_string(),
|
||||
description: "A comprehensive framework for sustainable tourism development within the Zanzibar Digital Freezone. This proposal outlines environmental standards, community benefit-sharing mechanisms, and digital infrastructure for eco-tourism businesses. It includes tokenization standards for tourism assets and a certification system for sustainable operators.".to_string(),
|
||||
status: ProposalStatus::Approved,
|
||||
created_at: now - Duration::days(15),
|
||||
updated_at: now - Duration::days(2),
|
||||
voting_starts_at: Some(now - Duration::days(14)),
|
||||
voting_ends_at: Some(now - Duration::days(2)),
|
||||
},
|
||||
Proposal {
|
||||
id: "prop-003".to_string(),
|
||||
creator_id: 3,
|
||||
creator_name: "Hassan Mwinyi".to_string(),
|
||||
title: "Spice Industry Modernization Initiative".to_string(),
|
||||
description: "This proposal seeks to modernize Zanzibar's traditional spice industry through blockchain-based supply chain tracking, international quality certification, and digital marketplace integration. The initiative will help local spice farmers and processors access premium international markets while preserving traditional cultivation methods.".to_string(),
|
||||
status: ProposalStatus::Draft,
|
||||
created_at: now - Duration::days(1),
|
||||
updated_at: now - Duration::days(1),
|
||||
voting_starts_at: None,
|
||||
voting_ends_at: None,
|
||||
},
|
||||
Proposal {
|
||||
id: "prop-004".to_string(),
|
||||
creator_id: 1,
|
||||
creator_name: "Ibrahim Faraji".to_string(),
|
||||
title: "ZDFZ Regulatory Framework for Digital Financial Services".to_string(),
|
||||
description: "Establish a comprehensive regulatory framework for digital financial services within the Zanzibar Digital Freezone. This includes licensing requirements for crypto exchanges, digital payment providers, and tokenized asset platforms operating within the zone, while ensuring compliance with international AML/KYC standards.".to_string(),
|
||||
status: ProposalStatus::Rejected,
|
||||
created_at: now - Duration::days(20),
|
||||
updated_at: now - Duration::days(5),
|
||||
voting_starts_at: Some(now - Duration::days(19)),
|
||||
voting_ends_at: Some(now - Duration::days(5)),
|
||||
},
|
||||
Proposal {
|
||||
id: "prop-005".to_string(),
|
||||
creator_id: 4,
|
||||
creator_name: "Fatma Busaidy".to_string(),
|
||||
title: "Digital Arts Incubator and Artwork Marketplace".to_string(),
|
||||
description: "Create a dedicated digital arts incubator and Artwork marketplace to support Zanzibar's creative economy. The initiative will provide technical training, equipment, and a curated marketplace for local artists to create and sell digital art that celebrates Zanzibar's rich cultural heritage while accessing global markets.".to_string(),
|
||||
status: ProposalStatus::Active,
|
||||
created_at: now - Duration::days(7),
|
||||
updated_at: now - Duration::days(7),
|
||||
voting_starts_at: Some(now - Duration::days(6)),
|
||||
voting_ends_at: Some(now + Duration::days(1)),
|
||||
},
|
||||
Proposal {
|
||||
id: "prop-006".to_string(),
|
||||
creator_id: 5,
|
||||
creator_name: "Omar Makame".to_string(),
|
||||
title: "Zanzibar Renewable Energy Microgrid Network".to_string(),
|
||||
description: "Develop a network of renewable energy microgrids across the Zanzibar Digital Freezone using tokenized investment and community ownership models. This proposal outlines the technical specifications, governance structure, and token economics for deploying solar and tidal energy systems that will ensure energy independence for the zone.".to_string(),
|
||||
status: ProposalStatus::Active,
|
||||
created_at: now - Duration::days(10),
|
||||
updated_at: now - Duration::days(9),
|
||||
voting_starts_at: Some(now - Duration::days(8)),
|
||||
voting_ends_at: Some(now + Duration::days(6)),
|
||||
},
|
||||
Proposal {
|
||||
id: "prop-007".to_string(),
|
||||
creator_id: 6,
|
||||
creator_name: "Saida Juma".to_string(),
|
||||
title: "ZDFZ Educational Technology Initiative".to_string(),
|
||||
description: "Establish a comprehensive educational technology program within the Zanzibar Digital Freezone to develop local tech talent. This initiative includes coding academies, blockchain development courses, and digital entrepreneurship training, with a focus on preparing Zanzibar's youth for careers in the zone's growing digital economy.".to_string(),
|
||||
status: ProposalStatus::Draft,
|
||||
created_at: now - Duration::days(3),
|
||||
updated_at: now - Duration::days(2),
|
||||
voting_starts_at: None,
|
||||
voting_ends_at: None,
|
||||
},
|
||||
Proposal::new(
|
||||
Some(1),
|
||||
"1",
|
||||
"Ibrahim Faraji",
|
||||
"Establish Zanzibar Digital Trade Hub",
|
||||
"This proposal aims to create a dedicated digital trade hub within the Zanzibar Digital Freezone to facilitate international e-commerce for local businesses. The hub will provide logistics support, digital marketing services, and regulatory compliance assistance to help Zanzibar businesses reach global markets.",
|
||||
ProposalStatus::Active,
|
||||
now - Duration::days(5),
|
||||
now - Duration::days(5),
|
||||
now - Duration::days(3),
|
||||
now + Duration::days(4),
|
||||
),
|
||||
Proposal::new(
|
||||
Some(2),
|
||||
"2",
|
||||
"Amina Salim",
|
||||
"ZDFZ Sustainable Tourism Framework",
|
||||
"A comprehensive framework for sustainable tourism development within the Zanzibar Digital Freezone. This proposal outlines environmental standards, community benefit-sharing mechanisms, and digital infrastructure for eco-tourism businesses. It includes tokenization standards for tourism assets and a certification system for sustainable operators.",
|
||||
ProposalStatus::Approved,
|
||||
now - Duration::days(15),
|
||||
now - Duration::days(2),
|
||||
now - Duration::days(14),
|
||||
now - Duration::days(2),
|
||||
),
|
||||
Proposal::new(
|
||||
Some(3),
|
||||
"3",
|
||||
"Hassan Mwinyi",
|
||||
"Spice Industry Modernization Initiative",
|
||||
"This proposal seeks to modernize Zanzibar's traditional spice industry through blockchain-based supply chain tracking, international quality certification, and digital marketplace integration. The initiative will help local spice farmers and processors access premium international markets while preserving traditional cultivation methods.",
|
||||
ProposalStatus::Draft,
|
||||
now - Duration::days(1),
|
||||
now - Duration::days(1),
|
||||
now - Duration::days(1),
|
||||
now + Duration::days(1),
|
||||
),
|
||||
Proposal::new(
|
||||
Some(4),
|
||||
"4",
|
||||
"Ibrahim Faraji",
|
||||
"ZDFZ Regulatory Framework for Digital Financial Services",
|
||||
"Establish a comprehensive regulatory framework for digital financial services within the Zanzibar Digital Freezone. This includes licensing requirements for crypto exchanges, digital payment providers, and tokenized asset platforms operating within the zone, while ensuring compliance with international AML/KYC standards.",
|
||||
ProposalStatus::Rejected,
|
||||
now - Duration::days(20),
|
||||
now - Duration::days(5),
|
||||
now - Duration::days(19),
|
||||
now - Duration::days(5),
|
||||
),
|
||||
Proposal::new(
|
||||
Some(5),
|
||||
"5",
|
||||
"Fatma Busaidy",
|
||||
"Digital Arts Incubator and Artwork Marketplace",
|
||||
"Create a dedicated digital arts incubator and Artwork marketplace to support Zanzibar's creative economy. The initiative will provide technical training, equipment, and a curated marketplace for local artists to create and sell digital art that celebrates Zanzibar's rich cultural heritage while accessing global markets.",
|
||||
ProposalStatus::Active,
|
||||
now - Duration::days(7),
|
||||
now - Duration::days(7),
|
||||
now - Duration::days(6),
|
||||
now + Duration::days(1),
|
||||
),
|
||||
Proposal::new(
|
||||
Some(6),
|
||||
"6",
|
||||
"Omar Makame",
|
||||
"Zanzibar Renewable Energy Microgrid Network",
|
||||
"Develop a network of renewable energy microgrids across the Zanzibar Digital Freezone using tokenized investment and community ownership models. This proposal outlines the technical specifications, governance structure, and token economics for deploying solar and tidal energy systems that will ensure energy independence for the zone.",
|
||||
ProposalStatus::Active,
|
||||
now - Duration::days(10),
|
||||
now - Duration::days(9),
|
||||
now - Duration::days(8),
|
||||
now + Duration::days(6),
|
||||
),
|
||||
Proposal::new(
|
||||
Some(7),
|
||||
"7",
|
||||
"Saida Juma",
|
||||
"ZDFZ Educational Technology Initiative",
|
||||
"Establish a comprehensive educational technology program within the Zanzibar Digital Freezone to develop local tech talent. This initiative includes coding academies, blockchain development courses, and digital entrepreneurship training, with a focus on preparing Zanzibar's youth for careers in the zone's growing digital economy.",
|
||||
ProposalStatus::Draft,
|
||||
now - Duration::days(3),
|
||||
now - Duration::days(2),
|
||||
now - Duration::days(1),
|
||||
now + Duration::days(1),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
/// Get a mock proposal by ID
|
||||
fn get_mock_proposal_by_id(id: &str) -> Option<Proposal> {
|
||||
Self::get_mock_proposals().into_iter().find(|p| p.id == id)
|
||||
Self::get_mock_proposals()
|
||||
.into_iter()
|
||||
.find(|p| p.base_data.id.to_string() == id)
|
||||
}
|
||||
|
||||
/// Generate mock votes for a specific proposal
|
||||
@ -561,7 +596,7 @@ impl GovernanceController {
|
||||
.filter_map(|vote| {
|
||||
proposals
|
||||
.iter()
|
||||
.find(|p| p.id == vote.proposal_id)
|
||||
.find(|p| p.base_data.id.to_string() == vote.proposal_id)
|
||||
.map(|p| (vote.clone(), p.clone()))
|
||||
})
|
||||
.collect()
|
||||
@ -600,6 +635,8 @@ pub struct ProposalForm {
|
||||
pub title: String,
|
||||
/// Description of the proposal
|
||||
pub description: String,
|
||||
/// Status of the proposal
|
||||
pub draft: Option<bool>,
|
||||
/// Start date for voting
|
||||
pub voting_start_date: Option<String>,
|
||||
/// End date for voting
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use chrono::{Duration, Utc};
|
||||
use heromodels::db::hero::OurDB;
|
||||
use heromodels::{
|
||||
@ -6,39 +8,70 @@ use heromodels::{
|
||||
};
|
||||
|
||||
/// The path to the database file. Change this as needed for your environment.
|
||||
pub const DB_PATH: &str = "/tmp/ourdb_governance";
|
||||
pub const DB_PATH: &str = "/tmp/ourdb_governance2";
|
||||
|
||||
/// Returns a shared OurDB instance for the given path. You can wrap this in Arc/Mutex for concurrent access if needed.
|
||||
pub fn get_db(db_path: &str) -> Result<OurDB, String> {
|
||||
let db = heromodels::db::hero::OurDB::new(db_path, true).expect("Can create DB");
|
||||
let db_path = PathBuf::from(db_path);
|
||||
if let Some(parent) = db_path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
// Temporarily reset the database to fix the serialization issue
|
||||
let db = heromodels::db::hero::OurDB::new(db_path, false).expect("Can create DB");
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
/// Creates a new proposal and saves it to the database. Returns the saved proposal and its ID.
|
||||
pub fn create_new_proposal(
|
||||
creator_id: &str,
|
||||
creator_name: &str,
|
||||
title: &str,
|
||||
description: &str,
|
||||
status: ProposalStatus,
|
||||
voting_start_date: Option<chrono::DateTime<Utc>>,
|
||||
voting_end_date: Option<chrono::DateTime<Utc>>,
|
||||
) -> Result<(u32, Proposal), String> {
|
||||
let db = get_db(DB_PATH).expect("Can create DB");
|
||||
|
||||
let created_at = Utc::now();
|
||||
let updated_at = created_at;
|
||||
|
||||
// Create a new proposal (with auto-generated ID)
|
||||
let mut proposal = Proposal::new(
|
||||
let proposal = Proposal::new(
|
||||
None,
|
||||
creator_id,
|
||||
creator_name,
|
||||
title,
|
||||
description,
|
||||
status,
|
||||
created_at,
|
||||
updated_at,
|
||||
voting_start_date.unwrap_or_else(Utc::now),
|
||||
voting_end_date.unwrap_or_else(|| Utc::now() + Duration::days(7)),
|
||||
);
|
||||
proposal.status = ProposalStatus::Draft;
|
||||
|
||||
// Save the proposal to the database
|
||||
let collection = db
|
||||
.collection::<Proposal>()
|
||||
.expect("can open proposal collection");
|
||||
let (proposal_id, saved_proposal) = collection.set(&proposal).expect("can save proposal");
|
||||
|
||||
Ok((proposal_id, saved_proposal))
|
||||
}
|
||||
|
||||
/// Loads all proposals from the database and returns them as a Vec<Proposal>.
|
||||
pub fn get_proposals() -> Result<Vec<Proposal>, String> {
|
||||
let db = get_db(DB_PATH).map_err(|e| format!("DB error: {}", e))?;
|
||||
let collection = db
|
||||
.collection::<Proposal>()
|
||||
.expect("can open proposal collection");
|
||||
|
||||
// Try to load all proposals, but handle deserialization errors gracefully
|
||||
let proposals = match collection.get_all() {
|
||||
Ok(props) => props,
|
||||
Err(e) => {
|
||||
eprintln!("Error loading proposals: {:?}", e);
|
||||
vec![] // Return an empty vector if there's an error
|
||||
}
|
||||
};
|
||||
Ok(proposals)
|
||||
}
|
||||
|
@ -37,10 +37,12 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<span class="badge {% if proposal.status == 'Active' %}bg-success{% elif proposal.status == 'Approved' %}bg-primary{% elif proposal.status == 'Rejected' %}bg-danger{% elif proposal.status == 'Draft' %}bg-secondary{% else %}bg-warning{% endif %} p-2">
|
||||
<span
|
||||
class="badge {% if proposal.status == 'Active' %}bg-success{% elif proposal.status == 'Approved' %}bg-primary{% elif proposal.status == 'Rejected' %}bg-danger{% elif proposal.status == 'Draft' %}bg-secondary{% else %}bg-warning{% endif %} p-2">
|
||||
{{ proposal.status }}
|
||||
</span>
|
||||
<small class="text-muted">Created by {{ proposal.creator_name }} on {{ proposal.created_at | date(format="%Y-%m-%d") }}</small>
|
||||
<small class="text-muted">Created by {{ proposal.creator_name }}
|
||||
<!-- on {{ proposal.created_at | date(format="%Y-%m-%d") }} --></small>
|
||||
</div>
|
||||
|
||||
<h5>Description</h5>
|
||||
@ -49,10 +51,10 @@
|
||||
<h5>Voting Period</h5>
|
||||
<p>
|
||||
{% if proposal.voting_starts_at and proposal.voting_ends_at %}
|
||||
<strong>Start:</strong> {{ proposal.voting_starts_at | date(format="%Y-%m-%d") }} <br>
|
||||
<strong>End:</strong> {{ proposal.voting_ends_at | date(format="%Y-%m-%d") }}
|
||||
<strong>Start:</strong> {{ proposal.voting_starts_at | date(format="%Y-%m-%d") }} <br>
|
||||
<strong>End:</strong> {{ proposal.voting_ends_at | date(format="%Y-%m-%d") }}
|
||||
{% else %}
|
||||
Not set
|
||||
Not set
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
@ -71,24 +73,27 @@
|
||||
{% set abstain_percent = 0 %}
|
||||
|
||||
{% if results.total_votes > 0 %}
|
||||
{% set yes_percent = (results.yes_count * 100 / results.total_votes) | int %}
|
||||
{% set no_percent = (results.no_count * 100 / results.total_votes) | int %}
|
||||
{% set abstain_percent = (results.abstain_count * 100 / results.total_votes) | int %}
|
||||
{% set yes_percent = (results.yes_count * 100 / results.total_votes) | int %}
|
||||
{% set no_percent = (results.no_count * 100 / results.total_votes) | int %}
|
||||
{% set abstain_percent = (results.abstain_count * 100 / results.total_votes) | int %}
|
||||
{% endif %}
|
||||
|
||||
<p class="mb-1">Yes: {{ results.yes_count }} ({{ yes_percent }}%)</p>
|
||||
<div class="progress mb-3">
|
||||
<div class="progress-bar bg-success" role="progressbar" style="width: {{ yes_percent }}%"></div>
|
||||
<div class="progress-bar bg-success" role="progressbar" style="width: {{ yes_percent }}%">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mb-1">No: {{ results.no_count }} ({{ no_percent }}%)</p>
|
||||
<div class="progress mb-3">
|
||||
<div class="progress-bar bg-danger" role="progressbar" style="width: {{ no_percent }}%"></div>
|
||||
<div class="progress-bar bg-danger" role="progressbar" style="width: {{ no_percent }}%">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mb-1">Abstain: {{ results.abstain_count }} ({{ abstain_percent }}%)</p>
|
||||
<div class="progress mb-3">
|
||||
<div class="progress-bar bg-secondary" role="progressbar" style="width: {{ abstain_percent }}%"></div>
|
||||
<div class="progress-bar bg-secondary" role="progressbar"
|
||||
style="width: {{ abstain_percent }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-center"><strong>Total Votes:</strong> {{ results.total_votes }}</p>
|
||||
@ -106,7 +111,8 @@
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Vote Type</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="vote_type" id="voteYes" value="Yes" checked>
|
||||
<input class="form-check-input" type="radio" name="vote_type" id="voteYes" value="Yes"
|
||||
checked>
|
||||
<label class="form-check-label" for="voteYes">
|
||||
Yes - I support this proposal
|
||||
</label>
|
||||
@ -118,7 +124,8 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="vote_type" id="voteAbstain" value="Abstain">
|
||||
<input class="form-check-input" type="radio" name="vote_type" id="voteAbstain"
|
||||
value="Abstain">
|
||||
<label class="form-check-label" for="voteAbstain">
|
||||
Abstain - I choose not to vote
|
||||
</label>
|
||||
@ -126,7 +133,8 @@
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="comment" class="form-label">Comment (Optional)</label>
|
||||
<textarea class="form-control" id="comment" name="comment" rows="3" placeholder="Explain your vote..."></textarea>
|
||||
<textarea class="form-control" id="comment" name="comment" rows="3"
|
||||
placeholder="Explain your vote..."></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Submit Vote</button>
|
||||
</form>
|
||||
@ -167,7 +175,8 @@
|
||||
<tr>
|
||||
<td>{{ vote.voter_name }}</td>
|
||||
<td>
|
||||
<span class="badge {% if vote.vote_type == 'Yes' %}bg-success{% elif vote.vote_type == 'No' %}bg-danger{% else %}bg-secondary{% endif %}">
|
||||
<span
|
||||
class="badge {% if vote.vote_type == 'Yes' %}bg-success{% elif vote.vote_type == 'No' %}bg-danger{% else %}bg-secondary{% endif %}">
|
||||
{{ vote.vote_type }}
|
||||
</span>
|
||||
</td>
|
||||
|
@ -3,128 +3,135 @@
|
||||
{% block title %}Proposals - Governance Dashboard{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Success message if present -->
|
||||
{% if success %}
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
{{ success }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<!-- Success message if present -->
|
||||
{% if success %}
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info alert-dismissible fade show">
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
{{ success }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
<h5><i class="bi bi-info-circle"></i> About Proposals</h5>
|
||||
<p>Proposals are formal requests for changes to the platform that require community approval. Each proposal includes a detailed description, implementation plan, and voting period. Browse the list below to see all active and past proposals.</p>
|
||||
<div class="mt-2">
|
||||
<a href="/governance/proposal-guidelines" class="btn btn-sm btn-outline-primary"><i class="bi bi-file-text"></i> Proposal Guidelines</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Filter Controls -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form action="/governance/proposals" method="get" class="row g-3">
|
||||
<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>
|
||||
</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">
|
||||
</div>
|
||||
<div class="col-md-2 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary w-100">Filter</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 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>
|
||||
<h5><i class="bi bi-info-circle"></i> About Proposals</h5>
|
||||
<p>Proposals are formal requests for changes to the platform that require community approval. Each proposal
|
||||
includes a detailed description, implementation plan, and voting period. Browse the list below to see all
|
||||
active and past proposals.</p>
|
||||
<div class="mt-2">
|
||||
<a href="/governance/proposal-guidelines" class="btn btn-sm btn-outline-primary"><i
|
||||
class="bi bi-file-text"></i> Proposal Guidelines</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Proposals List -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">All Proposals</h5>
|
||||
<a href="/governance/create" class="btn btn-sm btn-primary">Create New Proposal</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Creator</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
<th>Voting Period</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for proposal in proposals %}
|
||||
<tr>
|
||||
<td>{{ proposal.title }}</td>
|
||||
<td>{{ proposal.creator_name }}</td>
|
||||
<td>
|
||||
<span class="badge {% if proposal.status == 'Active' %}bg-success{% elif proposal.status == 'Approved' %}bg-primary{% elif proposal.status == 'Rejected' %}bg-danger{% elif proposal.status == 'Draft' %}bg-secondary{% else %}bg-warning{% endif %}">
|
||||
{{ proposal.status }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ proposal.created_at | date(format="%Y-%m-%d") }}</td>
|
||||
<td>
|
||||
{% if proposal.voting_starts_at and proposal.voting_ends_at %}
|
||||
{{ proposal.voting_starts_at | date(format="%Y-%m-%d") }} to {{ proposal.voting_ends_at | date(format="%Y-%m-%d") }}
|
||||
{% else %}
|
||||
Not set
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="/governance/proposals/{{ proposal.id }}" class="btn btn-sm btn-primary">View</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- Filter Controls -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form action="/governance/proposals" method="get" class="row g-3">
|
||||
<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>
|
||||
</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">
|
||||
</div>
|
||||
<div class="col-md-2 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary w-100">Filter</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Proposals List -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">All Proposals</h5>
|
||||
<a href="/governance/create" class="btn btn-sm btn-primary">Create New Proposal</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Creator</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
<th>Voting Period</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for proposal in proposals %}
|
||||
<tr>
|
||||
<td>{{ proposal.title }}</td>
|
||||
<td>{{ proposal.creator_name }}</td>
|
||||
<td>
|
||||
<span
|
||||
class="badge {% if proposal.status == 'Active' %}bg-success{% elif proposal.status == 'Approved' %}bg-primary{% elif proposal.status == 'Rejected' %}bg-danger{% elif proposal.status == 'Draft' %}bg-secondary{% else %}bg-warning{% endif %}">
|
||||
{{ proposal.status }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ proposal.created_at | date(format="%Y-%m-%d") }}</td>
|
||||
<td>
|
||||
{% if proposal.voting_starts_at and proposal.voting_ends_at %}
|
||||
{{ proposal.voting_starts_at | date(format="%Y-%m-%d") }} to {{
|
||||
proposal.voting_ends_at | date(format="%Y-%m-%d") }}
|
||||
{% else %}
|
||||
Not set
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="/governance/proposals/{{ proposal.base_data.id }}"
|
||||
class="btn btn-sm btn-primary">View</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user