implement governance and flow functionality
This commit is contained in:
		
							
								
								
									
										478
									
								
								actix_mvc_app/src/controllers/flow.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										478
									
								
								actix_mvc_app/src/controllers/flow.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,478 @@
 | 
			
		||||
use actix_web::{web, HttpResponse, Responder, Result};
 | 
			
		||||
use actix_session::Session;
 | 
			
		||||
use chrono::{Utc, Duration};
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
use tera::Tera;
 | 
			
		||||
use std::error::Error; // Add this line
 | 
			
		||||
 | 
			
		||||
use crate::models::flow::{Flow, FlowStatus, FlowType, FlowStatistics, FlowStep, StepStatus, FlowLog};
 | 
			
		||||
use crate::controllers::auth::Claims;
 | 
			
		||||
 | 
			
		||||
pub struct FlowController;
 | 
			
		||||
 | 
			
		||||
impl FlowController {
 | 
			
		||||
    /// Renders the flows dashboard
 | 
			
		||||
    pub async fn index(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
 | 
			
		||||
        let user = Self::get_user_from_session(&session);
 | 
			
		||||
        let flows = Self::get_mock_flows();
 | 
			
		||||
        let stats = FlowStatistics::new(&flows);
 | 
			
		||||
        
 | 
			
		||||
        let mut ctx = tera::Context::new();
 | 
			
		||||
        ctx.insert("active_page", "flows");
 | 
			
		||||
        ctx.insert("user", &user);
 | 
			
		||||
        ctx.insert("flows", &flows);
 | 
			
		||||
        ctx.insert("stats", &stats);
 | 
			
		||||
        ctx.insert("active_flows", &flows.iter().filter(|f| f.status == FlowStatus::InProgress).collect::<Vec<_>>());
 | 
			
		||||
        ctx.insert("stuck_flows", &flows.iter().filter(|f| f.status == FlowStatus::Stuck).collect::<Vec<_>>());
 | 
			
		||||
        
 | 
			
		||||
        let rendered = tmpl.render("flows/index.html", &ctx)
 | 
			
		||||
            .map_err(|e| {
 | 
			
		||||
                eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                eprintln!("Error details: {:?}", e);
 | 
			
		||||
                eprintln!("Context: {:?}", ctx);
 | 
			
		||||
                actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
            })?;
 | 
			
		||||
        
 | 
			
		||||
        Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Renders the flows list page
 | 
			
		||||
    pub async fn list_flows(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
 | 
			
		||||
        let user = Self::get_user_from_session(&session);
 | 
			
		||||
        let flows = Self::get_mock_flows();
 | 
			
		||||
        
 | 
			
		||||
        let mut ctx = tera::Context::new();
 | 
			
		||||
        ctx.insert("active_page", "flows");
 | 
			
		||||
        ctx.insert("user", &user);
 | 
			
		||||
        ctx.insert("flows", &flows);
 | 
			
		||||
        
 | 
			
		||||
        let rendered = tmpl.render("flows/flows.html", &ctx)
 | 
			
		||||
            .map_err(|e| {
 | 
			
		||||
                eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                eprintln!("Error details: {:?}", e);
 | 
			
		||||
                eprintln!("Context: {:?}", ctx);
 | 
			
		||||
                actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
            })?;
 | 
			
		||||
        
 | 
			
		||||
        Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Renders the flow detail page
 | 
			
		||||
    pub async fn flow_detail(
 | 
			
		||||
        path: web::Path<String>,
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
        session: Session
 | 
			
		||||
    ) -> Result<impl Responder> {
 | 
			
		||||
        let flow_id = path.into_inner();
 | 
			
		||||
        let user = Self::get_user_from_session(&session);
 | 
			
		||||
        
 | 
			
		||||
        // Find the flow with the given ID
 | 
			
		||||
        let flows = Self::get_mock_flows();
 | 
			
		||||
        let flow = flows.iter().find(|f| f.id == flow_id);
 | 
			
		||||
        
 | 
			
		||||
        if let Some(flow) = flow {
 | 
			
		||||
            let mut ctx = tera::Context::new();
 | 
			
		||||
            ctx.insert("active_page", "flows");
 | 
			
		||||
            ctx.insert("user", &user);
 | 
			
		||||
            ctx.insert("flow", flow);
 | 
			
		||||
            
 | 
			
		||||
            let rendered = tmpl.render("flows/flow_detail.html", &ctx)
 | 
			
		||||
                .map_err(|e| {
 | 
			
		||||
                    eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                    eprintln!("Error details: {:?}", e);
 | 
			
		||||
                    eprintln!("Context: {:?}", ctx);
 | 
			
		||||
                    actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
                })?;
 | 
			
		||||
            
 | 
			
		||||
            Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
 | 
			
		||||
        } else {
 | 
			
		||||
            let mut ctx = tera::Context::new();
 | 
			
		||||
            ctx.insert("error", "Flow not found");
 | 
			
		||||
            
 | 
			
		||||
            let rendered = tmpl.render("error.html", &ctx)
 | 
			
		||||
                .map_err(|e| {
 | 
			
		||||
                    eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                    eprintln!("Error details: {:?}", e);
 | 
			
		||||
                    eprintln!("Context: {:?}", ctx);
 | 
			
		||||
                    actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
                })?;
 | 
			
		||||
            
 | 
			
		||||
            Ok(HttpResponse::NotFound().content_type("text/html").body(rendered))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Renders the create flow page
 | 
			
		||||
    pub async fn create_flow_form(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
 | 
			
		||||
        let user = Self::get_user_from_session(&session);
 | 
			
		||||
        
 | 
			
		||||
        let mut ctx = tera::Context::new();
 | 
			
		||||
        ctx.insert("active_page", "flows");
 | 
			
		||||
        ctx.insert("user", &user);
 | 
			
		||||
        
 | 
			
		||||
        let rendered = tmpl.render("flows/create_flow.html", &ctx)
 | 
			
		||||
            .map_err(|e| {
 | 
			
		||||
                eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                eprintln!("Error details: {:?}", e);
 | 
			
		||||
                eprintln!("Context: {:?}", ctx);
 | 
			
		||||
                actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
            })?;
 | 
			
		||||
        
 | 
			
		||||
        Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Handles the create flow form submission
 | 
			
		||||
    pub async fn create_flow(
 | 
			
		||||
        _form: web::Form<FlowForm>,
 | 
			
		||||
        session: Session
 | 
			
		||||
    ) -> impl Responder {
 | 
			
		||||
        // In a real application, we would create a new flow here
 | 
			
		||||
        // For now, just redirect to the flows list
 | 
			
		||||
        
 | 
			
		||||
        HttpResponse::Found()
 | 
			
		||||
            .append_header(("Location", "/flows"))
 | 
			
		||||
            .finish()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Renders the my flows page
 | 
			
		||||
    pub async fn my_flows(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
 | 
			
		||||
        let user = Self::get_user_from_session(&session);
 | 
			
		||||
        
 | 
			
		||||
        if let Some(user) = &user {
 | 
			
		||||
            let flows = Self::get_mock_flows();
 | 
			
		||||
            let my_flows = flows.iter()
 | 
			
		||||
                .filter(|f| f.owner_name == user.sub)
 | 
			
		||||
                .collect::<Vec<_>>();
 | 
			
		||||
            
 | 
			
		||||
            let mut ctx = tera::Context::new();
 | 
			
		||||
            ctx.insert("active_page", "flows");
 | 
			
		||||
            ctx.insert("user", &user);
 | 
			
		||||
            ctx.insert("flows", &my_flows);
 | 
			
		||||
            
 | 
			
		||||
            let rendered = tmpl.render("flows/my_flows.html", &ctx)
 | 
			
		||||
                .map_err(|e| {
 | 
			
		||||
                    eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                    eprintln!("Error details: {:?}", e);
 | 
			
		||||
                    eprintln!("Context: {:?}", ctx);
 | 
			
		||||
                    actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
                })?;
 | 
			
		||||
            
 | 
			
		||||
            Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok(HttpResponse::Found()
 | 
			
		||||
                .append_header(("Location", "/login"))
 | 
			
		||||
                .finish())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Handles the advance flow step action
 | 
			
		||||
    pub async fn advance_flow_step(
 | 
			
		||||
        path: web::Path<String>,
 | 
			
		||||
        _session: Session
 | 
			
		||||
    ) -> impl Responder {
 | 
			
		||||
        let flow_id = path.into_inner();
 | 
			
		||||
        
 | 
			
		||||
        // In a real application, we would advance the flow step here
 | 
			
		||||
        // For now, just redirect to the flow detail page
 | 
			
		||||
        
 | 
			
		||||
        HttpResponse::Found()
 | 
			
		||||
            .append_header(("Location", format!("/flows/{}", flow_id)))
 | 
			
		||||
            .finish()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Handles the mark flow step as stuck action
 | 
			
		||||
    pub async fn mark_flow_step_stuck(
 | 
			
		||||
        path: web::Path<String>,
 | 
			
		||||
        _form: web::Form<StuckForm>,
 | 
			
		||||
        _session: Session
 | 
			
		||||
    ) -> impl Responder {
 | 
			
		||||
        let flow_id = path.into_inner();
 | 
			
		||||
        
 | 
			
		||||
        // In a real application, we would mark the flow step as stuck here
 | 
			
		||||
        // For now, just redirect to the flow detail page
 | 
			
		||||
        
 | 
			
		||||
        HttpResponse::Found()
 | 
			
		||||
            .append_header(("Location", format!("/flows/{}", flow_id)))
 | 
			
		||||
            .finish()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Handles the add log to flow step action
 | 
			
		||||
    pub async fn add_log_to_flow_step(
 | 
			
		||||
        path: web::Path<(String, String)>,
 | 
			
		||||
        _form: web::Form<LogForm>,
 | 
			
		||||
        _session: Session
 | 
			
		||||
    ) -> impl Responder {
 | 
			
		||||
        let (flow_id, _step_id) = path.into_inner();
 | 
			
		||||
        
 | 
			
		||||
        // In a real application, we would add a log to the flow step here
 | 
			
		||||
        // For now, just redirect to the flow detail page
 | 
			
		||||
        
 | 
			
		||||
        HttpResponse::Found()
 | 
			
		||||
            .append_header(("Location", format!("/flows/{}", flow_id)))
 | 
			
		||||
            .finish()
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Gets the user from the session
 | 
			
		||||
    fn get_user_from_session(session: &Session) -> Option<Claims> {
 | 
			
		||||
        if let Ok(Some(user)) = session.get::<Claims>("user") {
 | 
			
		||||
            Some(user)
 | 
			
		||||
        } else {
 | 
			
		||||
            None
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /// Creates mock flow data for testing
 | 
			
		||||
    fn get_mock_flows() -> Vec<Flow> {
 | 
			
		||||
        let mut flows = Vec::new();
 | 
			
		||||
        
 | 
			
		||||
        // Create a few mock flows
 | 
			
		||||
        let mut flow1 = Flow {
 | 
			
		||||
            id: "flow-1".to_string(),
 | 
			
		||||
            name: "Deploy Website".to_string(),
 | 
			
		||||
            description: "Deploy a new website to production".to_string(),
 | 
			
		||||
            flow_type: FlowType::ServiceActivation,
 | 
			
		||||
            status: FlowStatus::InProgress,
 | 
			
		||||
            owner_id: "user-1".to_string(),
 | 
			
		||||
            owner_name: "John Doe".to_string(),
 | 
			
		||||
            steps: vec![
 | 
			
		||||
                FlowStep {
 | 
			
		||||
                    id: "step-1-1".to_string(),
 | 
			
		||||
                    name: "Build".to_string(),
 | 
			
		||||
                    description: "Build the website".to_string(),
 | 
			
		||||
                    order: 1,
 | 
			
		||||
                    status: StepStatus::Completed,
 | 
			
		||||
                    started_at: Some(Utc::now() - Duration::days(1)),
 | 
			
		||||
                    completed_at: Some(Utc::now() - Duration::hours(23)),
 | 
			
		||||
                    logs: vec![
 | 
			
		||||
                        FlowLog {
 | 
			
		||||
                            id: "log-1-1-1".to_string(),
 | 
			
		||||
                            message: "Build started".to_string(),
 | 
			
		||||
                            timestamp: Utc::now() - Duration::days(1),
 | 
			
		||||
                        },
 | 
			
		||||
                        FlowLog {
 | 
			
		||||
                            id: "log-1-1-2".to_string(),
 | 
			
		||||
                            message: "Build completed successfully".to_string(),
 | 
			
		||||
                            timestamp: Utc::now() - Duration::hours(23),
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
                FlowStep {
 | 
			
		||||
                    id: "step-1-2".to_string(),
 | 
			
		||||
                    name: "Test".to_string(),
 | 
			
		||||
                    description: "Run tests on the website".to_string(),
 | 
			
		||||
                    order: 2,
 | 
			
		||||
                    status: StepStatus::InProgress,
 | 
			
		||||
                    started_at: Some(Utc::now() - Duration::hours(22)),
 | 
			
		||||
                    completed_at: None,
 | 
			
		||||
                    logs: vec![
 | 
			
		||||
                        FlowLog {
 | 
			
		||||
                            id: "log-1-2-1".to_string(),
 | 
			
		||||
                            message: "Tests started".to_string(),
 | 
			
		||||
                            timestamp: Utc::now() - Duration::hours(22),
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
                FlowStep {
 | 
			
		||||
                    id: "step-1-3".to_string(),
 | 
			
		||||
                    name: "Deploy".to_string(),
 | 
			
		||||
                    description: "Deploy the website to production".to_string(),
 | 
			
		||||
                    order: 3,
 | 
			
		||||
                    status: StepStatus::Pending,
 | 
			
		||||
                    started_at: None,
 | 
			
		||||
                    completed_at: None,
 | 
			
		||||
                    logs: vec![],
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
            created_at: Utc::now() - Duration::days(1),
 | 
			
		||||
            updated_at: Utc::now() - Duration::hours(22),
 | 
			
		||||
            completed_at: None,
 | 
			
		||||
            progress_percentage: 33,
 | 
			
		||||
            current_step: None,
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        // Update the current step
 | 
			
		||||
        flow1.current_step = flow1.steps.iter().find(|s| s.status == StepStatus::InProgress).cloned();
 | 
			
		||||
        
 | 
			
		||||
        let mut flow2 = Flow {
 | 
			
		||||
            id: "flow-2".to_string(),
 | 
			
		||||
            name: "Database Migration".to_string(),
 | 
			
		||||
            description: "Migrate database to new schema".to_string(),
 | 
			
		||||
            flow_type: FlowType::CompanyRegistration,
 | 
			
		||||
            status: FlowStatus::Completed,
 | 
			
		||||
            owner_id: "user-2".to_string(),
 | 
			
		||||
            owner_name: "Jane Smith".to_string(),
 | 
			
		||||
            steps: vec![
 | 
			
		||||
                FlowStep {
 | 
			
		||||
                    id: "step-2-1".to_string(),
 | 
			
		||||
                    name: "Backup".to_string(),
 | 
			
		||||
                    description: "Backup the database".to_string(),
 | 
			
		||||
                    order: 1,
 | 
			
		||||
                    status: StepStatus::Completed,
 | 
			
		||||
                    started_at: Some(Utc::now() - Duration::days(3)),
 | 
			
		||||
                    completed_at: Some(Utc::now() - Duration::days(3) + Duration::hours(2)),
 | 
			
		||||
                    logs: vec![
 | 
			
		||||
                        FlowLog {
 | 
			
		||||
                            id: "log-2-1-1".to_string(),
 | 
			
		||||
                            message: "Backup started".to_string(),
 | 
			
		||||
                            timestamp: Utc::now() - Duration::days(3),
 | 
			
		||||
                        },
 | 
			
		||||
                        FlowLog {
 | 
			
		||||
                            id: "log-2-1-2".to_string(),
 | 
			
		||||
                            message: "Backup completed successfully".to_string(),
 | 
			
		||||
                            timestamp: Utc::now() - Duration::days(3) + Duration::hours(2),
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
                FlowStep {
 | 
			
		||||
                    id: "step-2-2".to_string(),
 | 
			
		||||
                    name: "Migrate".to_string(),
 | 
			
		||||
                    description: "Run migration scripts".to_string(),
 | 
			
		||||
                    order: 2,
 | 
			
		||||
                    status: StepStatus::Completed,
 | 
			
		||||
                    started_at: Some(Utc::now() - Duration::days(3) + Duration::hours(3)),
 | 
			
		||||
                    completed_at: Some(Utc::now() - Duration::days(3) + Duration::hours(5)),
 | 
			
		||||
                    logs: vec![
 | 
			
		||||
                        FlowLog {
 | 
			
		||||
                            id: "log-2-2-1".to_string(),
 | 
			
		||||
                            message: "Migration started".to_string(),
 | 
			
		||||
                            timestamp: Utc::now() - Duration::days(3) + Duration::hours(3),
 | 
			
		||||
                        },
 | 
			
		||||
                        FlowLog {
 | 
			
		||||
                            id: "log-2-2-2".to_string(),
 | 
			
		||||
                            message: "Migration completed successfully".to_string(),
 | 
			
		||||
                            timestamp: Utc::now() - Duration::days(3) + Duration::hours(5),
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
                FlowStep {
 | 
			
		||||
                    id: "step-2-3".to_string(),
 | 
			
		||||
                    name: "Verify".to_string(),
 | 
			
		||||
                    description: "Verify the migration".to_string(),
 | 
			
		||||
                    order: 3,
 | 
			
		||||
                    status: StepStatus::Completed,
 | 
			
		||||
                    started_at: Some(Utc::now() - Duration::days(3) + Duration::hours(6)),
 | 
			
		||||
                    completed_at: Some(Utc::now() - Duration::days(3) + Duration::hours(7)),
 | 
			
		||||
                    logs: vec![
 | 
			
		||||
                        FlowLog {
 | 
			
		||||
                            id: "log-2-3-1".to_string(),
 | 
			
		||||
                            message: "Verification started".to_string(),
 | 
			
		||||
                            timestamp: Utc::now() - Duration::days(3) + Duration::hours(6),
 | 
			
		||||
                        },
 | 
			
		||||
                        FlowLog {
 | 
			
		||||
                            id: "log-2-3-2".to_string(),
 | 
			
		||||
                            message: "Verification completed successfully".to_string(),
 | 
			
		||||
                            timestamp: Utc::now() - Duration::days(3) + Duration::hours(7),
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
            created_at: Utc::now() - Duration::days(3),
 | 
			
		||||
            updated_at: Utc::now() - Duration::days(3) + Duration::hours(7),
 | 
			
		||||
            completed_at: Some(Utc::now() - Duration::days(3) + Duration::hours(7)),
 | 
			
		||||
            progress_percentage: 100,
 | 
			
		||||
            current_step: None,
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        let mut flow3 = Flow {
 | 
			
		||||
            id: "flow-3".to_string(),
 | 
			
		||||
            name: "Server Maintenance".to_string(),
 | 
			
		||||
            description: "Perform server maintenance".to_string(),
 | 
			
		||||
            flow_type: FlowType::PaymentProcessing,
 | 
			
		||||
            status: FlowStatus::Stuck,
 | 
			
		||||
            owner_id: "user-1".to_string(),
 | 
			
		||||
            owner_name: "John Doe".to_string(),
 | 
			
		||||
            steps: vec![
 | 
			
		||||
                FlowStep {
 | 
			
		||||
                    id: "step-3-1".to_string(),
 | 
			
		||||
                    name: "Backup".to_string(),
 | 
			
		||||
                    description: "Backup the server".to_string(),
 | 
			
		||||
                    order: 1,
 | 
			
		||||
                    status: StepStatus::Completed,
 | 
			
		||||
                    started_at: Some(Utc::now() - Duration::days(2)),
 | 
			
		||||
                    completed_at: Some(Utc::now() - Duration::days(2) + Duration::hours(1)),
 | 
			
		||||
                    logs: vec![
 | 
			
		||||
                        FlowLog {
 | 
			
		||||
                            id: "log-3-1-1".to_string(),
 | 
			
		||||
                            message: "Backup started".to_string(),
 | 
			
		||||
                            timestamp: Utc::now() - Duration::days(2),
 | 
			
		||||
                        },
 | 
			
		||||
                        FlowLog {
 | 
			
		||||
                            id: "log-3-1-2".to_string(),
 | 
			
		||||
                            message: "Backup completed successfully".to_string(),
 | 
			
		||||
                            timestamp: Utc::now() - Duration::days(2) + Duration::hours(1),
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
                FlowStep {
 | 
			
		||||
                    id: "step-3-2".to_string(),
 | 
			
		||||
                    name: "Update".to_string(),
 | 
			
		||||
                    description: "Update the server".to_string(),
 | 
			
		||||
                    order: 2,
 | 
			
		||||
                    status: StepStatus::Stuck,
 | 
			
		||||
                    started_at: Some(Utc::now() - Duration::days(2) + Duration::hours(2)),
 | 
			
		||||
                    completed_at: None,
 | 
			
		||||
                    logs: vec![
 | 
			
		||||
                        FlowLog {
 | 
			
		||||
                            id: "log-3-2-1".to_string(),
 | 
			
		||||
                            message: "Update started".to_string(),
 | 
			
		||||
                            timestamp: Utc::now() - Duration::days(2) + Duration::hours(2),
 | 
			
		||||
                        },
 | 
			
		||||
                        FlowLog {
 | 
			
		||||
                            id: "log-3-2-2".to_string(),
 | 
			
		||||
                            message: "Update failed: Disk space issue".to_string(),
 | 
			
		||||
                            timestamp: Utc::now() - Duration::days(2) + Duration::hours(3),
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
                FlowStep {
 | 
			
		||||
                    id: "step-3-3".to_string(),
 | 
			
		||||
                    name: "Restart".to_string(),
 | 
			
		||||
                    description: "Restart the server".to_string(),
 | 
			
		||||
                    order: 3,
 | 
			
		||||
                    status: StepStatus::Pending,
 | 
			
		||||
                    started_at: None,
 | 
			
		||||
                    completed_at: None,
 | 
			
		||||
                    logs: vec![],
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
            created_at: Utc::now() - Duration::days(2),
 | 
			
		||||
            updated_at: Utc::now() - Duration::days(2) + Duration::hours(3),
 | 
			
		||||
            completed_at: None,
 | 
			
		||||
            progress_percentage: 33,
 | 
			
		||||
            current_step: None,
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        // Update the current step
 | 
			
		||||
        flow3.current_step = flow3.steps.iter().find(|s| s.status == StepStatus::Stuck).cloned();
 | 
			
		||||
        
 | 
			
		||||
        flows.push(flow1);
 | 
			
		||||
        flows.push(flow2);
 | 
			
		||||
        flows.push(flow3);
 | 
			
		||||
        
 | 
			
		||||
        flows
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Form for creating a new flow
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
pub struct FlowForm {
 | 
			
		||||
    /// Flow name
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    /// Flow description
 | 
			
		||||
    pub description: String,
 | 
			
		||||
    /// Flow type
 | 
			
		||||
    pub flow_type: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Form for marking a step as stuck
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
pub struct StuckForm {
 | 
			
		||||
    /// Reason for being stuck
 | 
			
		||||
    pub reason: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Form for adding a log to a step
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
pub struct LogForm {
 | 
			
		||||
    /// Log message
 | 
			
		||||
    pub message: String,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										502
									
								
								actix_mvc_app/src/controllers/governance.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										502
									
								
								actix_mvc_app/src/controllers/governance.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,502 @@
 | 
			
		||||
use actix_web::{web, HttpResponse, Responder, Result};
 | 
			
		||||
use actix_session::Session;
 | 
			
		||||
use tera::Tera;
 | 
			
		||||
use serde_json::Value;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use chrono::{Utc, Duration};
 | 
			
		||||
use crate::models::governance::{Proposal, Vote, ProposalStatus, VoteType, ProposalFilter, VotingResults};
 | 
			
		||||
 | 
			
		||||
/// Controller for handling governance-related routes
 | 
			
		||||
pub struct GovernanceController;
 | 
			
		||||
 | 
			
		||||
impl GovernanceController {
 | 
			
		||||
    /// Helper function to get user from session
 | 
			
		||||
    fn get_user_from_session(session: &Session) -> Option<Value> {
 | 
			
		||||
        session.get::<String>("user").ok().flatten().and_then(|user_json| {
 | 
			
		||||
            serde_json::from_str(&user_json).ok()
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Handles the governance dashboard page route
 | 
			
		||||
    pub async fn index(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
 | 
			
		||||
        let mut ctx = tera::Context::new();
 | 
			
		||||
        ctx.insert("active_page", "governance");
 | 
			
		||||
        
 | 
			
		||||
        // Add user to context if available
 | 
			
		||||
        if let Some(user) = Self::get_user_from_session(&session) {
 | 
			
		||||
            ctx.insert("user", &user);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get mock proposals for the dashboard
 | 
			
		||||
        let proposals = Self::get_mock_proposals();
 | 
			
		||||
        ctx.insert("proposals", &proposals);
 | 
			
		||||
        
 | 
			
		||||
        // Get some statistics
 | 
			
		||||
        let stats = Self::get_mock_statistics();
 | 
			
		||||
        ctx.insert("stats", &stats);
 | 
			
		||||
        
 | 
			
		||||
        let rendered = tmpl.render("governance/index.html", &ctx)
 | 
			
		||||
            .map_err(|e| {
 | 
			
		||||
                eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
            })?;
 | 
			
		||||
        
 | 
			
		||||
        Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Handles the proposal list page route
 | 
			
		||||
    pub async fn proposals(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");
 | 
			
		||||
        
 | 
			
		||||
        // Add user to context if available
 | 
			
		||||
        if let Some(user) = Self::get_user_from_session(&session) {
 | 
			
		||||
            ctx.insert("user", &user);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get mock proposals
 | 
			
		||||
        let proposals = Self::get_mock_proposals();
 | 
			
		||||
        ctx.insert("proposals", &proposals);
 | 
			
		||||
        
 | 
			
		||||
        let rendered = tmpl.render("governance/proposals.html", &ctx)
 | 
			
		||||
            .map_err(|e| {
 | 
			
		||||
                eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
            })?;
 | 
			
		||||
        
 | 
			
		||||
        Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Handles the proposal detail page route
 | 
			
		||||
    pub async fn proposal_detail(
 | 
			
		||||
        path: web::Path<String>,
 | 
			
		||||
        tmpl: web::Data<Tera>, 
 | 
			
		||||
        session: Session
 | 
			
		||||
    ) -> Result<impl Responder> {
 | 
			
		||||
        let proposal_id = path.into_inner();
 | 
			
		||||
        let mut ctx = tera::Context::new();
 | 
			
		||||
        ctx.insert("active_page", "governance");
 | 
			
		||||
        
 | 
			
		||||
        // Add user to context if available
 | 
			
		||||
        if let Some(user) = Self::get_user_from_session(&session) {
 | 
			
		||||
            ctx.insert("user", &user);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get mock proposal detail
 | 
			
		||||
        let proposal = Self::get_mock_proposal_by_id(&proposal_id);
 | 
			
		||||
        if let Some(proposal) = proposal {
 | 
			
		||||
            ctx.insert("proposal", &proposal);
 | 
			
		||||
            
 | 
			
		||||
            // Get mock votes for this proposal
 | 
			
		||||
            let votes = Self::get_mock_votes_for_proposal(&proposal_id);
 | 
			
		||||
            ctx.insert("votes", &votes);
 | 
			
		||||
            
 | 
			
		||||
            // Get voting results
 | 
			
		||||
            let results = Self::get_mock_voting_results(&proposal_id);
 | 
			
		||||
            ctx.insert("results", &results);
 | 
			
		||||
            
 | 
			
		||||
            let rendered = tmpl.render("governance/proposal_detail.html", &ctx)
 | 
			
		||||
                .map_err(|e| {
 | 
			
		||||
                    eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                    actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
                })?;
 | 
			
		||||
            
 | 
			
		||||
            Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
 | 
			
		||||
        } else {
 | 
			
		||||
            // Proposal not found
 | 
			
		||||
            ctx.insert("error", "Proposal not found");
 | 
			
		||||
            let rendered = tmpl.render("error.html", &ctx)
 | 
			
		||||
                .map_err(|e| {
 | 
			
		||||
                    eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                    actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
                })?;
 | 
			
		||||
            
 | 
			
		||||
            Ok(HttpResponse::NotFound().content_type("text/html").body(rendered))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Handles the create proposal page route
 | 
			
		||||
    pub async fn create_proposal_form(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
 | 
			
		||||
        let mut ctx = tera::Context::new();
 | 
			
		||||
        ctx.insert("active_page", "governance");
 | 
			
		||||
        ctx.insert("active_tab", "create");
 | 
			
		||||
        
 | 
			
		||||
        // Add user to context if available
 | 
			
		||||
        if let Some(user) = Self::get_user_from_session(&session) {
 | 
			
		||||
            ctx.insert("user", &user);
 | 
			
		||||
        } else {
 | 
			
		||||
            // Redirect to login if not logged in
 | 
			
		||||
            return Ok(HttpResponse::Found().append_header(("Location", "/login")).finish());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        let rendered = tmpl.render("governance/create_proposal.html", &ctx)
 | 
			
		||||
            .map_err(|e| {
 | 
			
		||||
                eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
            })?;
 | 
			
		||||
        
 | 
			
		||||
        Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Handles the submission of a new proposal
 | 
			
		||||
    pub async fn submit_proposal(
 | 
			
		||||
        form: web::Form<ProposalForm>,
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
        session: Session
 | 
			
		||||
    ) -> Result<impl Responder> {
 | 
			
		||||
        // Check if user is logged in
 | 
			
		||||
        if Self::get_user_from_session(&session).is_none() {
 | 
			
		||||
            return Ok(HttpResponse::Found().append_header(("Location", "/login")).finish());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        let mut ctx = tera::Context::new();
 | 
			
		||||
        ctx.insert("active_page", "governance");
 | 
			
		||||
        
 | 
			
		||||
        // Add user to context if available
 | 
			
		||||
        if let Some(user) = Self::get_user_from_session(&session) {
 | 
			
		||||
            ctx.insert("user", &user);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // In a real application, we would save the proposal to a database
 | 
			
		||||
        // For now, we'll just redirect to the proposals page with a success message
 | 
			
		||||
        ctx.insert("success", "Proposal created successfully!");
 | 
			
		||||
        
 | 
			
		||||
        // Get mock proposals
 | 
			
		||||
        let proposals = Self::get_mock_proposals();
 | 
			
		||||
        ctx.insert("proposals", &proposals);
 | 
			
		||||
        
 | 
			
		||||
        let rendered = tmpl.render("governance/proposals.html", &ctx)
 | 
			
		||||
            .map_err(|e| {
 | 
			
		||||
                eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
            })?;
 | 
			
		||||
        
 | 
			
		||||
        Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Handles the submission of a vote on a proposal
 | 
			
		||||
    pub async fn submit_vote(
 | 
			
		||||
        path: web::Path<String>,
 | 
			
		||||
        form: web::Form<VoteForm>,
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
        session: Session
 | 
			
		||||
    ) -> Result<impl Responder> {
 | 
			
		||||
        let proposal_id = path.into_inner();
 | 
			
		||||
        
 | 
			
		||||
        // Check if user is logged in
 | 
			
		||||
        if Self::get_user_from_session(&session).is_none() {
 | 
			
		||||
            return Ok(HttpResponse::Found().append_header(("Location", "/login")).finish());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        let mut ctx = tera::Context::new();
 | 
			
		||||
        ctx.insert("active_page", "governance");
 | 
			
		||||
        
 | 
			
		||||
        // Add user to context if available
 | 
			
		||||
        if let Some(user) = Self::get_user_from_session(&session) {
 | 
			
		||||
            ctx.insert("user", &user);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get mock proposal detail
 | 
			
		||||
        let proposal = Self::get_mock_proposal_by_id(&proposal_id);
 | 
			
		||||
        if let Some(proposal) = proposal {
 | 
			
		||||
            ctx.insert("proposal", &proposal);
 | 
			
		||||
            ctx.insert("success", "Your vote has been recorded!");
 | 
			
		||||
            
 | 
			
		||||
            // Get mock votes for this proposal
 | 
			
		||||
            let votes = Self::get_mock_votes_for_proposal(&proposal_id);
 | 
			
		||||
            ctx.insert("votes", &votes);
 | 
			
		||||
            
 | 
			
		||||
            // Get voting results
 | 
			
		||||
            let results = Self::get_mock_voting_results(&proposal_id);
 | 
			
		||||
            ctx.insert("results", &results);
 | 
			
		||||
            
 | 
			
		||||
            let rendered = tmpl.render("governance/proposal_detail.html", &ctx)
 | 
			
		||||
                .map_err(|e| {
 | 
			
		||||
                    eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                    actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
                })?;
 | 
			
		||||
            
 | 
			
		||||
            Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
 | 
			
		||||
        } else {
 | 
			
		||||
            // Proposal not found
 | 
			
		||||
            ctx.insert("error", "Proposal not found");
 | 
			
		||||
            let rendered = tmpl.render("error.html", &ctx)
 | 
			
		||||
                .map_err(|e| {
 | 
			
		||||
                    eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                    actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
                })?;
 | 
			
		||||
            
 | 
			
		||||
            Ok(HttpResponse::NotFound().content_type("text/html").body(rendered))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Handles the my votes page route
 | 
			
		||||
    pub async fn my_votes(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
 | 
			
		||||
        let mut ctx = tera::Context::new();
 | 
			
		||||
        ctx.insert("active_page", "governance");
 | 
			
		||||
        ctx.insert("active_tab", "my_votes");
 | 
			
		||||
        
 | 
			
		||||
        // Add user to context if available
 | 
			
		||||
        if let Some(user) = Self::get_user_from_session(&session) {
 | 
			
		||||
            ctx.insert("user", &user);
 | 
			
		||||
            
 | 
			
		||||
            // Get mock votes for this user
 | 
			
		||||
            let votes = Self::get_mock_votes_for_user(1); // Assuming user ID 1 for mock data
 | 
			
		||||
            ctx.insert("votes", &votes);
 | 
			
		||||
            
 | 
			
		||||
            let rendered = tmpl.render("governance/my_votes.html", &ctx)
 | 
			
		||||
                .map_err(|e| {
 | 
			
		||||
                    eprintln!("Template rendering error: {}", e);
 | 
			
		||||
                    actix_web::error::ErrorInternalServerError("Template rendering error")
 | 
			
		||||
                })?;
 | 
			
		||||
            
 | 
			
		||||
            Ok(HttpResponse::Ok().content_type("text/html").body(rendered))
 | 
			
		||||
        } else {
 | 
			
		||||
            // Redirect to login if not logged in
 | 
			
		||||
            Ok(HttpResponse::Found().append_header(("Location", "/login")).finish())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Mock data generation methods
 | 
			
		||||
    
 | 
			
		||||
    /// Generate mock proposals for testing
 | 
			
		||||
    fn get_mock_proposals() -> Vec<Proposal> {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
        vec![
 | 
			
		||||
            Proposal {
 | 
			
		||||
                id: "prop-001".to_string(),
 | 
			
		||||
                creator_id: 1,
 | 
			
		||||
                creator_name: "John Doe".to_string(),
 | 
			
		||||
                title: "Implement Community Rewards Program".to_string(),
 | 
			
		||||
                description: "This proposal aims to implement a community rewards program to incentivize active participation in the platform.".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: "Jane Smith".to_string(),
 | 
			
		||||
                title: "Platform UI Redesign".to_string(),
 | 
			
		||||
                description: "A comprehensive redesign of the platform's user interface to improve usability and accessibility.".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: "Bob Johnson".to_string(),
 | 
			
		||||
                title: "Add Support for Mobile Notifications".to_string(),
 | 
			
		||||
                description: "Implement push notifications for mobile users to improve engagement and user experience.".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: "John Doe".to_string(),
 | 
			
		||||
                title: "Integrate with Third-party Payment Providers".to_string(),
 | 
			
		||||
                description: "Add support for additional payment providers to give users more options for transactions.".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: "Alice Williams".to_string(),
 | 
			
		||||
                title: "Implement Two-Factor Authentication".to_string(),
 | 
			
		||||
                description: "Enhance security by implementing two-factor authentication for all user accounts.".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)),
 | 
			
		||||
            },
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// 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)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Generate mock votes for a specific proposal
 | 
			
		||||
    fn get_mock_votes_for_proposal(proposal_id: &str) -> Vec<Vote> {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
        vec![
 | 
			
		||||
            Vote {
 | 
			
		||||
                id: "vote-001".to_string(),
 | 
			
		||||
                proposal_id: proposal_id.to_string(),
 | 
			
		||||
                voter_id: 1,
 | 
			
		||||
                voter_name: "John Doe".to_string(),
 | 
			
		||||
                vote_type: VoteType::Yes,
 | 
			
		||||
                comment: Some("I strongly support this initiative.".to_string()),
 | 
			
		||||
                created_at: now - Duration::days(2),
 | 
			
		||||
                updated_at: now - Duration::days(2),
 | 
			
		||||
            },
 | 
			
		||||
            Vote {
 | 
			
		||||
                id: "vote-002".to_string(),
 | 
			
		||||
                proposal_id: proposal_id.to_string(),
 | 
			
		||||
                voter_id: 2,
 | 
			
		||||
                voter_name: "Jane Smith".to_string(),
 | 
			
		||||
                vote_type: VoteType::Yes,
 | 
			
		||||
                comment: None,
 | 
			
		||||
                created_at: now - Duration::days(2),
 | 
			
		||||
                updated_at: now - Duration::days(2),
 | 
			
		||||
            },
 | 
			
		||||
            Vote {
 | 
			
		||||
                id: "vote-003".to_string(),
 | 
			
		||||
                proposal_id: proposal_id.to_string(),
 | 
			
		||||
                voter_id: 3,
 | 
			
		||||
                voter_name: "Bob Johnson".to_string(),
 | 
			
		||||
                vote_type: VoteType::No,
 | 
			
		||||
                comment: Some("I have concerns about the implementation cost.".to_string()),
 | 
			
		||||
                created_at: now - Duration::days(1),
 | 
			
		||||
                updated_at: now - Duration::days(1),
 | 
			
		||||
            },
 | 
			
		||||
            Vote {
 | 
			
		||||
                id: "vote-004".to_string(),
 | 
			
		||||
                proposal_id: proposal_id.to_string(),
 | 
			
		||||
                voter_id: 4,
 | 
			
		||||
                voter_name: "Alice Williams".to_string(),
 | 
			
		||||
                vote_type: VoteType::Abstain,
 | 
			
		||||
                comment: Some("I need more information before making a decision.".to_string()),
 | 
			
		||||
                created_at: now - Duration::hours(12),
 | 
			
		||||
                updated_at: now - Duration::hours(12),
 | 
			
		||||
            },
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Generate mock votes for a specific user
 | 
			
		||||
    fn get_mock_votes_for_user(user_id: i32) -> Vec<(Vote, Proposal)> {
 | 
			
		||||
        let votes = vec![
 | 
			
		||||
            Vote {
 | 
			
		||||
                id: "vote-001".to_string(),
 | 
			
		||||
                proposal_id: "prop-001".to_string(),
 | 
			
		||||
                voter_id: user_id,
 | 
			
		||||
                voter_name: "John Doe".to_string(),
 | 
			
		||||
                vote_type: VoteType::Yes,
 | 
			
		||||
                comment: Some("I strongly support this initiative.".to_string()),
 | 
			
		||||
                created_at: Utc::now() - Duration::days(2),
 | 
			
		||||
                updated_at: Utc::now() - Duration::days(2),
 | 
			
		||||
            },
 | 
			
		||||
            Vote {
 | 
			
		||||
                id: "vote-005".to_string(),
 | 
			
		||||
                proposal_id: "prop-002".to_string(),
 | 
			
		||||
                voter_id: user_id,
 | 
			
		||||
                voter_name: "John Doe".to_string(),
 | 
			
		||||
                vote_type: VoteType::No,
 | 
			
		||||
                comment: Some("I don't think this is a priority right now.".to_string()),
 | 
			
		||||
                created_at: Utc::now() - Duration::days(10),
 | 
			
		||||
                updated_at: Utc::now() - Duration::days(10),
 | 
			
		||||
            },
 | 
			
		||||
            Vote {
 | 
			
		||||
                id: "vote-008".to_string(),
 | 
			
		||||
                proposal_id: "prop-004".to_string(),
 | 
			
		||||
                voter_id: user_id,
 | 
			
		||||
                voter_name: "John Doe".to_string(),
 | 
			
		||||
                vote_type: VoteType::Yes,
 | 
			
		||||
                comment: None,
 | 
			
		||||
                created_at: Utc::now() - Duration::days(18),
 | 
			
		||||
                updated_at: Utc::now() - Duration::days(18),
 | 
			
		||||
            },
 | 
			
		||||
            Vote {
 | 
			
		||||
                id: "vote-010".to_string(),
 | 
			
		||||
                proposal_id: "prop-005".to_string(),
 | 
			
		||||
                voter_id: user_id,
 | 
			
		||||
                voter_name: "John Doe".to_string(),
 | 
			
		||||
                vote_type: VoteType::Yes,
 | 
			
		||||
                comment: Some("Security is always a top priority.".to_string()),
 | 
			
		||||
                created_at: Utc::now() - Duration::days(5),
 | 
			
		||||
                updated_at: Utc::now() - Duration::days(5),
 | 
			
		||||
            },
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        let proposals = Self::get_mock_proposals();
 | 
			
		||||
        votes.into_iter()
 | 
			
		||||
            .filter_map(|vote| {
 | 
			
		||||
                proposals.iter()
 | 
			
		||||
                    .find(|p| p.id == vote.proposal_id)
 | 
			
		||||
                    .map(|p| (vote.clone(), p.clone()))
 | 
			
		||||
            })
 | 
			
		||||
            .collect()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Generate mock voting results for a proposal
 | 
			
		||||
    fn get_mock_voting_results(proposal_id: &str) -> VotingResults {
 | 
			
		||||
        let votes = Self::get_mock_votes_for_proposal(proposal_id);
 | 
			
		||||
        let mut results = VotingResults::new(proposal_id.to_string());
 | 
			
		||||
        
 | 
			
		||||
        for vote in votes {
 | 
			
		||||
            results.add_vote(&vote.vote_type);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        results
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Generate mock statistics for the governance dashboard
 | 
			
		||||
    fn get_mock_statistics() -> GovernanceStats {
 | 
			
		||||
        GovernanceStats {
 | 
			
		||||
            total_proposals: 5,
 | 
			
		||||
            active_proposals: 2,
 | 
			
		||||
            approved_proposals: 1,
 | 
			
		||||
            rejected_proposals: 1,
 | 
			
		||||
            draft_proposals: 1,
 | 
			
		||||
            total_votes: 15,
 | 
			
		||||
            participation_rate: 75.0,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents the data submitted in the proposal form
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
pub struct ProposalForm {
 | 
			
		||||
    /// Title of the proposal
 | 
			
		||||
    pub title: String,
 | 
			
		||||
    /// Description of the proposal
 | 
			
		||||
    pub description: String,
 | 
			
		||||
    /// Start date for voting
 | 
			
		||||
    pub voting_start_date: Option<String>,
 | 
			
		||||
    /// End date for voting
 | 
			
		||||
    pub voting_end_date: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents the data submitted in the vote form
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
pub struct VoteForm {
 | 
			
		||||
    /// Type of vote (yes, no, abstain)
 | 
			
		||||
    pub vote_type: String,
 | 
			
		||||
    /// Optional comment explaining the vote
 | 
			
		||||
    pub comment: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents statistics for the governance dashboard
 | 
			
		||||
#[derive(Debug, Serialize)]
 | 
			
		||||
pub struct GovernanceStats {
 | 
			
		||||
    /// Total number of proposals
 | 
			
		||||
    pub total_proposals: usize,
 | 
			
		||||
    /// Number of active proposals
 | 
			
		||||
    pub active_proposals: usize,
 | 
			
		||||
    /// Number of approved proposals
 | 
			
		||||
    pub approved_proposals: usize,
 | 
			
		||||
    /// Number of rejected proposals
 | 
			
		||||
    pub rejected_proposals: usize,
 | 
			
		||||
    /// Number of draft proposals
 | 
			
		||||
    pub draft_proposals: usize,
 | 
			
		||||
    /// Total number of votes cast
 | 
			
		||||
    pub total_votes: usize,
 | 
			
		||||
    /// Participation rate (percentage)
 | 
			
		||||
    pub participation_rate: f64,
 | 
			
		||||
}
 | 
			
		||||
@@ -2,4 +2,14 @@
 | 
			
		||||
pub mod home;
 | 
			
		||||
pub mod auth;
 | 
			
		||||
pub mod ticket;
 | 
			
		||||
pub mod calendar;
 | 
			
		||||
pub mod calendar;
 | 
			
		||||
pub mod governance;
 | 
			
		||||
pub mod flow;
 | 
			
		||||
 | 
			
		||||
// Re-export controllers for easier imports
 | 
			
		||||
pub use home::HomeController;
 | 
			
		||||
pub use auth::AuthController;
 | 
			
		||||
pub use ticket::TicketController;
 | 
			
		||||
pub use calendar::CalendarController;
 | 
			
		||||
pub use governance::GovernanceController;
 | 
			
		||||
pub use flow::FlowController;
 | 
			
		||||
							
								
								
									
										378
									
								
								actix_mvc_app/src/models/flow.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										378
									
								
								actix_mvc_app/src/models/flow.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,378 @@
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
 | 
			
		||||
/// Status of a flow
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
pub enum FlowStatus {
 | 
			
		||||
    /// Flow is in progress
 | 
			
		||||
    InProgress,
 | 
			
		||||
    /// Flow is completed
 | 
			
		||||
    Completed,
 | 
			
		||||
    /// Flow is stuck at a step
 | 
			
		||||
    Stuck,
 | 
			
		||||
    /// Flow is cancelled
 | 
			
		||||
    Cancelled,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::fmt::Display for FlowStatus {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            FlowStatus::InProgress => write!(f, "In Progress"),
 | 
			
		||||
            FlowStatus::Completed => write!(f, "Completed"),
 | 
			
		||||
            FlowStatus::Stuck => write!(f, "Stuck"),
 | 
			
		||||
            FlowStatus::Cancelled => write!(f, "Cancelled"),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Type of flow
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
pub enum FlowType {
 | 
			
		||||
    /// Company registration flow
 | 
			
		||||
    CompanyRegistration,
 | 
			
		||||
    /// User onboarding flow
 | 
			
		||||
    UserOnboarding,
 | 
			
		||||
    /// Service activation flow
 | 
			
		||||
    ServiceActivation,
 | 
			
		||||
    /// Payment processing flow
 | 
			
		||||
    PaymentProcessing,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::fmt::Display for FlowType {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            FlowType::CompanyRegistration => write!(f, "Company Registration"),
 | 
			
		||||
            FlowType::UserOnboarding => write!(f, "User Onboarding"),
 | 
			
		||||
            FlowType::ServiceActivation => write!(f, "Service Activation"),
 | 
			
		||||
            FlowType::PaymentProcessing => write!(f, "Payment Processing"),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Filter for flows
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub enum FlowFilter {
 | 
			
		||||
    /// All flows
 | 
			
		||||
    All,
 | 
			
		||||
    /// Only in progress flows
 | 
			
		||||
    InProgress,
 | 
			
		||||
    /// Only completed flows
 | 
			
		||||
    Completed,
 | 
			
		||||
    /// Only stuck flows
 | 
			
		||||
    Stuck,
 | 
			
		||||
    /// Only cancelled flows
 | 
			
		||||
    Cancelled,
 | 
			
		||||
    /// Flows of a specific type
 | 
			
		||||
    ByType(FlowType),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::fmt::Display for FlowFilter {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            FlowFilter::All => write!(f, "All"),
 | 
			
		||||
            FlowFilter::InProgress => write!(f, "In Progress"),
 | 
			
		||||
            FlowFilter::Completed => write!(f, "Completed"),
 | 
			
		||||
            FlowFilter::Stuck => write!(f, "Stuck"),
 | 
			
		||||
            FlowFilter::Cancelled => write!(f, "Cancelled"),
 | 
			
		||||
            FlowFilter::ByType(flow_type) => write!(f, "Type: {}", flow_type),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A step in a flow
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct FlowStep {
 | 
			
		||||
    /// Step ID
 | 
			
		||||
    pub id: String,
 | 
			
		||||
    /// Step name
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    /// Step description
 | 
			
		||||
    pub description: String,
 | 
			
		||||
    /// Step status
 | 
			
		||||
    pub status: StepStatus,
 | 
			
		||||
    /// Step order in the flow
 | 
			
		||||
    pub order: u32,
 | 
			
		||||
    /// Step started at
 | 
			
		||||
    pub started_at: Option<DateTime<Utc>>,
 | 
			
		||||
    /// Step completed at
 | 
			
		||||
    pub completed_at: Option<DateTime<Utc>>,
 | 
			
		||||
    /// Step logs
 | 
			
		||||
    pub logs: Vec<FlowLog>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FlowStep {
 | 
			
		||||
    /// Creates a new flow step
 | 
			
		||||
    pub fn new(name: String, description: String, order: u32) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            id: Uuid::new_v4().to_string(),
 | 
			
		||||
            name,
 | 
			
		||||
            description,
 | 
			
		||||
            status: StepStatus::Pending,
 | 
			
		||||
            order,
 | 
			
		||||
            started_at: None,
 | 
			
		||||
            completed_at: None,
 | 
			
		||||
            logs: Vec::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Starts the step
 | 
			
		||||
    pub fn start(&mut self) {
 | 
			
		||||
        self.status = StepStatus::InProgress;
 | 
			
		||||
        self.started_at = Some(Utc::now());
 | 
			
		||||
        self.add_log("Step started".to_string());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Completes the step
 | 
			
		||||
    pub fn complete(&mut self) {
 | 
			
		||||
        self.status = StepStatus::Completed;
 | 
			
		||||
        self.completed_at = Some(Utc::now());
 | 
			
		||||
        self.add_log("Step completed".to_string());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Marks the step as stuck
 | 
			
		||||
    pub fn mark_stuck(&mut self, reason: String) {
 | 
			
		||||
        self.status = StepStatus::Stuck;
 | 
			
		||||
        self.add_log(format!("Step stuck: {}", reason));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Adds a log entry to the step
 | 
			
		||||
    pub fn add_log(&mut self, message: String) {
 | 
			
		||||
        self.logs.push(FlowLog::new(message));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Status of a step in a flow
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
pub enum StepStatus {
 | 
			
		||||
    /// Step is pending
 | 
			
		||||
    Pending,
 | 
			
		||||
    /// Step is in progress
 | 
			
		||||
    InProgress,
 | 
			
		||||
    /// Step is completed
 | 
			
		||||
    Completed,
 | 
			
		||||
    /// Step is stuck
 | 
			
		||||
    Stuck,
 | 
			
		||||
    /// Step is skipped
 | 
			
		||||
    Skipped,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::fmt::Display for StepStatus {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            StepStatus::Pending => write!(f, "Pending"),
 | 
			
		||||
            StepStatus::InProgress => write!(f, "In Progress"),
 | 
			
		||||
            StepStatus::Completed => write!(f, "Completed"),
 | 
			
		||||
            StepStatus::Stuck => write!(f, "Stuck"),
 | 
			
		||||
            StepStatus::Skipped => write!(f, "Skipped"),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A log entry in a flow step
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct FlowLog {
 | 
			
		||||
    /// Log ID
 | 
			
		||||
    pub id: String,
 | 
			
		||||
    /// Log message
 | 
			
		||||
    pub message: String,
 | 
			
		||||
    /// Log timestamp
 | 
			
		||||
    pub timestamp: DateTime<Utc>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FlowLog {
 | 
			
		||||
    /// Creates a new flow log
 | 
			
		||||
    pub fn new(message: String) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            id: Uuid::new_v4().to_string(),
 | 
			
		||||
            message,
 | 
			
		||||
            timestamp: Utc::now(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A flow with multiple steps
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct Flow {
 | 
			
		||||
    /// Flow ID
 | 
			
		||||
    pub id: String,
 | 
			
		||||
    /// Flow name
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    /// Flow description
 | 
			
		||||
    pub description: String,
 | 
			
		||||
    /// Flow type
 | 
			
		||||
    pub flow_type: FlowType,
 | 
			
		||||
    /// Flow status
 | 
			
		||||
    pub status: FlowStatus,
 | 
			
		||||
    /// Flow owner ID
 | 
			
		||||
    pub owner_id: String,
 | 
			
		||||
    /// Flow owner name
 | 
			
		||||
    pub owner_name: String,
 | 
			
		||||
    /// Flow created at
 | 
			
		||||
    pub created_at: DateTime<Utc>,
 | 
			
		||||
    /// Flow updated at
 | 
			
		||||
    pub updated_at: DateTime<Utc>,
 | 
			
		||||
    /// Flow completed at
 | 
			
		||||
    pub completed_at: Option<DateTime<Utc>>,
 | 
			
		||||
    /// Flow steps
 | 
			
		||||
    pub steps: Vec<FlowStep>,
 | 
			
		||||
    /// Progress percentage
 | 
			
		||||
    pub progress_percentage: u8,
 | 
			
		||||
    /// Current step
 | 
			
		||||
    pub current_step: Option<FlowStep>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Flow {
 | 
			
		||||
    /// Creates a new flow
 | 
			
		||||
    pub fn new(name: &str, description: &str, flow_type: FlowType, owner_id: &str, owner_name: &str) -> Self {
 | 
			
		||||
        let id = Uuid::new_v4().to_string();
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
        let steps = vec![
 | 
			
		||||
            FlowStep::new("Initialization".to_string(), "Setting up the flow".to_string(), 1),
 | 
			
		||||
            FlowStep::new("Processing".to_string(), "Processing the flow data".to_string(), 2),
 | 
			
		||||
            FlowStep::new("Finalization".to_string(), "Completing the flow".to_string(), 3),
 | 
			
		||||
        ];
 | 
			
		||||
        
 | 
			
		||||
        // Set the first step as in progress
 | 
			
		||||
        let mut flow = Self {
 | 
			
		||||
            id,
 | 
			
		||||
            name: name.to_string(),
 | 
			
		||||
            description: description.to_string(),
 | 
			
		||||
            flow_type,
 | 
			
		||||
            status: FlowStatus::InProgress,
 | 
			
		||||
            owner_id: owner_id.to_string(),
 | 
			
		||||
            owner_name: owner_name.to_string(),
 | 
			
		||||
            steps,
 | 
			
		||||
            created_at: now,
 | 
			
		||||
            updated_at: now,
 | 
			
		||||
            completed_at: None,
 | 
			
		||||
            progress_percentage: 0,
 | 
			
		||||
            current_step: None,
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        // Calculate progress and set current step
 | 
			
		||||
        flow.update_progress();
 | 
			
		||||
        
 | 
			
		||||
        flow
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn update_progress(&mut self) {
 | 
			
		||||
        // Calculate progress percentage
 | 
			
		||||
        let total_steps = self.steps.len();
 | 
			
		||||
        if total_steps == 0 {
 | 
			
		||||
            self.progress_percentage = 100;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        let completed_steps = self.steps.iter().filter(|s| s.status == StepStatus::Completed).count();
 | 
			
		||||
        self.progress_percentage = ((completed_steps as f32 / total_steps as f32) * 100.0) as u8;
 | 
			
		||||
        
 | 
			
		||||
        // Find current step
 | 
			
		||||
        self.current_step = self.steps.iter()
 | 
			
		||||
            .find(|s| s.status == StepStatus::InProgress)
 | 
			
		||||
            .cloned();
 | 
			
		||||
        
 | 
			
		||||
        // Update flow status based on steps
 | 
			
		||||
        if self.progress_percentage == 100 {
 | 
			
		||||
            self.status = FlowStatus::Completed;
 | 
			
		||||
        } else if self.steps.iter().any(|s| s.status == StepStatus::Stuck) {
 | 
			
		||||
            self.status = FlowStatus::Stuck;
 | 
			
		||||
        } else {
 | 
			
		||||
            self.status = FlowStatus::InProgress;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    pub fn advance_step(&mut self) -> Result<(), String> {
 | 
			
		||||
        let current_index = self.steps.iter().position(|s| s.status == StepStatus::InProgress);
 | 
			
		||||
        
 | 
			
		||||
        if let Some(index) = current_index {
 | 
			
		||||
            // Mark current step as completed
 | 
			
		||||
            self.steps[index].status = StepStatus::Completed;
 | 
			
		||||
            self.steps[index].completed_at = Some(Utc::now());
 | 
			
		||||
            
 | 
			
		||||
            // If there's a next step, mark it as in progress
 | 
			
		||||
            if index + 1 < self.steps.len() {
 | 
			
		||||
                self.steps[index + 1].status = StepStatus::InProgress;
 | 
			
		||||
                self.steps[index + 1].started_at = Some(Utc::now());
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            self.updated_at = Utc::now();
 | 
			
		||||
            self.update_progress();
 | 
			
		||||
            
 | 
			
		||||
            Ok(())
 | 
			
		||||
        } else {
 | 
			
		||||
            Err("No step in progress to advance".to_string())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    pub fn mark_step_stuck(&mut self, reason: &str) -> Result<(), String> {
 | 
			
		||||
        let current_index = self.steps.iter().position(|s| s.status == StepStatus::InProgress);
 | 
			
		||||
        
 | 
			
		||||
        if let Some(index) = current_index {
 | 
			
		||||
            // Mark current step as stuck
 | 
			
		||||
            self.steps[index].status = StepStatus::Stuck;
 | 
			
		||||
            
 | 
			
		||||
            // Add a log entry for the stuck reason
 | 
			
		||||
            self.steps[index].add_log(reason.to_string());
 | 
			
		||||
            
 | 
			
		||||
            self.updated_at = Utc::now();
 | 
			
		||||
            self.update_progress();
 | 
			
		||||
            
 | 
			
		||||
            Ok(())
 | 
			
		||||
        } else {
 | 
			
		||||
            Err("No step in progress to mark as stuck".to_string())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    pub fn add_log_to_step(&mut self, step_id: &str, message: &str) -> Result<(), String> {
 | 
			
		||||
        if let Some(step) = self.steps.iter_mut().find(|s| s.id == step_id) {
 | 
			
		||||
            step.add_log(message.to_string());
 | 
			
		||||
            self.updated_at = Utc::now();
 | 
			
		||||
            Ok(())
 | 
			
		||||
        } else {
 | 
			
		||||
            Err(format!("Step with ID {} not found", step_id))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Flow statistics
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct FlowStatistics {
 | 
			
		||||
    /// Total number of flows
 | 
			
		||||
    pub total_flows: usize,
 | 
			
		||||
    /// Number of in progress flows
 | 
			
		||||
    pub in_progress_flows: usize,
 | 
			
		||||
    /// Number of completed flows
 | 
			
		||||
    pub completed_flows: usize,
 | 
			
		||||
    /// Number of stuck flows
 | 
			
		||||
    pub stuck_flows: usize,
 | 
			
		||||
    /// Number of cancelled flows
 | 
			
		||||
    pub cancelled_flows: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FlowStatistics {
 | 
			
		||||
    /// Creates new flow statistics
 | 
			
		||||
    pub fn new(flows: &[Flow]) -> Self {
 | 
			
		||||
        let total_flows = flows.len();
 | 
			
		||||
        let in_progress_flows = flows.iter()
 | 
			
		||||
            .filter(|flow| flow.status == FlowStatus::InProgress)
 | 
			
		||||
            .count();
 | 
			
		||||
        let completed_flows = flows.iter()
 | 
			
		||||
            .filter(|flow| flow.status == FlowStatus::Completed)
 | 
			
		||||
            .count();
 | 
			
		||||
        let stuck_flows = flows.iter()
 | 
			
		||||
            .filter(|flow| flow.status == FlowStatus::Stuck)
 | 
			
		||||
            .count();
 | 
			
		||||
        let cancelled_flows = flows.iter()
 | 
			
		||||
            .filter(|flow| flow.status == FlowStatus::Cancelled)
 | 
			
		||||
            .count();
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            total_flows,
 | 
			
		||||
            in_progress_flows,
 | 
			
		||||
            completed_flows,
 | 
			
		||||
            stuck_flows,
 | 
			
		||||
            cancelled_flows,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										248
									
								
								actix_mvc_app/src/models/governance.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								actix_mvc_app/src/models/governance.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,248 @@
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
 | 
			
		||||
/// Represents the status of a governance proposal
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
pub enum ProposalStatus {
 | 
			
		||||
    /// Proposal is in draft status, not yet open for voting
 | 
			
		||||
    Draft,
 | 
			
		||||
    /// Proposal is active and open for voting
 | 
			
		||||
    Active,
 | 
			
		||||
    /// Proposal has been approved by the community
 | 
			
		||||
    Approved,
 | 
			
		||||
    /// Proposal has been rejected by the community
 | 
			
		||||
    Rejected,
 | 
			
		||||
    /// Proposal has been cancelled by the creator
 | 
			
		||||
    Cancelled,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::fmt::Display for ProposalStatus {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            ProposalStatus::Draft => write!(f, "Draft"),
 | 
			
		||||
            ProposalStatus::Active => write!(f, "Active"),
 | 
			
		||||
            ProposalStatus::Approved => write!(f, "Approved"),
 | 
			
		||||
            ProposalStatus::Rejected => write!(f, "Rejected"),
 | 
			
		||||
            ProposalStatus::Cancelled => write!(f, "Cancelled"),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents a vote on a governance proposal
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
pub enum VoteType {
 | 
			
		||||
    /// Vote in favor of the proposal
 | 
			
		||||
    Yes,
 | 
			
		||||
    /// Vote against the proposal
 | 
			
		||||
    No,
 | 
			
		||||
    /// Abstain from voting on the proposal
 | 
			
		||||
    Abstain,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::fmt::Display for VoteType {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            VoteType::Yes => write!(f, "Yes"),
 | 
			
		||||
            VoteType::No => write!(f, "No"),
 | 
			
		||||
            VoteType::Abstain => write!(f, "Abstain"),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents a governance proposal in the system
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct Proposal {
 | 
			
		||||
    /// Unique identifier for the proposal
 | 
			
		||||
    pub id: String,
 | 
			
		||||
    /// User ID of the proposal creator
 | 
			
		||||
    pub creator_id: i32,
 | 
			
		||||
    /// Name of the proposal creator
 | 
			
		||||
    pub creator_name: String,
 | 
			
		||||
    /// Title of the proposal
 | 
			
		||||
    pub title: String,
 | 
			
		||||
    /// Detailed description of the proposal
 | 
			
		||||
    pub description: String,
 | 
			
		||||
    /// Current status of the proposal
 | 
			
		||||
    pub status: ProposalStatus,
 | 
			
		||||
    /// Date and time when the proposal was created
 | 
			
		||||
    pub created_at: DateTime<Utc>,
 | 
			
		||||
    /// Date and time when the proposal was last updated
 | 
			
		||||
    pub updated_at: DateTime<Utc>,
 | 
			
		||||
    /// Date and time when voting starts
 | 
			
		||||
    pub voting_starts_at: Option<DateTime<Utc>>,
 | 
			
		||||
    /// Date and time when voting ends
 | 
			
		||||
    pub voting_ends_at: Option<DateTime<Utc>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Proposal {
 | 
			
		||||
    /// Creates a new proposal
 | 
			
		||||
    pub fn new(creator_id: i32, creator_name: String, title: String, description: String) -> Self {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
        Self {
 | 
			
		||||
            id: Uuid::new_v4().to_string(),
 | 
			
		||||
            creator_id,
 | 
			
		||||
            creator_name,
 | 
			
		||||
            title,
 | 
			
		||||
            description,
 | 
			
		||||
            status: ProposalStatus::Draft,
 | 
			
		||||
            created_at: now,
 | 
			
		||||
            updated_at: now,
 | 
			
		||||
            voting_starts_at: None,
 | 
			
		||||
            voting_ends_at: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Updates the proposal status
 | 
			
		||||
    pub fn update_status(&mut self, status: ProposalStatus) {
 | 
			
		||||
        self.status = status;
 | 
			
		||||
        self.updated_at = Utc::now();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Sets the voting period for the proposal
 | 
			
		||||
    pub fn set_voting_period(&mut self, starts_at: DateTime<Utc>, ends_at: DateTime<Utc>) {
 | 
			
		||||
        self.voting_starts_at = Some(starts_at);
 | 
			
		||||
        self.voting_ends_at = Some(ends_at);
 | 
			
		||||
        self.updated_at = Utc::now();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Activates the proposal for voting
 | 
			
		||||
    pub fn activate(&mut self) {
 | 
			
		||||
        self.status = ProposalStatus::Active;
 | 
			
		||||
        self.updated_at = Utc::now();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Cancels the proposal
 | 
			
		||||
    pub fn cancel(&mut self) {
 | 
			
		||||
        self.status = ProposalStatus::Cancelled;
 | 
			
		||||
        self.updated_at = Utc::now();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents a vote cast on a proposal
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct Vote {
 | 
			
		||||
    /// Unique identifier for the vote
 | 
			
		||||
    pub id: String,
 | 
			
		||||
    /// ID of the proposal being voted on
 | 
			
		||||
    pub proposal_id: String,
 | 
			
		||||
    /// User ID of the voter
 | 
			
		||||
    pub voter_id: i32,
 | 
			
		||||
    /// Name of the voter
 | 
			
		||||
    pub voter_name: String,
 | 
			
		||||
    /// Type of vote cast
 | 
			
		||||
    pub vote_type: VoteType,
 | 
			
		||||
    /// Optional comment explaining the vote
 | 
			
		||||
    pub comment: Option<String>,
 | 
			
		||||
    /// Date and time when the vote was cast
 | 
			
		||||
    pub created_at: DateTime<Utc>,
 | 
			
		||||
    /// Date and time when the vote was last updated
 | 
			
		||||
    pub updated_at: DateTime<Utc>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Vote {
 | 
			
		||||
    /// Creates a new vote
 | 
			
		||||
    pub fn new(proposal_id: String, voter_id: i32, voter_name: String, vote_type: VoteType, comment: Option<String>) -> Self {
 | 
			
		||||
        let now = Utc::now();
 | 
			
		||||
        Self {
 | 
			
		||||
            id: Uuid::new_v4().to_string(),
 | 
			
		||||
            proposal_id,
 | 
			
		||||
            voter_id,
 | 
			
		||||
            voter_name,
 | 
			
		||||
            vote_type,
 | 
			
		||||
            comment,
 | 
			
		||||
            created_at: now,
 | 
			
		||||
            updated_at: now,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Updates the vote type
 | 
			
		||||
    pub fn update_vote(&mut self, vote_type: VoteType, comment: Option<String>) {
 | 
			
		||||
        self.vote_type = vote_type;
 | 
			
		||||
        self.comment = comment;
 | 
			
		||||
        self.updated_at = Utc::now();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents a filter for searching proposals
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct ProposalFilter {
 | 
			
		||||
    /// Filter by proposal status
 | 
			
		||||
    pub status: Option<String>,
 | 
			
		||||
    /// Filter by creator ID
 | 
			
		||||
    pub creator_id: Option<i32>,
 | 
			
		||||
    /// Search term for title and description
 | 
			
		||||
    pub search: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for ProposalFilter {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            status: None,
 | 
			
		||||
            creator_id: None,
 | 
			
		||||
            search: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents the voting results for a proposal
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct VotingResults {
 | 
			
		||||
    /// Proposal ID
 | 
			
		||||
    pub proposal_id: String,
 | 
			
		||||
    /// Number of yes votes
 | 
			
		||||
    pub yes_count: usize,
 | 
			
		||||
    /// Number of no votes
 | 
			
		||||
    pub no_count: usize,
 | 
			
		||||
    /// Number of abstain votes
 | 
			
		||||
    pub abstain_count: usize,
 | 
			
		||||
    /// Total number of votes
 | 
			
		||||
    pub total_votes: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl VotingResults {
 | 
			
		||||
    /// Creates a new empty voting results object
 | 
			
		||||
    pub fn new(proposal_id: String) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            proposal_id,
 | 
			
		||||
            yes_count: 0,
 | 
			
		||||
            no_count: 0,
 | 
			
		||||
            abstain_count: 0,
 | 
			
		||||
            total_votes: 0,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Adds a vote to the results
 | 
			
		||||
    pub fn add_vote(&mut self, vote_type: &VoteType) {
 | 
			
		||||
        match vote_type {
 | 
			
		||||
            VoteType::Yes => self.yes_count += 1,
 | 
			
		||||
            VoteType::No => self.no_count += 1,
 | 
			
		||||
            VoteType::Abstain => self.abstain_count += 1,
 | 
			
		||||
        }
 | 
			
		||||
        self.total_votes += 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Calculates the percentage of yes votes
 | 
			
		||||
    pub fn yes_percentage(&self) -> f64 {
 | 
			
		||||
        if self.total_votes == 0 {
 | 
			
		||||
            return 0.0;
 | 
			
		||||
        }
 | 
			
		||||
        (self.yes_count as f64 / self.total_votes as f64) * 100.0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Calculates the percentage of no votes
 | 
			
		||||
    pub fn no_percentage(&self) -> f64 {
 | 
			
		||||
        if self.total_votes == 0 {
 | 
			
		||||
            return 0.0;
 | 
			
		||||
        }
 | 
			
		||||
        (self.no_count as f64 / self.total_votes as f64) * 100.0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Calculates the percentage of abstain votes
 | 
			
		||||
    pub fn abstain_percentage(&self) -> f64 {
 | 
			
		||||
        if self.total_votes == 0 {
 | 
			
		||||
            return 0.0;
 | 
			
		||||
        }
 | 
			
		||||
        (self.abstain_count as f64 / self.total_votes as f64) * 100.0
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,8 +2,12 @@
 | 
			
		||||
pub mod user;
 | 
			
		||||
pub mod ticket;
 | 
			
		||||
pub mod calendar;
 | 
			
		||||
pub mod governance;
 | 
			
		||||
pub mod flow;
 | 
			
		||||
 | 
			
		||||
// Re-export models for easier imports
 | 
			
		||||
pub use user::User;
 | 
			
		||||
pub use ticket::{Ticket, TicketComment, TicketStatus, TicketPriority, TicketFilter};
 | 
			
		||||
pub use calendar::{CalendarEvent, CalendarViewMode};
 | 
			
		||||
pub use ticket::{Ticket, TicketComment, TicketStatus, TicketPriority};
 | 
			
		||||
pub use calendar::{CalendarEvent, CalendarViewMode};
 | 
			
		||||
pub use governance::{Proposal, ProposalStatus, Vote, VoteType, VotingResults, ProposalFilter};
 | 
			
		||||
pub use flow::{Flow, FlowStep, FlowLog, FlowStatus, FlowType, StepStatus, FlowFilter, FlowStatistics};
 | 
			
		||||
@@ -4,6 +4,8 @@ use crate::controllers::home::HomeController;
 | 
			
		||||
use crate::controllers::auth::AuthController;
 | 
			
		||||
use crate::controllers::ticket::TicketController;
 | 
			
		||||
use crate::controllers::calendar::CalendarController;
 | 
			
		||||
use crate::controllers::governance::GovernanceController;
 | 
			
		||||
use crate::controllers::flow::FlowController;
 | 
			
		||||
use crate::middleware::JwtAuth;
 | 
			
		||||
use crate::SESSION_KEY;
 | 
			
		||||
 | 
			
		||||
@@ -52,6 +54,29 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) {
 | 
			
		||||
            .route("/calendar/events/new", web::get().to(CalendarController::new_event))
 | 
			
		||||
            .route("/calendar/events", web::post().to(CalendarController::create_event))
 | 
			
		||||
            .route("/calendar/events/{id}/delete", web::post().to(CalendarController::delete_event))
 | 
			
		||||
            
 | 
			
		||||
            // Governance routes
 | 
			
		||||
            .route("/governance", web::get().to(GovernanceController::index))
 | 
			
		||||
            .route("/governance/proposals", web::get().to(GovernanceController::proposals))
 | 
			
		||||
            .route("/governance/proposals/{id}", web::get().to(GovernanceController::proposal_detail))
 | 
			
		||||
            .route("/governance/proposals/{id}/vote", web::post().to(GovernanceController::submit_vote))
 | 
			
		||||
            .route("/governance/create-proposal", web::get().to(GovernanceController::create_proposal_form))
 | 
			
		||||
            .route("/governance/create-proposal", web::post().to(GovernanceController::submit_proposal))
 | 
			
		||||
            .route("/governance/my-votes", web::get().to(GovernanceController::my_votes))
 | 
			
		||||
            
 | 
			
		||||
            // Flow routes
 | 
			
		||||
            .service(
 | 
			
		||||
                web::scope("/flows")
 | 
			
		||||
                    .route("", web::get().to(FlowController::index))
 | 
			
		||||
                    .route("/list", web::get().to(FlowController::list_flows))
 | 
			
		||||
                    .route("/{id}", web::get().to(FlowController::flow_detail))
 | 
			
		||||
                    .route("/{id}/advance", web::post().to(FlowController::advance_flow_step))
 | 
			
		||||
                    .route("/{id}/stuck", web::post().to(FlowController::mark_flow_step_stuck))
 | 
			
		||||
                    .route("/{id}/step/{step_id}/log", web::post().to(FlowController::add_log_to_flow_step))
 | 
			
		||||
                    .route("/create", web::get().to(FlowController::create_flow_form))
 | 
			
		||||
                    .route("/create", web::post().to(FlowController::create_flow))
 | 
			
		||||
                    .route("/my-flows", web::get().to(FlowController::my_flows))
 | 
			
		||||
            )
 | 
			
		||||
    );
 | 
			
		||||
    
 | 
			
		||||
    // Keep the /protected scope for any future routes that should be under that path
 | 
			
		||||
 
 | 
			
		||||
@@ -6,86 +6,158 @@
 | 
			
		||||
    <title>{% block title %}Actix MVC App{% endblock %}</title>
 | 
			
		||||
    <link rel="stylesheet" href="/static/css/bootstrap.min.css">
 | 
			
		||||
    <link rel="stylesheet" href="https://unpkg.com/unpoly@3.7.2/unpoly.min.css">
 | 
			
		||||
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
 | 
			
		||||
    <style>
 | 
			
		||||
        /* Minimal custom CSS that can't be achieved with Bootstrap classes */
 | 
			
		||||
        @media (min-width: 768px) {
 | 
			
		||||
            .sidebar {
 | 
			
		||||
                width: 280px;
 | 
			
		||||
                position: fixed;
 | 
			
		||||
                height: 100vh;
 | 
			
		||||
            }
 | 
			
		||||
            .main-content {
 | 
			
		||||
                margin-left: 280px;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        @media (max-width: 767.98px) {
 | 
			
		||||
            .sidebar {
 | 
			
		||||
                width: 280px;
 | 
			
		||||
                position: fixed;
 | 
			
		||||
                height: 100vh;
 | 
			
		||||
                left: -280px;
 | 
			
		||||
                transition: left 0.3s ease;
 | 
			
		||||
                z-index: 1030;
 | 
			
		||||
            }
 | 
			
		||||
            .sidebar.show {
 | 
			
		||||
                left: 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
    {% block extra_css %}{% endblock %}
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
 | 
			
		||||
        <div class="container">
 | 
			
		||||
            <a class="navbar-brand" href="/">Actix MVC App</a>
 | 
			
		||||
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
 | 
			
		||||
                <span class="navbar-toggler-icon"></span>
 | 
			
		||||
            </button>
 | 
			
		||||
            <div class="collapse navbar-collapse" id="navbarNav">
 | 
			
		||||
                <ul class="navbar-nav me-auto">
 | 
			
		||||
                    <li class="nav-item">
 | 
			
		||||
                        <a class="nav-link {% if active_page == 'home' %}active{% endif %}" href="/">Home</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="nav-item">
 | 
			
		||||
                        <a class="nav-link {% if active_page == 'about' %}active{% endif %}" href="/about">About</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="nav-item">
 | 
			
		||||
                        <a class="nav-link {% if active_page == 'contact' %}active{% endif %}" href="/contact">Contact</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="nav-item">
 | 
			
		||||
                        <a class="nav-link {% if active_page == 'tickets' %}active{% endif %}" href="/tickets">Support Tickets</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="nav-item">
 | 
			
		||||
                        <a class="nav-link {% if active_page == 'editor' %}active{% endif %}" href="/editor">Markdown Editor</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="nav-item">
 | 
			
		||||
                        <a class="nav-link {% if active_page == 'calendar' %}active{% endif %}" href="/calendar">Calendar</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                </ul>
 | 
			
		||||
                <ul class="navbar-nav ms-auto">
 | 
			
		||||
                    {% if user and user.id %}
 | 
			
		||||
                    <li class="nav-item dropdown">
 | 
			
		||||
                        <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
 | 
			
		||||
                            {{ user.name }}
 | 
			
		||||
                        </a>
 | 
			
		||||
                        <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
 | 
			
		||||
                            <li><a class="dropdown-item" href="/tickets/new">New Ticket</a></li>
 | 
			
		||||
                            <li><a class="dropdown-item" href="/tickets/my">My Tickets</a></li>
 | 
			
		||||
                            {% if user.role == "Admin" %}
 | 
			
		||||
                            <li><a class="dropdown-item" href="/admin">Admin Panel</a></li>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                            <li><hr class="dropdown-divider"></li>
 | 
			
		||||
                            <li><a class="dropdown-item" href="/logout">Logout</a></li>
 | 
			
		||||
                        </ul>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                    <li class="nav-item">
 | 
			
		||||
                        <a class="nav-link {% if active_page == 'login' %}active{% endif %}" href="/login">Login</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li class="nav-item">
 | 
			
		||||
                        <a class="nav-link {% if active_page == 'register' %}active{% endif %}" href="/register">Register</a>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </ul>
 | 
			
		||||
            </div>
 | 
			
		||||
<body class="d-flex flex-column min-vh-100">
 | 
			
		||||
    <!-- Sidebar -->
 | 
			
		||||
    <div class="sidebar bg-light shadow-sm border-end" id="sidebar">
 | 
			
		||||
        <div class="bg-dark text-white p-3">
 | 
			
		||||
            <h3 class="mb-0">Actix MVC App</h3>
 | 
			
		||||
        </div>
 | 
			
		||||
    </nav>
 | 
			
		||||
        <div class="py-3">
 | 
			
		||||
            <ul class="nav flex-column">
 | 
			
		||||
                <li class="nav-item">
 | 
			
		||||
                    <a class="nav-link d-flex align-items-center ps-3 py-2 {% if active_page == 'home' %}active fw-bold border-start border-4 border-primary bg-light{% endif %}" href="/">
 | 
			
		||||
                        <i class="bi bi-house-door me-2"></i> Home
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="nav-item">
 | 
			
		||||
                    <a class="nav-link d-flex align-items-center ps-3 py-2 {% if active_page == 'tickets' %}active fw-bold border-start border-4 border-primary bg-light{% endif %}" href="/tickets">
 | 
			
		||||
                        <i class="bi bi-ticket-perforated me-2"></i> Support Tickets
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="nav-item">
 | 
			
		||||
                    <a class="nav-link d-flex align-items-center ps-3 py-2 {% if active_page == 'governance' %}active fw-bold border-start border-4 border-primary bg-light{% endif %}" href="/governance">
 | 
			
		||||
                        <i class="bi bi-people me-2"></i> Governance
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="nav-item">
 | 
			
		||||
                    <a class="nav-link d-flex align-items-center ps-3 py-2 {% if active_page == 'flows' %}active fw-bold border-start border-4 border-primary bg-light{% endif %}" href="/flows">
 | 
			
		||||
                        <i class="bi bi-diagram-3 me-2"></i> Flows
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="nav-item">
 | 
			
		||||
                    <a class="nav-link d-flex align-items-center ps-3 py-2 {% if active_page == 'editor' %}active fw-bold border-start border-4 border-primary bg-light{% endif %}" href="/editor">
 | 
			
		||||
                        <i class="bi bi-markdown me-2"></i> Markdown Editor
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="nav-item">
 | 
			
		||||
                    <a class="nav-link d-flex align-items-center ps-3 py-2 {% if active_page == 'calendar' %}active fw-bold border-start border-4 border-primary bg-light{% endif %}" href="/calendar">
 | 
			
		||||
                        <i class="bi bi-calendar3 me-2"></i> Calendar
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="nav-item">
 | 
			
		||||
                    <a class="nav-link d-flex align-items-center ps-3 py-2 {% if active_page == 'about' %}active fw-bold border-start border-4 border-primary bg-light{% endif %}" href="/about">
 | 
			
		||||
                        <i class="bi bi-info-circle me-2"></i> About
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="nav-item">
 | 
			
		||||
                    <a class="nav-link d-flex align-items-center ps-3 py-2 {% if active_page == 'contact' %}active fw-bold border-start border-4 border-primary bg-light{% endif %}" href="/contact">
 | 
			
		||||
                        <i class="bi bi-envelope me-2"></i> Contact
 | 
			
		||||
                    </a>
 | 
			
		||||
                </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <main class="container py-4">
 | 
			
		||||
        {% block content %}{% endblock %}
 | 
			
		||||
    </main>
 | 
			
		||||
 | 
			
		||||
    <footer class="bg-dark text-white py-4 mt-5">
 | 
			
		||||
        <div class="container">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <div class="col-md-6">
 | 
			
		||||
                    <h5>Actix MVC App</h5>
 | 
			
		||||
                    <p>A Rust web application using Actix Web, Tera templates, and Bootstrap.</p>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-md-6 text-md-end">
 | 
			
		||||
                    <p>© {{ now(year=true) }} Actix MVC App. All rights reserved.</p>
 | 
			
		||||
    <!-- Main Content -->
 | 
			
		||||
    <div class="main-content d-flex flex-column min-vh-100">
 | 
			
		||||
        <!-- Top Navbar -->
 | 
			
		||||
        <nav class="navbar navbar-dark bg-dark">
 | 
			
		||||
            <div class="container-fluid">
 | 
			
		||||
                <button class="navbar-toggler d-md-none" type="button" id="sidebarToggle" aria-label="Toggle navigation">
 | 
			
		||||
                    <i class="bi bi-list"></i>
 | 
			
		||||
                </button>
 | 
			
		||||
                <a class="navbar-brand d-md-none" href="/">Actix MVC App</a>
 | 
			
		||||
                <div class="ms-auto">
 | 
			
		||||
                    <ul class="navbar-nav flex-row">
 | 
			
		||||
                        {% if user and user.id %}
 | 
			
		||||
                        <li class="nav-item dropdown">
 | 
			
		||||
                            <a class="nav-link dropdown-toggle text-white" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
 | 
			
		||||
                                <i class="bi bi-person-circle"></i> {{ user.name }}
 | 
			
		||||
                            </a>
 | 
			
		||||
                            <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
 | 
			
		||||
                                <li><a class="dropdown-item" href="/tickets/new">New Ticket</a></li>
 | 
			
		||||
                                <li><a class="dropdown-item" href="/my-tickets">My Tickets</a></li>
 | 
			
		||||
                                <li><a class="dropdown-item" href="/governance/my-votes">My Votes</a></li>
 | 
			
		||||
                                {% if user.role == "Admin" %}
 | 
			
		||||
                                <li><a class="dropdown-item" href="/admin">Admin Panel</a></li>
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                                <li><hr class="dropdown-divider"></li>
 | 
			
		||||
                                <li><a class="dropdown-item" href="/logout">Logout</a></li>
 | 
			
		||||
                            </ul>
 | 
			
		||||
                        </li>
 | 
			
		||||
                        {% else %}
 | 
			
		||||
                        <li class="nav-item me-2">
 | 
			
		||||
                            <a class="nav-link text-white {% if active_page == 'login' %}active{% endif %}" href="/login">Login</a>
 | 
			
		||||
                        </li>
 | 
			
		||||
                        <li class="nav-item">
 | 
			
		||||
                            <a class="nav-link text-white {% if active_page == 'register' %}active{% endif %}" href="/register">Register</a>
 | 
			
		||||
                        </li>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    </ul>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </footer>
 | 
			
		||||
        </nav>
 | 
			
		||||
 | 
			
		||||
        <!-- Page Content -->
 | 
			
		||||
        <main class="container py-4 flex-grow-1">
 | 
			
		||||
            {% block content %}{% endblock %}
 | 
			
		||||
        </main>
 | 
			
		||||
 | 
			
		||||
        <!-- Footer -->
 | 
			
		||||
        <footer class="bg-dark text-white py-4 mt-auto">
 | 
			
		||||
            <div class="container">
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col-md-6">
 | 
			
		||||
                        <h5>Actix MVC App</h5>
 | 
			
		||||
                        <p>A Rust web application using Actix Web, Tera templates, and Bootstrap.</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col-md-6 text-md-end">
 | 
			
		||||
                        <p>© {{ now(year=true) }} Actix MVC App. All rights reserved.</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </footer>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <script src="/static/js/bootstrap.bundle.min.js"></script>
 | 
			
		||||
    <script src="https://unpkg.com/unpoly@3.7.2/unpoly.min.js"></script>
 | 
			
		||||
    <script src="https://unpkg.com/unpoly@3.7.2/unpoly-bootstrap5.min.js"></script>
 | 
			
		||||
    <script>
 | 
			
		||||
        // Toggle sidebar on mobile
 | 
			
		||||
        document.getElementById('sidebarToggle').addEventListener('click', function() {
 | 
			
		||||
            document.getElementById('sidebar').classList.toggle('show');
 | 
			
		||||
        });
 | 
			
		||||
    </script>
 | 
			
		||||
    {% block extra_js %}{% endblock %}
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										102
									
								
								actix_mvc_app/src/views/flows/create_flow.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								actix_mvc_app/src/views/flows/create_flow.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Create New Flow{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container">
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <nav aria-label="breadcrumb">
 | 
			
		||||
                <ol class="breadcrumb">
 | 
			
		||||
                    <li class="breadcrumb-item"><a href="/flows">Flows</a></li>
 | 
			
		||||
                    <li class="breadcrumb-item active" aria-current="page">Create New Flow</li>
 | 
			
		||||
                </ol>
 | 
			
		||||
            </nav>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <h1 class="display-5 mb-3">Create New Flow</h1>
 | 
			
		||||
            <p class="lead">Start a new workflow process by filling out the form below.</p>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="col-lg-8">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <form action="/flows/create" method="post">
 | 
			
		||||
                        <!-- Flow Information -->
 | 
			
		||||
                        <div class="mb-4">
 | 
			
		||||
                            <h5>Flow Information</h5>
 | 
			
		||||
                            <hr>
 | 
			
		||||
                            <div class="mb-3">
 | 
			
		||||
                                <label for="name" class="form-label">Flow Name</label>
 | 
			
		||||
                                <input type="text" class="form-control" id="name" name="name" required>
 | 
			
		||||
                                <div class="form-text">A descriptive name for the flow process.</div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="mb-3">
 | 
			
		||||
                                <label for="description" class="form-label">Description</label>
 | 
			
		||||
                                <textarea class="form-control" id="description" name="description" rows="3" required></textarea>
 | 
			
		||||
                                <div class="form-text">Detailed description of the flow's purpose and expected outcome.</div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="mb-3">
 | 
			
		||||
                                <label for="flow_type" class="form-label">Flow Type</label>
 | 
			
		||||
                                <select class="form-select" id="flow_type" name="flow_type" required>
 | 
			
		||||
                                    <option value="" selected disabled>Select a flow type</option>
 | 
			
		||||
                                    <option value="CompanyRegistration">Company Registration</option>
 | 
			
		||||
                                    <option value="UserOnboarding">User Onboarding</option>
 | 
			
		||||
                                    <option value="ServiceActivation">Service Activation</option>
 | 
			
		||||
                                    <option value="PaymentProcessing">Payment Processing</option>
 | 
			
		||||
                                </select>
 | 
			
		||||
                                <div class="form-text">The type of workflow process.</div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- Flow Steps -->
 | 
			
		||||
                        <div class="mb-4">
 | 
			
		||||
                            <h5>Flow Steps</h5>
 | 
			
		||||
                            <hr>
 | 
			
		||||
                            <div class="alert alert-info">
 | 
			
		||||
                                <i class="bi bi-info-circle me-2"></i> Steps will be configured after creating the flow.
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- Submit Button -->
 | 
			
		||||
                        <div class="d-grid gap-2 d-md-flex justify-content-md-end">
 | 
			
		||||
                            <a href="/flows" class="btn btn-outline-secondary me-md-2">Cancel</a>
 | 
			
		||||
                            <button type="submit" class="btn btn-primary">Create Flow</button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </form>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-lg-4">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Flow Types</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <h6>Company Registration</h6>
 | 
			
		||||
                        <p class="small text-muted">Process for registering a new company, including document submission, verification, and approval.</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <h6>User Onboarding</h6>
 | 
			
		||||
                        <p class="small text-muted">Process for onboarding new users to the platform, including account setup and verification.</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <h6>Service Activation</h6>
 | 
			
		||||
                        <p class="small text-muted">Process for activating a service, including subscription selection and payment processing.</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <h6>Payment Processing</h6>
 | 
			
		||||
                        <p class="small text-muted">Process for handling payments, including verification, processing, and receipt generation.</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										247
									
								
								actix_mvc_app/src/views/flows/flow_detail.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								actix_mvc_app/src/views/flows/flow_detail.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,247 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}{{ flow.name }} - Flow Details{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container">
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <nav aria-label="breadcrumb">
 | 
			
		||||
                <ol class="breadcrumb">
 | 
			
		||||
                    <li class="breadcrumb-item"><a href="/flows">Flows</a></li>
 | 
			
		||||
                    <li class="breadcrumb-item"><a href="/flows/list">All Flows</a></li>
 | 
			
		||||
                    <li class="breadcrumb-item active" aria-current="page">{{ flow.name }}</li>
 | 
			
		||||
                </ol>
 | 
			
		||||
            </nav>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- 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 %}
 | 
			
		||||
 | 
			
		||||
    <!-- Flow Overview -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-md-8">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header d-flex justify-content-between align-items-center">
 | 
			
		||||
                    <h4 class="mb-0">{{ flow.name }}</h4>
 | 
			
		||||
                    <span class="badge {% if flow.status == 'In Progress' %}bg-primary{% elif flow.status == 'Completed' %}bg-success{% elif flow.status == 'Stuck' %}bg-danger{% else %}bg-secondary{% endif %} p-2">
 | 
			
		||||
                        {{ flow.status }}
 | 
			
		||||
                    </span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        <h5>Description</h5>
 | 
			
		||||
                        <p>{{ flow.description }}</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="row mb-3">
 | 
			
		||||
                        <div class="col-md-6">
 | 
			
		||||
                            <h5>Details</h5>
 | 
			
		||||
                            <ul class="list-group list-group-flush">
 | 
			
		||||
                                <li class="list-group-item d-flex justify-content-between">
 | 
			
		||||
                                    <span>Flow Type:</span>
 | 
			
		||||
                                    <span class="fw-bold">{{ flow.flow_type }}</span>
 | 
			
		||||
                                </li>
 | 
			
		||||
                                <li class="list-group-item d-flex justify-content-between">
 | 
			
		||||
                                    <span>Owner:</span>
 | 
			
		||||
                                    <span class="fw-bold">{{ flow.owner_name }}</span>
 | 
			
		||||
                                </li>
 | 
			
		||||
                                <li class="list-group-item d-flex justify-content-between">
 | 
			
		||||
                                    <span>Created:</span>
 | 
			
		||||
                                    <span class="fw-bold">{{ flow.created_at | date(format="%Y-%m-%d %H:%M") }}</span>
 | 
			
		||||
                                </li>
 | 
			
		||||
                                <li class="list-group-item d-flex justify-content-between">
 | 
			
		||||
                                    <span>Last Updated:</span>
 | 
			
		||||
                                    <span class="fw-bold">{{ flow.updated_at | date(format="%Y-%m-%d %H:%M") }}</span>
 | 
			
		||||
                                </li>
 | 
			
		||||
                                {% if flow.completed_at %}
 | 
			
		||||
                                <li class="list-group-item d-flex justify-content-between">
 | 
			
		||||
                                    <span>Completed:</span>
 | 
			
		||||
                                    <span class="fw-bold">{{ flow.completed_at | date(format="%Y-%m-%d %H:%M") }}</span>
 | 
			
		||||
                                </li>
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                            </ul>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-md-6">
 | 
			
		||||
                            <h5>Progress</h5>
 | 
			
		||||
                            <div class="progress mb-3" style="height: 25px;">
 | 
			
		||||
                                <div class="progress-bar {% if flow.status == 'Completed' %}bg-success{% elif flow.status == 'Stuck' %}bg-danger{% else %}bg-primary{% endif %}" role="progressbar" 
 | 
			
		||||
                                    style="width: {{ flow.progress_percentage }}%;" 
 | 
			
		||||
                                    aria-valuenow="{{ flow.progress_percentage }}" 
 | 
			
		||||
                                    aria-valuemin="0" 
 | 
			
		||||
                                    aria-valuemax="100">
 | 
			
		||||
                                    {{ flow.progress_percentage }}%
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <p>
 | 
			
		||||
                                <strong>Current Step:</strong>
 | 
			
		||||
                                {% set current = flow.current_step %}
 | 
			
		||||
                                {% if current %}
 | 
			
		||||
                                    {{ current.name }}
 | 
			
		||||
                                {% else %}
 | 
			
		||||
                                    {% if flow.status == 'Completed' %}
 | 
			
		||||
                                        All steps completed
 | 
			
		||||
                                    {% elif flow.status == 'Cancelled' %}
 | 
			
		||||
                                        Flow cancelled
 | 
			
		||||
                                    {% else %}
 | 
			
		||||
                                        No active step
 | 
			
		||||
                                    {% endif %}
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-4">
 | 
			
		||||
            <div class="card mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Actions</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% if flow.status == 'In Progress' %}
 | 
			
		||||
                    <div class="d-grid gap-2 mb-3">
 | 
			
		||||
                        <form action="/flows/{{ flow.id }}/advance" method="post" id="advance">
 | 
			
		||||
                            <button type="submit" class="btn btn-success w-100">
 | 
			
		||||
                                <i class="bi bi-arrow-right-circle me-1"></i> Advance to Next Step
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </form>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="d-grid gap-2">
 | 
			
		||||
                        <button type="button" class="btn btn-warning w-100" data-bs-toggle="modal" data-bs-target="#markStuckModal">
 | 
			
		||||
                            <i class="bi bi-exclamation-triangle me-1"></i> Mark as Stuck
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% elif flow.status == 'Stuck' %}
 | 
			
		||||
                    <div class="d-grid gap-2">
 | 
			
		||||
                        <form action="/flows/{{ flow.id }}/advance" method="post">
 | 
			
		||||
                            <button type="submit" class="btn btn-success w-100">
 | 
			
		||||
                                <i class="bi bi-arrow-right-circle me-1"></i> Resume Flow
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </form>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                    <p class="text-center text-muted">No actions available for {{ flow.status | lower }} flows.</p>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            {% if flow.steps | length > 0 %}
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header d-flex justify-content-between align-items-center">
 | 
			
		||||
                    <h5 class="mb-0">Add Log to Current Step</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% set current = flow.current_step %}
 | 
			
		||||
                    {% if current %}
 | 
			
		||||
                        <form action="/flows/{{ flow.id }}/step/{{ current.id }}/log" method="post">
 | 
			
		||||
                            <div class="mb-3">
 | 
			
		||||
                                <label for="message" class="form-label">Log Message</label>
 | 
			
		||||
                                <textarea class="form-control" id="message" name="message" rows="3" required></textarea>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="d-grid">
 | 
			
		||||
                                <button type="submit" class="btn btn-primary">
 | 
			
		||||
                                    <i class="bi bi-plus-circle me-1"></i> Add Log Entry
 | 
			
		||||
                                </button>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </form>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Flow Steps -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Flow Steps</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="flow-steps">
 | 
			
		||||
                        {% for step in flow.steps %}
 | 
			
		||||
                        <div class="flow-step mb-4">
 | 
			
		||||
                            <div class="card {% if step.status == 'In Progress' %}border-primary{% elif step.status == 'Completed' %}border-success{% elif step.status == 'Stuck' %}border-danger{% else %}border-secondary{% endif %}">
 | 
			
		||||
                                <div class="card-header d-flex justify-content-between align-items-center {% if step.status == 'In Progress' %}bg-primary text-white{% elif step.status == 'Completed' %}bg-success text-white{% elif step.status == 'Stuck' %}bg-danger text-white{% else %}{% endif %}">
 | 
			
		||||
                                    <h5 class="mb-0">Step {{ step.order + 1 }}: {{ step.name }}</h5>
 | 
			
		||||
                                    <span class="badge {% if step.status == 'In Progress' %}bg-light text-primary{% elif step.status == 'Completed' %}bg-light text-success{% elif step.status == 'Stuck' %}bg-light text-danger{% else %}bg-light text-secondary{% endif %}">
 | 
			
		||||
                                        {{ step.status }}
 | 
			
		||||
                                    </span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="card-body">
 | 
			
		||||
                                    <p>{{ step.description }}</p>
 | 
			
		||||
                                    <div class="d-flex justify-content-between small text-muted mb-3">
 | 
			
		||||
                                        {% if step.started_at %}
 | 
			
		||||
                                        <span>Started: {{ step.started_at | date(format="%Y-%m-%d %H:%M") }}</span>
 | 
			
		||||
                                        {% else %}
 | 
			
		||||
                                        <span>Not started yet</span>
 | 
			
		||||
                                        {% endif %}
 | 
			
		||||
                                        
 | 
			
		||||
                                        {% if step.completed_at %}
 | 
			
		||||
                                        <span>Completed: {{ step.completed_at | date(format="%Y-%m-%d %H:%M") }}</span>
 | 
			
		||||
                                        {% endif %}
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    
 | 
			
		||||
                                    {% if step.logs|length > 0 %}
 | 
			
		||||
                                    <h6>Logs</h6>
 | 
			
		||||
                                    <div class="logs-container border rounded p-2 bg-light" style="max-height: 200px; overflow-y: auto;">
 | 
			
		||||
                                        {% for log in step.logs %}
 | 
			
		||||
                                        <div class="log-entry mb-2 pb-2 {% if not loop.last %}border-bottom{% endif %}">
 | 
			
		||||
                                            <div class="d-flex justify-content-between">
 | 
			
		||||
                                                <span class="fw-bold">{{ log.timestamp | date(format="%Y-%m-%d %H:%M:%S") }}</span>
 | 
			
		||||
                                                <span class="text-muted small">ID: {{ log.id }}</span>
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                            <p class="mb-0">{{ log.message }}</p>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                        {% endfor %}
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    {% else %}
 | 
			
		||||
                                    <p class="text-muted">No logs for this step.</p>
 | 
			
		||||
                                    {% endif %}
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Mark as Stuck Modal -->
 | 
			
		||||
    <div class="modal fade" id="markStuckModal" tabindex="-1" aria-labelledby="markStuckModalLabel" aria-hidden="true">
 | 
			
		||||
        <div class="modal-dialog">
 | 
			
		||||
            <div class="modal-content">
 | 
			
		||||
                <form action="/flows/{{ flow.id }}/stuck" method="post">
 | 
			
		||||
                    <div class="modal-header">
 | 
			
		||||
                        <h5 class="modal-title" id="markStuckModalLabel">Mark Flow as Stuck</h5>
 | 
			
		||||
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="modal-body">
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="reason" class="form-label">Reason</label>
 | 
			
		||||
                            <textarea class="form-control" id="reason" name="reason" rows="3" required></textarea>
 | 
			
		||||
                            <div class="form-text">Please provide a detailed explanation of why this flow is stuck.</div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="modal-footer">
 | 
			
		||||
                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
 | 
			
		||||
                        <button type="submit" class="btn btn-warning">Mark as Stuck</button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </form>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										153
									
								
								actix_mvc_app/src/views/flows/flows.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								actix_mvc_app/src/views/flows/flows.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,153 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}All Flows{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container">
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <nav aria-label="breadcrumb">
 | 
			
		||||
                <ol class="breadcrumb">
 | 
			
		||||
                    <li class="breadcrumb-item"><a href="/flows">Flows</a></li>
 | 
			
		||||
                    <li class="breadcrumb-item active" aria-current="page">All Flows</li>
 | 
			
		||||
                </ol>
 | 
			
		||||
            </nav>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-md-8">
 | 
			
		||||
            <h1 class="display-5 mb-0">All Flows</h1>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-4 text-md-end">
 | 
			
		||||
            <a href="/flows/create" class="btn btn-primary">
 | 
			
		||||
                <i class="bi bi-plus-circle me-1"></i> Create New Flow
 | 
			
		||||
            </a>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Filter Controls -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <form class="row g-3" action="/flows/list" method="get">
 | 
			
		||||
                        <div class="col-md-4">
 | 
			
		||||
                            <label for="status" class="form-label">Status</label>
 | 
			
		||||
                            <select class="form-select" id="status" name="status">
 | 
			
		||||
                                <option value="all" selected>All</option>
 | 
			
		||||
                                <option value="in_progress">In Progress</option>
 | 
			
		||||
                                <option value="completed">Completed</option>
 | 
			
		||||
                                <option value="stuck">Stuck</option>
 | 
			
		||||
                                <option value="cancelled">Cancelled</option>
 | 
			
		||||
                            </select>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-md-4">
 | 
			
		||||
                            <label for="type" class="form-label">Type</label>
 | 
			
		||||
                            <select class="form-select" id="type" name="type">
 | 
			
		||||
                                <option value="all" selected>All</option>
 | 
			
		||||
                                <option value="company_registration">Company Registration</option>
 | 
			
		||||
                                <option value="user_onboarding">User Onboarding</option>
 | 
			
		||||
                                <option value="service_activation">Service Activation</option>
 | 
			
		||||
                                <option value="payment_processing">Payment Processing</option>
 | 
			
		||||
                            </select>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-md-4">
 | 
			
		||||
                            <label for="search" class="form-label">Search</label>
 | 
			
		||||
                            <input type="text" class="form-control" id="search" name="search" placeholder="Search flows...">
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-12 text-end">
 | 
			
		||||
                            <button type="submit" class="btn btn-primary">
 | 
			
		||||
                                <i class="bi bi-filter me-1"></i> Apply Filters
 | 
			
		||||
                            </button>
 | 
			
		||||
                            <a href="/flows/list" class="btn btn-outline-secondary">
 | 
			
		||||
                                <i class="bi bi-x-circle me-1"></i> Clear Filters
 | 
			
		||||
                            </a>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </form>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Flows Table -->
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% if flows|length > 0 %}
 | 
			
		||||
                    <div class="table-responsive">
 | 
			
		||||
                        <table class="table table-hover">
 | 
			
		||||
                            <thead>
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <th>Flow Name</th>
 | 
			
		||||
                                    <th>Type</th>
 | 
			
		||||
                                    <th>Status</th>
 | 
			
		||||
                                    <th>Owner</th>
 | 
			
		||||
                                    <th>Progress</th>
 | 
			
		||||
                                    <th>Created</th>
 | 
			
		||||
                                    <th>Updated</th>
 | 
			
		||||
                                    <th>Current Step</th>
 | 
			
		||||
                                    <th>Actions</th>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                            </thead>
 | 
			
		||||
                            <tbody>
 | 
			
		||||
                                {% for flow in flows %}
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <a href="/flows/{{ flow.id }}">{{ flow.name }}</a>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>{{ flow.flow_type }}</td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <span class="badge {% if flow.status == 'In Progress' %}bg-primary{% elif flow.status == 'Completed' %}bg-success{% elif flow.status == 'Stuck' %}bg-danger{% else %}bg-secondary{% endif %}">
 | 
			
		||||
                                            {{ flow.status }}
 | 
			
		||||
                                        </span>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>{{ flow.owner_name }}</td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <div class="progress mb-2" style="height: 20px;">
 | 
			
		||||
                                            <div class="progress-bar {% if flow.status == 'Completed' %}bg-success{% elif flow.status == 'Stuck' %}bg-danger{% else %}bg-primary{% endif %}" role="progressbar" style="width: {{ flow.progress_percentage }}%;" aria-valuenow="{{ flow.progress_percentage }}" aria-valuemin="0" aria-valuemax="100">{{ flow.progress_percentage }}%</div>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>{{ flow.created_at | date(format="%Y-%m-%d") }}</td>
 | 
			
		||||
                                    <td>{{ flow.updated_at | date(format="%Y-%m-%d") }}</td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        {% set current = flow.current_step %}
 | 
			
		||||
                                        {% if current %}
 | 
			
		||||
                                            {{ current.name }}
 | 
			
		||||
                                        {% else %}
 | 
			
		||||
                                            {% if flow.status == 'Completed' %}
 | 
			
		||||
                                                All steps completed
 | 
			
		||||
                                            {% elif flow.status == 'Cancelled' %}
 | 
			
		||||
                                                Flow cancelled
 | 
			
		||||
                                            {% else %}
 | 
			
		||||
                                                No active step
 | 
			
		||||
                                            {% endif %}
 | 
			
		||||
                                        {% endif %}
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <div class="btn-group">
 | 
			
		||||
                                            <a href="/flows/{{ flow.id }}" class="btn btn-sm btn-primary">
 | 
			
		||||
                                                <i class="bi bi-eye"></i>
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                            {% if flow.status == 'In Progress' %}
 | 
			
		||||
                                            <a href="/flows/{{ flow.id }}#advance" class="btn btn-sm btn-success">
 | 
			
		||||
                                                <i class="bi bi-arrow-right"></i>
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                            {% endif %}
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                                {% endfor %}
 | 
			
		||||
                            </tbody>
 | 
			
		||||
                        </table>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                    <p class="text-center">No flows found matching your criteria.</p>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										74
									
								
								actix_mvc_app/src/views/flows/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								actix_mvc_app/src/views/flows/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Flows Dashboard{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container">
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <h1 class="display-5 mb-3">Flows Dashboard</h1>
 | 
			
		||||
            <p class="lead">Track and manage workflow processes across the organization.</p>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Statistics Cards -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-md-3 mb-3">
 | 
			
		||||
            <div class="card text-white bg-primary h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h5 class="card-title">Total Flows</h5>
 | 
			
		||||
                    <p class="display-4">{{ stats.total_flows }}</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-3 mb-3">
 | 
			
		||||
            <div class="card text-white bg-success h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h5 class="card-title">In Progress</h5>
 | 
			
		||||
                    <p class="display-4">{{ stats.in_progress_flows }}</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-3 mb-3">
 | 
			
		||||
            <div class="card text-white bg-danger h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h5 class="card-title">Stuck</h5>
 | 
			
		||||
                    <p class="display-4">{{ stats.stuck_flows }}</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-3 mb-3">
 | 
			
		||||
            <div class="card text-white bg-info h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h5 class="card-title">Completed</h5>
 | 
			
		||||
                    <p class="display-4">{{ stats.completed_flows }}</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Quick Actions -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Quick Actions</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="d-flex flex-wrap gap-2">
 | 
			
		||||
                        <a href="/flows/create" class="btn btn-primary">
 | 
			
		||||
                            <i class="bi bi-plus-circle me-1"></i> Create New Flow
 | 
			
		||||
                        </a>
 | 
			
		||||
                        <a href="/flows/list" class="btn btn-outline-secondary">
 | 
			
		||||
                            <i class="bi bi-list me-1"></i> View All Flows
 | 
			
		||||
                        </a>
 | 
			
		||||
                        <a href="/flows/my-flows" class="btn btn-outline-secondary">
 | 
			
		||||
                            <i class="bi bi-person me-1"></i> My Flows
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										114
									
								
								actix_mvc_app/src/views/flows/my_flows.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								actix_mvc_app/src/views/flows/my_flows.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}My Flows{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container">
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <nav aria-label="breadcrumb">
 | 
			
		||||
                <ol class="breadcrumb">
 | 
			
		||||
                    <li class="breadcrumb-item"><a href="/flows">Flows</a></li>
 | 
			
		||||
                    <li class="breadcrumb-item active" aria-current="page">My Flows</li>
 | 
			
		||||
                </ol>
 | 
			
		||||
            </nav>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-md-8">
 | 
			
		||||
            <h1 class="display-5 mb-0">My Flows</h1>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-4 text-md-end">
 | 
			
		||||
            <a href="/flows/create" class="btn btn-primary">
 | 
			
		||||
                <i class="bi bi-plus-circle me-1"></i> Create New Flow
 | 
			
		||||
            </a>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Flows Table -->
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% if flows|length > 0 %}
 | 
			
		||||
                    <div class="table-responsive">
 | 
			
		||||
                        <table class="table table-hover">
 | 
			
		||||
                            <thead>
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <th>Flow Name</th>
 | 
			
		||||
                                    <th>Type</th>
 | 
			
		||||
                                    <th>Status</th>
 | 
			
		||||
                                    <th>Progress</th>
 | 
			
		||||
                                    <th>Current Step</th>
 | 
			
		||||
                                    <th>Created</th>
 | 
			
		||||
                                    <th>Updated</th>
 | 
			
		||||
                                    <th>Actions</th>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                            </thead>
 | 
			
		||||
                            <tbody>
 | 
			
		||||
                                {% for flow in flows %}
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <a href="/flows/{{ flow.id }}">{{ flow.name }}</a>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>{{ flow.flow_type }}</td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <span class="badge {% if flow.status == 'In Progress' %}bg-primary{% elif flow.status == 'Completed' %}bg-success{% elif flow.status == 'Stuck' %}bg-danger{% else %}bg-secondary{% endif %}">
 | 
			
		||||
                                            {{ flow.status }}
 | 
			
		||||
                                        </span>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <div class="progress mb-2" style="height: 20px;">
 | 
			
		||||
                                            <div class="progress-bar {% if flow.status == 'Completed' %}bg-success{% elif flow.status == 'Stuck' %}bg-danger{% else %}bg-primary{% endif %}" role="progressbar" style="width: {{ flow.progress_percentage }}%;" aria-valuenow="{{ flow.progress_percentage }}" aria-valuemin="0" aria-valuemax="100">{{ flow.progress_percentage }}%</div>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        {% set current = flow.current_step %}
 | 
			
		||||
                                        {% if current %}
 | 
			
		||||
                                            {{ current.name }}
 | 
			
		||||
                                        {% else %}
 | 
			
		||||
                                            {% if flow.status == 'Completed' %}
 | 
			
		||||
                                                All steps completed
 | 
			
		||||
                                            {% elif flow.status == 'Cancelled' %}
 | 
			
		||||
                                                Flow cancelled
 | 
			
		||||
                                            {% else %}
 | 
			
		||||
                                                No active step
 | 
			
		||||
                                            {% endif %}
 | 
			
		||||
                                        {% endif %}
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>{{ flow.created_at | date(format="%Y-%m-%d") }}</td>
 | 
			
		||||
                                    <td>{{ flow.updated_at | date(format="%Y-%m-%d") }}</td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <div class="btn-group">
 | 
			
		||||
                                            <a href="/flows/{{ flow.id }}" class="btn btn-sm btn-primary">
 | 
			
		||||
                                                <i class="bi bi-eye"></i>
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                            {% if flow.status == 'In Progress' %}
 | 
			
		||||
                                            <a href="/flows/{{ flow.id }}#advance" class="btn btn-sm btn-success">
 | 
			
		||||
                                                <i class="bi bi-arrow-right"></i>
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                            {% endif %}
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                                {% endfor %}
 | 
			
		||||
                            </tbody>
 | 
			
		||||
                        </table>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                    <div class="text-center py-5">
 | 
			
		||||
                        <i class="bi bi-diagram-3 display-1 text-muted"></i>
 | 
			
		||||
                        <h4 class="mt-3">You don't have any flows yet</h4>
 | 
			
		||||
                        <p class="text-muted">Create a new flow to get started with tracking your processes.</p>
 | 
			
		||||
                        <a href="/flows/create" class="btn btn-primary mt-2">
 | 
			
		||||
                            <i class="bi bi-plus-circle me-1"></i> Create New Flow
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										119
									
								
								actix_mvc_app/src/views/governance/create_proposal.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								actix_mvc_app/src/views/governance/create_proposal.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Create Proposal - Governance Dashboard{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container">
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <h1 class="display-5 mb-4">Create Governance Proposal</h1>
 | 
			
		||||
            <p class="lead">Submit a new proposal for the community to vote on.</p>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- 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>
 | 
			
		||||
 | 
			
		||||
    <!-- Proposal Form -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-md-8 mx-auto">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">New Proposal</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <form action="/governance/create" method="post">
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="title" class="form-label">Title</label>
 | 
			
		||||
                            <input type="text" class="form-control" id="title" name="title" required 
 | 
			
		||||
                                   placeholder="Enter a clear, concise title for your proposal">
 | 
			
		||||
                            <div class="form-text">Make it descriptive and specific</div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="description" class="form-label">Description</label>
 | 
			
		||||
                            <textarea class="form-control" id="description" name="description" rows="6" required
 | 
			
		||||
                                      placeholder="Provide a detailed description of your proposal..."></textarea>
 | 
			
		||||
                            <div class="form-text">Explain the purpose, benefits, and implementation details</div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        
 | 
			
		||||
                        <div class="row mb-3">
 | 
			
		||||
                            <div class="col-md-6">
 | 
			
		||||
                                <label for="voting_start_date" class="form-label">Voting Start Date</label>
 | 
			
		||||
                                <input type="date" class="form-control" id="voting_start_date" name="voting_start_date">
 | 
			
		||||
                                <div class="form-text">When should voting begin?</div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="col-md-6">
 | 
			
		||||
                                <label for="voting_end_date" class="form-label">Voting End Date</label>
 | 
			
		||||
                                <input type="date" class="form-control" id="voting_end_date" name="voting_end_date">
 | 
			
		||||
                                <div class="form-text">When should voting end?</div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <div class="form-check">
 | 
			
		||||
                                <input class="form-check-input" type="checkbox" id="draft" name="draft" value="true">
 | 
			
		||||
                                <label class="form-check-label" for="draft">
 | 
			
		||||
                                    Save as draft (not ready for voting yet)
 | 
			
		||||
                                </label>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        
 | 
			
		||||
                        <div class="d-grid gap-2">
 | 
			
		||||
                            <button type="submit" class="btn btn-primary">Submit Proposal</button>
 | 
			
		||||
                            <a href="/governance" class="btn btn-outline-secondary">Cancel</a>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </form>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
    <!-- Guidelines Card -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-md-8 mx-auto">
 | 
			
		||||
            <div class="card bg-light">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Proposal Guidelines</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <ul class="list-group list-group-flush">
 | 
			
		||||
                        <li class="list-group-item bg-transparent">
 | 
			
		||||
                            <strong>Be specific:</strong> Clearly state what you're proposing and why.
 | 
			
		||||
                        </li>
 | 
			
		||||
                        <li class="list-group-item bg-transparent">
 | 
			
		||||
                            <strong>Provide context:</strong> Explain the current situation and why change is needed.
 | 
			
		||||
                        </li>
 | 
			
		||||
                        <li class="list-group-item bg-transparent">
 | 
			
		||||
                            <strong>Consider implementation:</strong> Outline how your proposal could be implemented.
 | 
			
		||||
                        </li>
 | 
			
		||||
                        <li class="list-group-item bg-transparent">
 | 
			
		||||
                            <strong>Address concerns:</strong> Anticipate potential objections and address them.
 | 
			
		||||
                        </li>
 | 
			
		||||
                        <li class="list-group-item bg-transparent">
 | 
			
		||||
                            <strong>Be respectful:</strong> Focus on ideas, not individuals or groups.
 | 
			
		||||
                        </li>
 | 
			
		||||
                    </ul>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										164
									
								
								actix_mvc_app/src/views/governance/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								actix_mvc_app/src/views/governance/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,164 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Governance Dashboard - Actix MVC App{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container">
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <h1 class="display-5 mb-4">Governance Dashboard</h1>
 | 
			
		||||
            <p class="lead">Participate in the decision-making process by voting on proposals and creating new ones.</p>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Statistics Cards -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-md-3 mb-3">
 | 
			
		||||
            <div class="card text-white bg-primary h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h5 class="card-title">Total Proposals</h5>
 | 
			
		||||
                    <p class="card-text display-6">{{ stats.total_proposals }}</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-3 mb-3">
 | 
			
		||||
            <div class="card text-white bg-success h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h5 class="card-title">Active Proposals</h5>
 | 
			
		||||
                    <p class="card-text display-6">{{ stats.active_proposals }}</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-3 mb-3">
 | 
			
		||||
            <div class="card text-white bg-info h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h5 class="card-title">Total Votes</h5>
 | 
			
		||||
                    <p class="card-text display-6">{{ stats.total_votes }}</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-3 mb-3">
 | 
			
		||||
            <div class="card text-white bg-secondary h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h5 class="card-title">Participation Rate</h5>
 | 
			
		||||
                    <p class="card-text display-6">{{ stats.participation_rate }}%</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Navigation Tabs -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <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>
 | 
			
		||||
 | 
			
		||||
    <!-- Active Proposals Section -->
 | 
			
		||||
    <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">Active Proposals</h5>
 | 
			
		||||
                    <a href="/governance/proposals" class="btn btn-sm btn-outline-primary">View All</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>Voting Ends</th>
 | 
			
		||||
                                    <th>Actions</th>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                            </thead>
 | 
			
		||||
                            <tbody>
 | 
			
		||||
                                {% for proposal in proposals %}
 | 
			
		||||
                                    {% if proposal.status == "Active" %}
 | 
			
		||||
                                    <tr>
 | 
			
		||||
                                        <td>{{ proposal.title }}</td>
 | 
			
		||||
                                        <td>{{ proposal.creator_name }}</td>
 | 
			
		||||
                                        <td><span class="badge bg-success">{{ proposal.status }}</span></td>
 | 
			
		||||
                                        <td>{{ proposal.voting_ends_at | date(format="%Y-%m-%d") }}</td>
 | 
			
		||||
                                        <td>
 | 
			
		||||
                                            <a href="/governance/proposals/{{ proposal.id }}" class="btn btn-sm btn-primary">View</a>
 | 
			
		||||
                                        </td>
 | 
			
		||||
                                    </tr>
 | 
			
		||||
                                    {% endif %}
 | 
			
		||||
                                {% endfor %}
 | 
			
		||||
                            </tbody>
 | 
			
		||||
                        </table>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Recent Proposals Section -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Recent Proposals</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="row">
 | 
			
		||||
                        {% set count = 0 %}
 | 
			
		||||
                        {% for proposal in proposals %}
 | 
			
		||||
                            {% if count < 3 %}
 | 
			
		||||
                                <div class="col-md-4 mb-3">
 | 
			
		||||
                                    <div class="card h-100">
 | 
			
		||||
                                        <div class="card-body">
 | 
			
		||||
                                            <h5 class="card-title">{{ proposal.title }}</h5>
 | 
			
		||||
                                            <h6 class="card-subtitle mb-2 text-muted">By {{ proposal.creator_name }}</h6>
 | 
			
		||||
                                            <p class="card-text">{{ proposal.description | truncate(length=100) }}</p>
 | 
			
		||||
                                            <div class="d-flex justify-content-between align-items-center">
 | 
			
		||||
                                                <span class="badge {% if proposal.status == 'Active' %}bg-success{% elif proposal.status == 'Approved' %}bg-primary{% elif proposal.status == 'Rejected' %}bg-danger{% else %}bg-secondary{% endif %}">
 | 
			
		||||
                                                    {{ proposal.status }}
 | 
			
		||||
                                                </span>
 | 
			
		||||
                                                <a href="/governance/proposals/{{ proposal.id }}" class="btn btn-sm btn-outline-primary">View Details</a>
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                        <div class="card-footer text-muted">
 | 
			
		||||
                                            Created: {{ proposal.created_at | date(format="%Y-%m-%d") }}
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                {% set count = count + 1 %}
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Call to Action -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card bg-light">
 | 
			
		||||
                <div class="card-body text-center">
 | 
			
		||||
                    <h4 class="mb-3">Have an idea to improve our platform?</h4>
 | 
			
		||||
                    <p class="mb-4">Create a proposal and let the community vote on it.</p>
 | 
			
		||||
                    <a href="/governance/create" class="btn btn-primary">Create Proposal</a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										144
									
								
								actix_mvc_app/src/views/governance/my_votes.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								actix_mvc_app/src/views/governance/my_votes.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,144 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}My Votes - Governance Dashboard{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container">
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <h1 class="display-5 mb-4">My Votes</h1>
 | 
			
		||||
            <p class="lead">View all proposals you have voted on.</p>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- 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>
 | 
			
		||||
 | 
			
		||||
    <!-- My Votes List -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">My Voting History</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% if votes | length > 0 %}
 | 
			
		||||
                    <div class="table-responsive">
 | 
			
		||||
                        <table class="table table-hover">
 | 
			
		||||
                            <thead>
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <th>Proposal</th>
 | 
			
		||||
                                    <th>My Vote</th>
 | 
			
		||||
                                    <th>Status</th>
 | 
			
		||||
                                    <th>Voted On</th>
 | 
			
		||||
                                    <th>Actions</th>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                            </thead>
 | 
			
		||||
                            <tbody>
 | 
			
		||||
                                {% for vote, proposal in votes %}
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <td>{{ proposal.title }}</td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <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>
 | 
			
		||||
                                    <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>{{ vote.created_at | date(format="%Y-%m-%d") }}</td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <a href="/governance/proposals/{{ proposal.id }}" class="btn btn-sm btn-primary">View Proposal</a>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                                {% endfor %}
 | 
			
		||||
                            </tbody>
 | 
			
		||||
                        </table>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                    <div class="text-center py-5">
 | 
			
		||||
                        <i class="bi bi-clipboard-check fs-1 text-muted mb-3"></i>
 | 
			
		||||
                        <h5>You haven't voted on any proposals yet</h5>
 | 
			
		||||
                        <p class="text-muted">When you vote on proposals, they will appear here.</p>
 | 
			
		||||
                        <a href="/governance/proposals" class="btn btn-primary mt-3">Browse Proposals</a>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Voting Stats -->
 | 
			
		||||
    {% if votes | length > 0 %}
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-md-4 mb-3">
 | 
			
		||||
            <div class="card text-white bg-success h-100">
 | 
			
		||||
                <div class="card-body text-center">
 | 
			
		||||
                    <h5 class="card-title">Yes Votes</h5>
 | 
			
		||||
                    <p class="display-4">
 | 
			
		||||
                        {% set yes_count = 0 %}
 | 
			
		||||
                        {% for vote, proposal in votes %}
 | 
			
		||||
                            {% if vote.vote_type == 'Yes' %}
 | 
			
		||||
                                {% set yes_count = yes_count + 1 %}
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                        {{ yes_count }}
 | 
			
		||||
                    </p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-4 mb-3">
 | 
			
		||||
            <div class="card text-white bg-danger h-100">
 | 
			
		||||
                <div class="card-body text-center">
 | 
			
		||||
                    <h5 class="card-title">No Votes</h5>
 | 
			
		||||
                    <p class="display-4">
 | 
			
		||||
                        {% set no_count = 0 %}
 | 
			
		||||
                        {% for vote, proposal in votes %}
 | 
			
		||||
                            {% if vote.vote_type == 'No' %}
 | 
			
		||||
                                {% set no_count = no_count + 1 %}
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                        {{ no_count }}
 | 
			
		||||
                    </p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-4 mb-3">
 | 
			
		||||
            <div class="card text-white bg-secondary h-100">
 | 
			
		||||
                <div class="card-body text-center">
 | 
			
		||||
                    <h5 class="card-title">Abstain Votes</h5>
 | 
			
		||||
                    <p class="display-4">
 | 
			
		||||
                        {% set abstain_count = 0 %}
 | 
			
		||||
                        {% for vote, proposal in votes %}
 | 
			
		||||
                            {% if vote.vote_type == 'Abstain' %}
 | 
			
		||||
                                {% set abstain_count = abstain_count + 1 %}
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                        {{ abstain_count }}
 | 
			
		||||
                    </p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										189
									
								
								actix_mvc_app/src/views/governance/proposal_detail.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								actix_mvc_app/src/views/governance/proposal_detail.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,189 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}{{ proposal.title }} - Governance Proposal{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container">
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <nav aria-label="breadcrumb">
 | 
			
		||||
                <ol class="breadcrumb">
 | 
			
		||||
                    <li class="breadcrumb-item"><a href="/governance">Governance</a></li>
 | 
			
		||||
                    <li class="breadcrumb-item"><a href="/governance/proposals">Proposals</a></li>
 | 
			
		||||
                    <li class="breadcrumb-item active" aria-current="page">{{ proposal.title }}</li>
 | 
			
		||||
                </ol>
 | 
			
		||||
            </nav>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- 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 %}
 | 
			
		||||
 | 
			
		||||
    <!-- Proposal Details -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-md-8">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h4 class="mb-0">{{ proposal.title }}</h4>
 | 
			
		||||
                </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">
 | 
			
		||||
                            {{ proposal.status }}
 | 
			
		||||
                        </span>
 | 
			
		||||
                        <small class="text-muted">Created by {{ proposal.creator_name }} on {{ proposal.created_at | date(format="%Y-%m-%d") }}</small>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    
 | 
			
		||||
                    <h5>Description</h5>
 | 
			
		||||
                    <p class="mb-4">{{ proposal.description }}</p>
 | 
			
		||||
                    
 | 
			
		||||
                    <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") }}
 | 
			
		||||
                        {% else %}
 | 
			
		||||
                            Not set
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    </p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <div class="col-md-4">
 | 
			
		||||
            <div class="card mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Voting Results</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="mb-3">
 | 
			
		||||
                        {% set yes_percent = 0 %}
 | 
			
		||||
                        {% set no_percent = 0 %}
 | 
			
		||||
                        {% 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 %}
 | 
			
		||||
                        {% 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>
 | 
			
		||||
                        
 | 
			
		||||
                        <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>
 | 
			
		||||
                        
 | 
			
		||||
                        <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>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <p class="text-center"><strong>Total Votes:</strong> {{ results.total_votes }}</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <!-- Vote Form -->
 | 
			
		||||
            {% if proposal.status == "Active" and user and user.id %}
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Cast Your Vote</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <form action="/governance/proposals/{{ proposal.id }}/vote" method="post">
 | 
			
		||||
                        <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>
 | 
			
		||||
                                <label class="form-check-label" for="voteYes">
 | 
			
		||||
                                    Yes - I support this proposal
 | 
			
		||||
                                </label>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="form-check">
 | 
			
		||||
                                <input class="form-check-input" type="radio" name="vote_type" id="voteNo" value="No">
 | 
			
		||||
                                <label class="form-check-label" for="voteNo">
 | 
			
		||||
                                    No - I oppose this proposal
 | 
			
		||||
                                </label>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="form-check">
 | 
			
		||||
                                <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>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </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>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <button type="submit" class="btn btn-primary w-100">Submit Vote</button>
 | 
			
		||||
                    </form>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% elif not user or not user.id %}
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-body text-center">
 | 
			
		||||
                    <p>You must be logged in to vote.</p>
 | 
			
		||||
                    <a href="/login" class="btn btn-primary">Login to Vote</a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Votes List -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Votes ({{ votes | length }})</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% if votes | length > 0 %}
 | 
			
		||||
                    <div class="table-responsive">
 | 
			
		||||
                        <table class="table">
 | 
			
		||||
                            <thead>
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <th>Voter</th>
 | 
			
		||||
                                    <th>Vote</th>
 | 
			
		||||
                                    <th>Comment</th>
 | 
			
		||||
                                    <th>Date</th>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                            </thead>
 | 
			
		||||
                            <tbody>
 | 
			
		||||
                                {% for vote in votes %}
 | 
			
		||||
                                <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 %}">
 | 
			
		||||
                                            {{ vote.vote_type }}
 | 
			
		||||
                                        </span>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>{% if vote.comment %}{{ vote.comment }}{% else %}No comment{% endif %}</td>
 | 
			
		||||
                                    <td>{{ vote.created_at | date(format="%Y-%m-%d %H:%M") }}</td>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                                {% endfor %}
 | 
			
		||||
                            </tbody>
 | 
			
		||||
                        </table>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                    <p class="text-center">No votes have been cast yet.</p>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										128
									
								
								actix_mvc_app/src/views/governance/proposals.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								actix_mvc_app/src/views/governance/proposals.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Proposals - Governance Dashboard{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container">
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <h1 class="display-5 mb-4">Governance Proposals</h1>
 | 
			
		||||
            <p class="lead">View and vote on all proposals in the system.</p>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- 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-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 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>
 | 
			
		||||
 | 
			
		||||
    <!-- 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.id }}" class="btn btn-sm btn-primary">View</a>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                                {% endfor %}
 | 
			
		||||
                            </tbody>
 | 
			
		||||
                        </table>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
		Reference in New Issue
	
	Block a user