WIP: development_backend #4
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										121
									
								
								actix_mvc_app/src/controllers/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								actix_mvc_app/src/controllers/error.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
			
		||||
use actix_web::{Error, HttpResponse, web};
 | 
			
		||||
use tera::{Context, Tera};
 | 
			
		||||
 | 
			
		||||
pub struct ErrorController;
 | 
			
		||||
 | 
			
		||||
impl ErrorController {
 | 
			
		||||
    /// Renders a 404 Not Found page with customizable content
 | 
			
		||||
    pub async fn not_found(
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
        error_title: Option<&str>,
 | 
			
		||||
        error_message: Option<&str>,
 | 
			
		||||
        return_url: Option<&str>,
 | 
			
		||||
        return_text: Option<&str>,
 | 
			
		||||
    ) -> Result<HttpResponse, Error> {
 | 
			
		||||
        let mut context = Context::new();
 | 
			
		||||
 | 
			
		||||
        // Set default or custom error content
 | 
			
		||||
        context.insert("error_title", &error_title.unwrap_or("Page Not Found"));
 | 
			
		||||
        context.insert(
 | 
			
		||||
            "error_message",
 | 
			
		||||
            &error_message
 | 
			
		||||
                .unwrap_or("The page you're looking for doesn't exist or has been moved."),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Optional return URL and text
 | 
			
		||||
        if let Some(url) = return_url {
 | 
			
		||||
            context.insert("return_url", &url);
 | 
			
		||||
            context.insert("return_text", &return_text.unwrap_or("Return"));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Render the 404 template with 404 status
 | 
			
		||||
        match tmpl.render("errors/404.html", &context) {
 | 
			
		||||
            Ok(rendered) => Ok(HttpResponse::NotFound()
 | 
			
		||||
                .content_type("text/html; charset=utf-8")
 | 
			
		||||
                .body(rendered)),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::error!("Failed to render 404 template: {}", e);
 | 
			
		||||
                // Fallback to simple text response
 | 
			
		||||
                Ok(HttpResponse::NotFound()
 | 
			
		||||
                    .content_type("text/plain")
 | 
			
		||||
                    .body("404 - Page Not Found"))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Renders a 404 page for contract not found
 | 
			
		||||
    pub async fn contract_not_found(
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
        contract_id: Option<&str>,
 | 
			
		||||
    ) -> Result<HttpResponse, Error> {
 | 
			
		||||
        let error_title = "Contract Not Found";
 | 
			
		||||
        let error_message = if let Some(id) = contract_id {
 | 
			
		||||
            format!(
 | 
			
		||||
                "The contract with ID '{}' doesn't exist or has been removed.",
 | 
			
		||||
                id
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
            "The contract you're looking for doesn't exist or has been removed.".to_string()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Self::not_found(
 | 
			
		||||
            tmpl,
 | 
			
		||||
            Some(error_title),
 | 
			
		||||
            Some(&error_message),
 | 
			
		||||
            Some("/contracts"),
 | 
			
		||||
            Some("Back to Contracts"),
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Renders a 404 page for calendar event not found
 | 
			
		||||
    pub async fn calendar_event_not_found(
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
        event_id: Option<&str>,
 | 
			
		||||
    ) -> Result<HttpResponse, Error> {
 | 
			
		||||
        let error_title = "Calendar Event Not Found";
 | 
			
		||||
        let error_message = if let Some(id) = event_id {
 | 
			
		||||
            format!(
 | 
			
		||||
                "The calendar event with ID '{}' doesn't exist or has been removed.",
 | 
			
		||||
                id
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
            "The calendar event you're looking for doesn't exist or has been removed.".to_string()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Self::not_found(
 | 
			
		||||
            tmpl,
 | 
			
		||||
            Some(error_title),
 | 
			
		||||
            Some(&error_message),
 | 
			
		||||
            Some("/calendar"),
 | 
			
		||||
            Some("Back to Calendar"),
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Renders a generic 404 page
 | 
			
		||||
    pub async fn generic_not_found(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> {
 | 
			
		||||
        Self::not_found(tmpl, None, None, None, None).await
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Helper function to quickly render a contract not found response
 | 
			
		||||
pub async fn render_contract_not_found(
 | 
			
		||||
    tmpl: &web::Data<Tera>,
 | 
			
		||||
    contract_id: Option<&str>,
 | 
			
		||||
) -> Result<HttpResponse, Error> {
 | 
			
		||||
    ErrorController::contract_not_found(tmpl.clone(), contract_id).await
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Helper function to quickly render a calendar event not found response
 | 
			
		||||
pub async fn render_calendar_event_not_found(
 | 
			
		||||
    tmpl: &web::Data<Tera>,
 | 
			
		||||
    event_id: Option<&str>,
 | 
			
		||||
) -> Result<HttpResponse, Error> {
 | 
			
		||||
    ErrorController::calendar_event_not_found(tmpl.clone(), event_id).await
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Helper function to quickly render a generic not found response
 | 
			
		||||
pub async fn render_generic_not_found(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> {
 | 
			
		||||
    ErrorController::generic_not_found(tmpl).await
 | 
			
		||||
}
 | 
			
		||||
@@ -1,14 +1,15 @@
 | 
			
		||||
// Export controllers
 | 
			
		||||
pub mod home;
 | 
			
		||||
pub mod auth;
 | 
			
		||||
pub mod ticket;
 | 
			
		||||
pub mod calendar;
 | 
			
		||||
pub mod governance;
 | 
			
		||||
pub mod flow;
 | 
			
		||||
pub mod contract;
 | 
			
		||||
pub mod asset;
 | 
			
		||||
pub mod defi;
 | 
			
		||||
pub mod marketplace;
 | 
			
		||||
pub mod auth;
 | 
			
		||||
pub mod calendar;
 | 
			
		||||
pub mod company;
 | 
			
		||||
pub mod contract;
 | 
			
		||||
pub mod defi;
 | 
			
		||||
pub mod error;
 | 
			
		||||
pub mod flow;
 | 
			
		||||
pub mod governance;
 | 
			
		||||
pub mod home;
 | 
			
		||||
pub mod marketplace;
 | 
			
		||||
pub mod ticket;
 | 
			
		||||
 | 
			
		||||
// Re-export controllers for easier imports
 | 
			
		||||
 
 | 
			
		||||
@@ -97,7 +97,7 @@ pub fn get_calendars() -> Result<Vec<Calendar>, String> {
 | 
			
		||||
    let calendars = match collection.get_all() {
 | 
			
		||||
        Ok(calendars) => calendars,
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            eprintln!("Error loading calendars: {:?}", e);
 | 
			
		||||
            log::error!("Error loading calendars: {:?}", e);
 | 
			
		||||
            vec![] // Return an empty vector if there's an error
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
@@ -113,7 +113,7 @@ pub fn get_events() -> Result<Vec<Event>, String> {
 | 
			
		||||
    let events = match collection.get_all() {
 | 
			
		||||
        Ok(events) => events,
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            eprintln!("Error loading events: {:?}", e);
 | 
			
		||||
            log::error!("Error loading events: {:?}", e);
 | 
			
		||||
            vec![] // Return an empty vector if there's an error
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
@@ -129,7 +129,7 @@ pub fn get_calendar_by_id(calendar_id: u32) -> Result<Option<Calendar>, String>
 | 
			
		||||
    match collection.get_by_id(calendar_id) {
 | 
			
		||||
        Ok(calendar) => Ok(calendar),
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            eprintln!("Error fetching calendar by id {}: {:?}", calendar_id, e);
 | 
			
		||||
            log::error!("Error fetching calendar by id {}: {:?}", calendar_id, e);
 | 
			
		||||
            Err(format!("Failed to fetch calendar: {:?}", e))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -144,7 +144,7 @@ pub fn get_event_by_id(event_id: u32) -> Result<Option<Event>, String> {
 | 
			
		||||
    match collection.get_by_id(event_id) {
 | 
			
		||||
        Ok(event) => Ok(event),
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            eprintln!("Error fetching event by id {}: {:?}", event_id, e);
 | 
			
		||||
            log::error!("Error fetching event by id {}: {:?}", event_id, e);
 | 
			
		||||
            Err(format!("Failed to fetch event: {:?}", e))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -178,7 +178,7 @@ pub fn get_attendee_by_id(attendee_id: u32) -> Result<Option<Attendee>, String>
 | 
			
		||||
    match collection.get_by_id(attendee_id) {
 | 
			
		||||
        Ok(attendee) => Ok(attendee),
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            eprintln!("Error fetching attendee by id {}: {:?}", attendee_id, e);
 | 
			
		||||
            log::error!("Error fetching attendee by id {}: {:?}", attendee_id, e);
 | 
			
		||||
            Err(format!("Failed to fetch attendee: {:?}", e))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -332,7 +332,7 @@ pub fn get_or_create_user_calendar(user_id: u32, user_name: &str) -> Result<Cale
 | 
			
		||||
    let calendars = match collection.get_all() {
 | 
			
		||||
        Ok(calendars) => calendars,
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            eprintln!("Error loading calendars: {:?}", e);
 | 
			
		||||
            log::error!("Error loading calendars: {:?}", e);
 | 
			
		||||
            vec![] // Return an empty vector if there's an error
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										458
									
								
								actix_mvc_app/src/db/contracts.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										458
									
								
								actix_mvc_app/src/db/contracts.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,458 @@
 | 
			
		||||
use heromodels::{
 | 
			
		||||
    db::{Collection, Db},
 | 
			
		||||
    models::legal::{Contract, ContractRevision, ContractSigner, ContractStatus, SignerStatus},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::db::get_db;
 | 
			
		||||
 | 
			
		||||
/// Creates a new contract and saves it to the database. Returns the saved contract and its ID.
 | 
			
		||||
pub fn create_new_contract(
 | 
			
		||||
    base_id: u32,
 | 
			
		||||
    contract_id: &str,
 | 
			
		||||
    title: &str,
 | 
			
		||||
    description: &str,
 | 
			
		||||
    contract_type: &str,
 | 
			
		||||
    status: ContractStatus,
 | 
			
		||||
    created_by: &str,
 | 
			
		||||
    terms_and_conditions: Option<&str>,
 | 
			
		||||
    start_date: Option<u64>,
 | 
			
		||||
    end_date: Option<u64>,
 | 
			
		||||
    renewal_period_days: Option<u32>,
 | 
			
		||||
) -> Result<(u32, Contract), String> {
 | 
			
		||||
    let db = get_db().expect("Can get DB");
 | 
			
		||||
 | 
			
		||||
    // Create a new contract using the heromodels Contract::new constructor
 | 
			
		||||
    let mut contract = Contract::new(base_id, contract_id.to_string())
 | 
			
		||||
        .title(title)
 | 
			
		||||
        .description(description)
 | 
			
		||||
        .contract_type(contract_type.to_string())
 | 
			
		||||
        .status(status)
 | 
			
		||||
        .created_by(created_by.to_string());
 | 
			
		||||
 | 
			
		||||
    if let Some(terms) = terms_and_conditions {
 | 
			
		||||
        contract = contract.terms_and_conditions(terms);
 | 
			
		||||
    }
 | 
			
		||||
    if let Some(start) = start_date {
 | 
			
		||||
        contract = contract.start_date(start);
 | 
			
		||||
    }
 | 
			
		||||
    if let Some(end) = end_date {
 | 
			
		||||
        contract = contract.end_date(end);
 | 
			
		||||
    }
 | 
			
		||||
    if let Some(renewal) = renewal_period_days {
 | 
			
		||||
        contract = contract.renewal_period_days(renewal as i32);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Save the contract to the database
 | 
			
		||||
    let collection = db
 | 
			
		||||
        .collection::<Contract>()
 | 
			
		||||
        .expect("can open contract collection");
 | 
			
		||||
    let (contract_id, saved_contract) = collection.set(&contract).expect("can save contract");
 | 
			
		||||
 | 
			
		||||
    Ok((contract_id, saved_contract))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Loads all contracts from the database and returns them as a Vec<Contract>.
 | 
			
		||||
pub fn get_contracts() -> Result<Vec<Contract>, String> {
 | 
			
		||||
    let db = get_db().map_err(|e| format!("DB error: {}", e))?;
 | 
			
		||||
    let collection = db
 | 
			
		||||
        .collection::<Contract>()
 | 
			
		||||
        .expect("can open contract collection");
 | 
			
		||||
 | 
			
		||||
    // Try to load all contracts, but handle deserialization errors gracefully
 | 
			
		||||
    let contracts = match collection.get_all() {
 | 
			
		||||
        Ok(contracts) => contracts,
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            log::error!("Failed to load contracts from database: {:?}", e);
 | 
			
		||||
            vec![] // Return empty vector if there's an error
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    Ok(contracts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Fetches a single contract by its ID from the database.
 | 
			
		||||
pub fn get_contract_by_id(contract_id: u32) -> Result<Option<Contract>, String> {
 | 
			
		||||
    let db = get_db().map_err(|e| format!("DB error: {}", e))?;
 | 
			
		||||
    let collection = db
 | 
			
		||||
        .collection::<Contract>()
 | 
			
		||||
        .map_err(|e| format!("Collection error: {:?}", e))?;
 | 
			
		||||
    match collection.get_by_id(contract_id) {
 | 
			
		||||
        Ok(contract) => Ok(contract),
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            log::error!("Error fetching contract by id {}: {:?}", contract_id, e);
 | 
			
		||||
            Err(format!("Failed to fetch contract: {:?}", e))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Updates a contract's basic information in the database and returns the updated contract.
 | 
			
		||||
pub fn update_contract(
 | 
			
		||||
    contract_id: u32,
 | 
			
		||||
    title: &str,
 | 
			
		||||
    description: &str,
 | 
			
		||||
    contract_type: &str,
 | 
			
		||||
    terms_and_conditions: Option<&str>,
 | 
			
		||||
    start_date: Option<u64>,
 | 
			
		||||
    end_date: Option<u64>,
 | 
			
		||||
) -> Result<Contract, String> {
 | 
			
		||||
    let db = get_db().map_err(|e| format!("DB error: {}", e))?;
 | 
			
		||||
    let collection = db
 | 
			
		||||
        .collection::<Contract>()
 | 
			
		||||
        .map_err(|e| format!("Collection error: {:?}", e))?;
 | 
			
		||||
 | 
			
		||||
    if let Some(mut contract) = collection
 | 
			
		||||
        .get_by_id(contract_id)
 | 
			
		||||
        .map_err(|e| format!("Failed to fetch contract: {:?}", e))?
 | 
			
		||||
    {
 | 
			
		||||
        // Update the contract fields
 | 
			
		||||
        contract = contract
 | 
			
		||||
            .title(title)
 | 
			
		||||
            .description(description)
 | 
			
		||||
            .contract_type(contract_type.to_string());
 | 
			
		||||
 | 
			
		||||
        if let Some(terms) = terms_and_conditions {
 | 
			
		||||
            contract = contract.terms_and_conditions(terms);
 | 
			
		||||
        }
 | 
			
		||||
        if let Some(start) = start_date {
 | 
			
		||||
            contract = contract.start_date(start);
 | 
			
		||||
        }
 | 
			
		||||
        if let Some(end) = end_date {
 | 
			
		||||
            contract = contract.end_date(end);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let (_, updated_contract) = collection
 | 
			
		||||
            .set(&contract)
 | 
			
		||||
            .map_err(|e| format!("Failed to update contract: {:?}", e))?;
 | 
			
		||||
        Ok(updated_contract)
 | 
			
		||||
    } else {
 | 
			
		||||
        Err("Contract not found".to_string())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Updates a contract's status in the database and returns the updated contract.
 | 
			
		||||
pub fn update_contract_status(
 | 
			
		||||
    contract_id: u32,
 | 
			
		||||
    status: ContractStatus,
 | 
			
		||||
) -> Result<Contract, String> {
 | 
			
		||||
    let db = get_db().map_err(|e| format!("DB error: {}", e))?;
 | 
			
		||||
    let collection = db
 | 
			
		||||
        .collection::<Contract>()
 | 
			
		||||
        .map_err(|e| format!("Collection error: {:?}", e))?;
 | 
			
		||||
 | 
			
		||||
    if let Some(mut contract) = collection
 | 
			
		||||
        .get_by_id(contract_id)
 | 
			
		||||
        .map_err(|e| format!("Failed to fetch contract: {:?}", e))?
 | 
			
		||||
    {
 | 
			
		||||
        contract = contract.status(status);
 | 
			
		||||
        let (_, updated_contract) = collection
 | 
			
		||||
            .set(&contract)
 | 
			
		||||
            .map_err(|e| format!("Failed to update contract: {:?}", e))?;
 | 
			
		||||
        Ok(updated_contract)
 | 
			
		||||
    } else {
 | 
			
		||||
        Err("Contract not found".to_string())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Adds a signer to a contract and returns the updated contract.
 | 
			
		||||
pub fn add_signer_to_contract(
 | 
			
		||||
    contract_id: u32,
 | 
			
		||||
    signer_id: &str,
 | 
			
		||||
    name: &str,
 | 
			
		||||
    email: &str,
 | 
			
		||||
) -> Result<Contract, String> {
 | 
			
		||||
    let db = get_db().map_err(|e| format!("DB error: {}", e))?;
 | 
			
		||||
    let collection = db
 | 
			
		||||
        .collection::<Contract>()
 | 
			
		||||
        .map_err(|e| format!("Collection error: {:?}", e))?;
 | 
			
		||||
 | 
			
		||||
    if let Some(mut contract) = collection
 | 
			
		||||
        .get_by_id(contract_id)
 | 
			
		||||
        .map_err(|e| format!("Failed to fetch contract: {:?}", e))?
 | 
			
		||||
    {
 | 
			
		||||
        let signer =
 | 
			
		||||
            ContractSigner::new(signer_id.to_string(), name.to_string(), email.to_string());
 | 
			
		||||
        contract = contract.add_signer(signer);
 | 
			
		||||
        let (_, updated_contract) = collection
 | 
			
		||||
            .set(&contract)
 | 
			
		||||
            .map_err(|e| format!("Failed to update contract: {:?}", e))?;
 | 
			
		||||
        Ok(updated_contract)
 | 
			
		||||
    } else {
 | 
			
		||||
        Err("Contract not found".to_string())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Adds a revision to a contract and returns the updated contract.
 | 
			
		||||
pub fn add_revision_to_contract(
 | 
			
		||||
    contract_id: u32,
 | 
			
		||||
    version: u32,
 | 
			
		||||
    content: &str,
 | 
			
		||||
    created_by: &str,
 | 
			
		||||
    comments: Option<&str>,
 | 
			
		||||
) -> Result<Contract, String> {
 | 
			
		||||
    let db = get_db().map_err(|e| format!("DB error: {}", e))?;
 | 
			
		||||
    let collection = db
 | 
			
		||||
        .collection::<Contract>()
 | 
			
		||||
        .map_err(|e| format!("Collection error: {:?}", e))?;
 | 
			
		||||
 | 
			
		||||
    if let Some(mut contract) = collection
 | 
			
		||||
        .get_by_id(contract_id)
 | 
			
		||||
        .map_err(|e| format!("Failed to fetch contract: {:?}", e))?
 | 
			
		||||
    {
 | 
			
		||||
        let revision = ContractRevision::new(
 | 
			
		||||
            version,
 | 
			
		||||
            content.to_string(),
 | 
			
		||||
            current_timestamp_secs(),
 | 
			
		||||
            created_by.to_string(),
 | 
			
		||||
        )
 | 
			
		||||
        .comments(comments.unwrap_or("").to_string());
 | 
			
		||||
 | 
			
		||||
        contract = contract.add_revision(revision);
 | 
			
		||||
        let (_, updated_contract) = collection
 | 
			
		||||
            .set(&contract)
 | 
			
		||||
            .map_err(|e| format!("Failed to update contract: {:?}", e))?;
 | 
			
		||||
        Ok(updated_contract)
 | 
			
		||||
    } else {
 | 
			
		||||
        Err("Contract not found".to_string())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Updates a signer's status for a contract and returns the updated contract.
 | 
			
		||||
pub fn update_signer_status(
 | 
			
		||||
    contract_id: u32,
 | 
			
		||||
    signer_id: &str,
 | 
			
		||||
    status: SignerStatus,
 | 
			
		||||
    comments: Option<&str>,
 | 
			
		||||
) -> Result<Contract, String> {
 | 
			
		||||
    update_signer_status_with_signature(contract_id, signer_id, status, comments, None)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Updates a signer's status with signature data for a contract and returns the updated contract.
 | 
			
		||||
pub fn update_signer_status_with_signature(
 | 
			
		||||
    contract_id: u32,
 | 
			
		||||
    signer_id: &str,
 | 
			
		||||
    status: SignerStatus,
 | 
			
		||||
    comments: Option<&str>,
 | 
			
		||||
    signature_data: Option<&str>,
 | 
			
		||||
) -> Result<Contract, String> {
 | 
			
		||||
    let db = get_db().map_err(|e| format!("DB error: {}", e))?;
 | 
			
		||||
    let collection = db
 | 
			
		||||
        .collection::<Contract>()
 | 
			
		||||
        .map_err(|e| format!("Collection error: {:?}", e))?;
 | 
			
		||||
 | 
			
		||||
    if let Some(mut contract) = collection
 | 
			
		||||
        .get_by_id(contract_id)
 | 
			
		||||
        .map_err(|e| format!("Failed to fetch contract: {:?}", e))?
 | 
			
		||||
    {
 | 
			
		||||
        // Find and update the signer
 | 
			
		||||
        let mut signer_found = false;
 | 
			
		||||
        for signer in &mut contract.signers {
 | 
			
		||||
            if signer.id == signer_id {
 | 
			
		||||
                signer.status = status.clone();
 | 
			
		||||
                if status == SignerStatus::Signed {
 | 
			
		||||
                    signer.signed_at = Some(current_timestamp_secs());
 | 
			
		||||
                }
 | 
			
		||||
                if let Some(comment) = comments {
 | 
			
		||||
                    signer.comments = Some(comment.to_string());
 | 
			
		||||
                }
 | 
			
		||||
                if let Some(sig_data) = signature_data {
 | 
			
		||||
                    signer.signature_data = Some(sig_data.to_string());
 | 
			
		||||
                }
 | 
			
		||||
                signer_found = true;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if !signer_found {
 | 
			
		||||
            return Err(format!("Signer with ID {} not found", signer_id));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let (_, updated_contract) = collection
 | 
			
		||||
            .set(&contract)
 | 
			
		||||
            .map_err(|e| format!("Failed to update contract: {:?}", e))?;
 | 
			
		||||
        Ok(updated_contract)
 | 
			
		||||
    } else {
 | 
			
		||||
        Err("Contract not found".to_string())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Deletes a contract from the database.
 | 
			
		||||
pub fn delete_contract(contract_id: u32) -> Result<(), String> {
 | 
			
		||||
    let db = get_db().map_err(|e| format!("DB error: {}", e))?;
 | 
			
		||||
    let collection = db
 | 
			
		||||
        .collection::<Contract>()
 | 
			
		||||
        .map_err(|e| format!("Collection error: {:?}", e))?;
 | 
			
		||||
 | 
			
		||||
    collection
 | 
			
		||||
        .delete_by_id(contract_id)
 | 
			
		||||
        .map_err(|e| format!("Failed to delete contract: {:?}", e))?;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Gets contracts by status
 | 
			
		||||
pub fn get_contracts_by_status(status: ContractStatus) -> Result<Vec<Contract>, String> {
 | 
			
		||||
    let contracts = get_contracts()?;
 | 
			
		||||
    let filtered_contracts = contracts
 | 
			
		||||
        .into_iter()
 | 
			
		||||
        .filter(|contract| contract.status == status)
 | 
			
		||||
        .collect();
 | 
			
		||||
    Ok(filtered_contracts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Gets contracts by creator
 | 
			
		||||
pub fn get_contracts_by_creator(created_by: &str) -> Result<Vec<Contract>, String> {
 | 
			
		||||
    let contracts = get_contracts()?;
 | 
			
		||||
    let filtered_contracts = contracts
 | 
			
		||||
        .into_iter()
 | 
			
		||||
        .filter(|contract| contract.created_by == created_by)
 | 
			
		||||
        .collect();
 | 
			
		||||
    Ok(filtered_contracts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Gets contracts that need renewal (approaching end date)
 | 
			
		||||
pub fn get_contracts_needing_renewal(days_ahead: u64) -> Result<Vec<Contract>, String> {
 | 
			
		||||
    let contracts = get_contracts()?;
 | 
			
		||||
    let threshold_timestamp = current_timestamp_secs() + (days_ahead * 24 * 60 * 60);
 | 
			
		||||
 | 
			
		||||
    let filtered_contracts = contracts
 | 
			
		||||
        .into_iter()
 | 
			
		||||
        .filter(|contract| {
 | 
			
		||||
            if let Some(end_date) = contract.end_date {
 | 
			
		||||
                end_date <= threshold_timestamp && contract.status == ContractStatus::Active
 | 
			
		||||
            } else {
 | 
			
		||||
                false
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        .collect();
 | 
			
		||||
    Ok(filtered_contracts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Gets expired contracts
 | 
			
		||||
pub fn get_expired_contracts() -> Result<Vec<Contract>, String> {
 | 
			
		||||
    let contracts = get_contracts()?;
 | 
			
		||||
    let current_time = current_timestamp_secs();
 | 
			
		||||
 | 
			
		||||
    let filtered_contracts = contracts
 | 
			
		||||
        .into_iter()
 | 
			
		||||
        .filter(|contract| {
 | 
			
		||||
            if let Some(end_date) = contract.end_date {
 | 
			
		||||
                end_date < current_time && contract.status != ContractStatus::Expired
 | 
			
		||||
            } else {
 | 
			
		||||
                false
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        .collect();
 | 
			
		||||
    Ok(filtered_contracts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Updates multiple contracts to expired status
 | 
			
		||||
pub fn mark_contracts_as_expired(contract_ids: Vec<u32>) -> Result<Vec<Contract>, String> {
 | 
			
		||||
    let mut updated_contracts = Vec::new();
 | 
			
		||||
 | 
			
		||||
    for contract_id in contract_ids {
 | 
			
		||||
        match update_contract_status(contract_id, ContractStatus::Expired) {
 | 
			
		||||
            Ok(contract) => updated_contracts.push(contract),
 | 
			
		||||
            Err(e) => log::error!("Failed to update contract {}: {}", contract_id, e),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(updated_contracts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Updates a signer's reminder timestamp for a contract and returns the updated contract.
 | 
			
		||||
pub fn update_signer_reminder_timestamp(
 | 
			
		||||
    contract_id: u32,
 | 
			
		||||
    signer_id: &str,
 | 
			
		||||
    _timestamp: u64,
 | 
			
		||||
) -> Result<Contract, String> {
 | 
			
		||||
    let db = get_db().map_err(|e| format!("DB error: {}", e))?;
 | 
			
		||||
    let collection = db
 | 
			
		||||
        .collection::<Contract>()
 | 
			
		||||
        .expect("can open contract collection");
 | 
			
		||||
 | 
			
		||||
    if let Some(mut contract) = collection
 | 
			
		||||
        .get_by_id(contract_id)
 | 
			
		||||
        .map_err(|e| format!("Failed to fetch contract: {:?}", e))?
 | 
			
		||||
    {
 | 
			
		||||
        let mut signer_found = false;
 | 
			
		||||
        for signer in &mut contract.signers {
 | 
			
		||||
            if signer.id == signer_id {
 | 
			
		||||
                // TODO: Update reminder timestamp when field is available in heromodels
 | 
			
		||||
                // signer.last_reminder_mail_sent_at = Some(timestamp);
 | 
			
		||||
                signer_found = true;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if !signer_found {
 | 
			
		||||
            return Err(format!("Signer with ID {} not found", signer_id));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let (_, updated_contract) = collection
 | 
			
		||||
            .set(&contract)
 | 
			
		||||
            .map_err(|e| format!("Failed to update contract: {:?}", e))?;
 | 
			
		||||
        Ok(updated_contract)
 | 
			
		||||
    } else {
 | 
			
		||||
        Err("Contract not found".to_string())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Gets contract statistics
 | 
			
		||||
pub fn get_contract_statistics() -> Result<ContractStatistics, String> {
 | 
			
		||||
    let contracts = get_contracts()?;
 | 
			
		||||
 | 
			
		||||
    let total = contracts.len();
 | 
			
		||||
    let draft = contracts
 | 
			
		||||
        .iter()
 | 
			
		||||
        .filter(|c| c.status == ContractStatus::Draft)
 | 
			
		||||
        .count();
 | 
			
		||||
    let pending = contracts
 | 
			
		||||
        .iter()
 | 
			
		||||
        .filter(|c| c.status == ContractStatus::PendingSignatures)
 | 
			
		||||
        .count();
 | 
			
		||||
    let signed = contracts
 | 
			
		||||
        .iter()
 | 
			
		||||
        .filter(|c| c.status == ContractStatus::Signed)
 | 
			
		||||
        .count();
 | 
			
		||||
    let active = contracts
 | 
			
		||||
        .iter()
 | 
			
		||||
        .filter(|c| c.status == ContractStatus::Active)
 | 
			
		||||
        .count();
 | 
			
		||||
    let expired = contracts
 | 
			
		||||
        .iter()
 | 
			
		||||
        .filter(|c| c.status == ContractStatus::Expired)
 | 
			
		||||
        .count();
 | 
			
		||||
    let cancelled = contracts
 | 
			
		||||
        .iter()
 | 
			
		||||
        .filter(|c| c.status == ContractStatus::Cancelled)
 | 
			
		||||
        .count();
 | 
			
		||||
 | 
			
		||||
    Ok(ContractStatistics {
 | 
			
		||||
        total_contracts: total,
 | 
			
		||||
        draft_contracts: draft,
 | 
			
		||||
        pending_signature_contracts: pending,
 | 
			
		||||
        signed_contracts: signed,
 | 
			
		||||
        active_contracts: active,
 | 
			
		||||
        expired_contracts: expired,
 | 
			
		||||
        cancelled_contracts: cancelled,
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A helper for current timestamp (seconds since epoch)
 | 
			
		||||
fn current_timestamp_secs() -> u64 {
 | 
			
		||||
    std::time::SystemTime::now()
 | 
			
		||||
        .duration_since(std::time::UNIX_EPOCH)
 | 
			
		||||
        .unwrap_or_default()
 | 
			
		||||
        .as_secs()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Contract statistics structure
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct ContractStatistics {
 | 
			
		||||
    pub total_contracts: usize,
 | 
			
		||||
    pub draft_contracts: usize,
 | 
			
		||||
    pub pending_signature_contracts: usize,
 | 
			
		||||
    pub signed_contracts: usize,
 | 
			
		||||
    pub active_contracts: usize,
 | 
			
		||||
    pub expired_contracts: usize,
 | 
			
		||||
    pub cancelled_contracts: usize,
 | 
			
		||||
}
 | 
			
		||||
@@ -54,7 +54,7 @@ pub fn get_proposals() -> Result<Vec<Proposal>, String> {
 | 
			
		||||
    let proposals = match collection.get_all() {
 | 
			
		||||
        Ok(props) => props,
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            eprintln!("Error loading proposals: {:?}", e);
 | 
			
		||||
            log::error!("Error loading proposals: {:?}", e);
 | 
			
		||||
            vec![] // Return an empty vector if there's an error
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
@@ -70,7 +70,7 @@ pub fn get_proposal_by_id(proposal_id: u32) -> Result<Option<Proposal>, String>
 | 
			
		||||
    match collection.get_by_id(proposal_id) {
 | 
			
		||||
        Ok(proposal) => Ok(Some(proposal.expect("proposal not found"))),
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            eprintln!("Error fetching proposal by id {}: {:?}", proposal_id, e);
 | 
			
		||||
            log::error!("Error fetching proposal by id {}: {:?}", proposal_id, e);
 | 
			
		||||
            Err(format!("Failed to fetch proposal: {:?}", e))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -50,10 +50,18 @@ async fn main() -> io::Result<()> {
 | 
			
		||||
    // Load configuration
 | 
			
		||||
    let config = config::get_config();
 | 
			
		||||
 | 
			
		||||
    // Check for port override from command line arguments
 | 
			
		||||
    // Check for port override from environment variable or command line arguments
 | 
			
		||||
    let args: Vec<String> = env::args().collect();
 | 
			
		||||
    let mut port = config.server.port;
 | 
			
		||||
 | 
			
		||||
    // First check environment variable
 | 
			
		||||
    if let Ok(env_port) = env::var("PORT") {
 | 
			
		||||
        if let Ok(p) = env_port.parse::<u16>() {
 | 
			
		||||
            port = p;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Then check command line arguments (takes precedence over env var)
 | 
			
		||||
    for i in 1..args.len() {
 | 
			
		||||
        if args[i] == "--port" && i + 1 < args.len() {
 | 
			
		||||
            if let Ok(p) = args[i + 1].parse::<u16>() {
 | 
			
		||||
@@ -111,6 +119,8 @@ async fn main() -> io::Result<()> {
 | 
			
		||||
            .app_data(web::Data::new(tera))
 | 
			
		||||
            // Configure routes
 | 
			
		||||
            .configure(routes::configure_routes)
 | 
			
		||||
            // Add default handler for 404 errors
 | 
			
		||||
            .default_service(web::route().to(controllers::error::render_generic_not_found))
 | 
			
		||||
    })
 | 
			
		||||
    .bind(bind_address)?
 | 
			
		||||
    .workers(num_cpus::get())
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,96 @@ use chrono::{DateTime, Utc};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
 | 
			
		||||
/// Contract activity types
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
pub enum ContractActivityType {
 | 
			
		||||
    Created,
 | 
			
		||||
    SignerAdded,
 | 
			
		||||
    SignerRemoved,
 | 
			
		||||
    SentForSignatures,
 | 
			
		||||
    Signed,
 | 
			
		||||
    Rejected,
 | 
			
		||||
    StatusChanged,
 | 
			
		||||
    Revised,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ContractActivityType {
 | 
			
		||||
    pub fn as_str(&self) -> &str {
 | 
			
		||||
        match self {
 | 
			
		||||
            ContractActivityType::Created => "Contract Created",
 | 
			
		||||
            ContractActivityType::SignerAdded => "Signer Added",
 | 
			
		||||
            ContractActivityType::SignerRemoved => "Signer Removed",
 | 
			
		||||
            ContractActivityType::SentForSignatures => "Sent for Signatures",
 | 
			
		||||
            ContractActivityType::Signed => "Contract Signed",
 | 
			
		||||
            ContractActivityType::Rejected => "Contract Rejected",
 | 
			
		||||
            ContractActivityType::StatusChanged => "Status Changed",
 | 
			
		||||
            ContractActivityType::Revised => "Contract Revised",
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Contract activity model
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct ContractActivity {
 | 
			
		||||
    pub id: String,
 | 
			
		||||
    pub contract_id: u32,
 | 
			
		||||
    pub activity_type: ContractActivityType,
 | 
			
		||||
    pub description: String,
 | 
			
		||||
    pub user_name: String,
 | 
			
		||||
    pub created_at: DateTime<Utc>,
 | 
			
		||||
    pub metadata: Option<serde_json::Value>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ContractActivity {
 | 
			
		||||
    /// Creates a new contract activity
 | 
			
		||||
    pub fn new(
 | 
			
		||||
        contract_id: u32,
 | 
			
		||||
        activity_type: ContractActivityType,
 | 
			
		||||
        description: String,
 | 
			
		||||
        user_name: String,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            id: Uuid::new_v4().to_string(),
 | 
			
		||||
            contract_id,
 | 
			
		||||
            activity_type,
 | 
			
		||||
            description,
 | 
			
		||||
            user_name,
 | 
			
		||||
            created_at: Utc::now(),
 | 
			
		||||
            metadata: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Creates a contract creation activity
 | 
			
		||||
    pub fn contract_created(contract_id: u32, contract_title: &str, user_name: &str) -> Self {
 | 
			
		||||
        Self::new(
 | 
			
		||||
            contract_id,
 | 
			
		||||
            ContractActivityType::Created,
 | 
			
		||||
            format!("Created contract '{}'", contract_title),
 | 
			
		||||
            user_name.to_string(),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Creates a signer added activity
 | 
			
		||||
    pub fn signer_added(contract_id: u32, signer_name: &str, user_name: &str) -> Self {
 | 
			
		||||
        Self::new(
 | 
			
		||||
            contract_id,
 | 
			
		||||
            ContractActivityType::SignerAdded,
 | 
			
		||||
            format!("Added signer: {}", signer_name),
 | 
			
		||||
            user_name.to_string(),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Creates a sent for signatures activity
 | 
			
		||||
    pub fn sent_for_signatures(contract_id: u32, signer_count: usize, user_name: &str) -> Self {
 | 
			
		||||
        Self::new(
 | 
			
		||||
            contract_id,
 | 
			
		||||
            ContractActivityType::SentForSignatures,
 | 
			
		||||
            format!("Sent contract for signatures to {} signer(s)", signer_count),
 | 
			
		||||
            user_name.to_string(),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Contract status enum
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
pub enum ContractStatus {
 | 
			
		||||
@@ -10,7 +100,7 @@ pub enum ContractStatus {
 | 
			
		||||
    Signed,
 | 
			
		||||
    Active,
 | 
			
		||||
    Expired,
 | 
			
		||||
    Cancelled
 | 
			
		||||
    Cancelled,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ContractStatus {
 | 
			
		||||
@@ -37,7 +127,7 @@ pub enum ContractType {
 | 
			
		||||
    Distribution,
 | 
			
		||||
    License,
 | 
			
		||||
    Membership,
 | 
			
		||||
    Other
 | 
			
		||||
    Other,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ContractType {
 | 
			
		||||
@@ -61,7 +151,7 @@ impl ContractType {
 | 
			
		||||
pub enum SignerStatus {
 | 
			
		||||
    Pending,
 | 
			
		||||
    Signed,
 | 
			
		||||
    Rejected
 | 
			
		||||
    Rejected,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SignerStatus {
 | 
			
		||||
@@ -127,7 +217,12 @@ pub struct ContractRevision {
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl ContractRevision {
 | 
			
		||||
    /// Creates a new contract revision
 | 
			
		||||
    pub fn new(version: u32, content: String, created_by: String, comments: Option<String>) -> Self {
 | 
			
		||||
    pub fn new(
 | 
			
		||||
        version: u32,
 | 
			
		||||
        content: String,
 | 
			
		||||
        created_by: String,
 | 
			
		||||
        comments: Option<String>,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            version,
 | 
			
		||||
            content,
 | 
			
		||||
@@ -171,7 +266,13 @@ pub struct Contract {
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
impl Contract {
 | 
			
		||||
    /// Creates a new contract
 | 
			
		||||
    pub fn new(title: String, description: String, contract_type: ContractType, created_by: String, organization_id: Option<String>) -> Self {
 | 
			
		||||
    pub fn new(
 | 
			
		||||
        title: String,
 | 
			
		||||
        description: String,
 | 
			
		||||
        contract_type: ContractType,
 | 
			
		||||
        created_by: String,
 | 
			
		||||
        organization_id: Option<String>,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            id: Uuid::new_v4().to_string(),
 | 
			
		||||
            title,
 | 
			
		||||
@@ -229,7 +330,9 @@ impl Contract {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.signers.iter().all(|signer| signer.status == SignerStatus::Signed)
 | 
			
		||||
        self.signers
 | 
			
		||||
            .iter()
 | 
			
		||||
            .all(|signer| signer.status == SignerStatus::Signed)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Marks the contract as signed if all signers have signed
 | 
			
		||||
@@ -261,17 +364,26 @@ impl Contract {
 | 
			
		||||
 | 
			
		||||
    /// Gets the number of pending signers
 | 
			
		||||
    pub fn pending_signers_count(&self) -> usize {
 | 
			
		||||
        self.signers.iter().filter(|s| s.status == SignerStatus::Pending).count()
 | 
			
		||||
        self.signers
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|s| s.status == SignerStatus::Pending)
 | 
			
		||||
            .count()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Gets the number of signed signers
 | 
			
		||||
    pub fn signed_signers_count(&self) -> usize {
 | 
			
		||||
        self.signers.iter().filter(|s| s.status == SignerStatus::Signed).count()
 | 
			
		||||
        self.signers
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|s| s.status == SignerStatus::Signed)
 | 
			
		||||
            .count()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Gets the number of rejected signers
 | 
			
		||||
    pub fn rejected_signers_count(&self) -> usize {
 | 
			
		||||
        self.signers.iter().filter(|s| s.status == SignerStatus::Rejected).count()
 | 
			
		||||
        self.signers
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|s| s.status == SignerStatus::Rejected)
 | 
			
		||||
            .count()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -299,11 +411,26 @@ impl ContractStatistics {
 | 
			
		||||
    /// Creates new contract statistics from a list of contracts
 | 
			
		||||
    pub fn new(contracts: &[Contract]) -> Self {
 | 
			
		||||
        let total_contracts = contracts.len();
 | 
			
		||||
        let draft_contracts = contracts.iter().filter(|c| c.status == ContractStatus::Draft).count();
 | 
			
		||||
        let pending_signature_contracts = contracts.iter().filter(|c| c.status == ContractStatus::PendingSignatures).count();
 | 
			
		||||
        let signed_contracts = contracts.iter().filter(|c| c.status == ContractStatus::Signed).count();
 | 
			
		||||
        let expired_contracts = contracts.iter().filter(|c| c.status == ContractStatus::Expired).count();
 | 
			
		||||
        let cancelled_contracts = contracts.iter().filter(|c| c.status == ContractStatus::Cancelled).count();
 | 
			
		||||
        let draft_contracts = contracts
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|c| c.status == ContractStatus::Draft)
 | 
			
		||||
            .count();
 | 
			
		||||
        let pending_signature_contracts = contracts
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|c| c.status == ContractStatus::PendingSignatures)
 | 
			
		||||
            .count();
 | 
			
		||||
        let signed_contracts = contracts
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|c| c.status == ContractStatus::Signed)
 | 
			
		||||
            .count();
 | 
			
		||||
        let expired_contracts = contracts
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|c| c.status == ContractStatus::Expired)
 | 
			
		||||
            .count();
 | 
			
		||||
        let cancelled_contracts = contracts
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|c| c.status == ContractStatus::Cancelled)
 | 
			
		||||
            .count();
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            total_contracts,
 | 
			
		||||
 
 | 
			
		||||
@@ -127,11 +127,90 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) {
 | 
			
		||||
            .service(
 | 
			
		||||
                web::scope("/contracts")
 | 
			
		||||
                    .route("", web::get().to(ContractController::index))
 | 
			
		||||
                    .route("/", web::get().to(ContractController::index)) // Handle trailing slash
 | 
			
		||||
                    .route("/list", web::get().to(ContractController::list))
 | 
			
		||||
                    .route("/my", web::get().to(ContractController::my_contracts))
 | 
			
		||||
                    .route("/{id}", web::get().to(ContractController::detail))
 | 
			
		||||
                    .route("/list/", web::get().to(ContractController::list)) // Handle trailing slash
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/my-contracts",
 | 
			
		||||
                        web::get().to(ContractController::my_contracts),
 | 
			
		||||
                    )
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/my-contracts/",
 | 
			
		||||
                        web::get().to(ContractController::my_contracts),
 | 
			
		||||
                    ) // Handle trailing slash
 | 
			
		||||
                    .route("/create", web::get().to(ContractController::create_form))
 | 
			
		||||
                    .route("/create", web::post().to(ContractController::create)),
 | 
			
		||||
                    .route("/create/", web::get().to(ContractController::create_form)) // Handle trailing slash
 | 
			
		||||
                    .route("/create", web::post().to(ContractController::create))
 | 
			
		||||
                    .route("/create/", web::post().to(ContractController::create)) // Handle trailing slash
 | 
			
		||||
                    .route("/statistics", web::get().to(ContractController::statistics))
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/activities",
 | 
			
		||||
                        web::get().to(ContractController::all_activities),
 | 
			
		||||
                    )
 | 
			
		||||
                    .route("/{id}/edit", web::get().to(ContractController::edit_form))
 | 
			
		||||
                    .route("/{id}/edit", web::post().to(ContractController::update))
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/filter/{status}",
 | 
			
		||||
                        web::get().to(ContractController::filter_by_status),
 | 
			
		||||
                    )
 | 
			
		||||
                    .route("/{id}", web::get().to(ContractController::detail))
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/{id}/status/{status}",
 | 
			
		||||
                        web::post().to(ContractController::update_status),
 | 
			
		||||
                    )
 | 
			
		||||
                    .route("/{id}/delete", web::post().to(ContractController::delete))
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/{id}/add-signer",
 | 
			
		||||
                        web::get().to(ContractController::add_signer_form),
 | 
			
		||||
                    )
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/{id}/add-signer",
 | 
			
		||||
                        web::post().to(ContractController::add_signer),
 | 
			
		||||
                    )
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/{id}/remind",
 | 
			
		||||
                        web::post().to(ContractController::remind_to_sign),
 | 
			
		||||
                    )
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/{id}/send",
 | 
			
		||||
                        web::post().to(ContractController::send_for_signatures),
 | 
			
		||||
                    )
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/{id}/reminder-status",
 | 
			
		||||
                        web::get().to(ContractController::get_reminder_status),
 | 
			
		||||
                    )
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/{id}/add-revision",
 | 
			
		||||
                        web::post().to(ContractController::add_revision),
 | 
			
		||||
                    )
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/{id}/signer/{signer_id}/status/{status}",
 | 
			
		||||
                        web::post().to(ContractController::update_signer_status),
 | 
			
		||||
                    )
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/{id}/sign/{signer_id}",
 | 
			
		||||
                        web::post().to(ContractController::sign_contract),
 | 
			
		||||
                    )
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/{id}/reject/{signer_id}",
 | 
			
		||||
                        web::post().to(ContractController::reject_contract),
 | 
			
		||||
                    )
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/{id}/cancel",
 | 
			
		||||
                        web::post().to(ContractController::cancel_contract),
 | 
			
		||||
                    )
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/{id}/clone",
 | 
			
		||||
                        web::post().to(ContractController::clone_contract),
 | 
			
		||||
                    )
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/{id}/signed/{signer_id}",
 | 
			
		||||
                        web::get().to(ContractController::view_signed_document),
 | 
			
		||||
                    )
 | 
			
		||||
                    .route(
 | 
			
		||||
                        "/{id}/share",
 | 
			
		||||
                        web::post().to(ContractController::share_contract),
 | 
			
		||||
                    ),
 | 
			
		||||
            )
 | 
			
		||||
            // Asset routes
 | 
			
		||||
            .service(
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
use actix_web::{Error, HttpResponse};
 | 
			
		||||
use chrono::{DateTime, Utc};
 | 
			
		||||
use pulldown_cmark::{Options, Parser, html};
 | 
			
		||||
use std::error::Error as StdError;
 | 
			
		||||
use tera::{self, Context, Function, Tera, Value};
 | 
			
		||||
 | 
			
		||||
@@ -224,6 +225,26 @@ pub fn truncate_string(s: &str, max_length: usize) -> String {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Parses markdown content and returns HTML
 | 
			
		||||
pub fn parse_markdown(markdown_content: &str) -> String {
 | 
			
		||||
    // Set up markdown parser options
 | 
			
		||||
    let mut options = Options::empty();
 | 
			
		||||
    options.insert(Options::ENABLE_TABLES);
 | 
			
		||||
    options.insert(Options::ENABLE_FOOTNOTES);
 | 
			
		||||
    options.insert(Options::ENABLE_STRIKETHROUGH);
 | 
			
		||||
    options.insert(Options::ENABLE_TASKLISTS);
 | 
			
		||||
    options.insert(Options::ENABLE_SMART_PUNCTUATION);
 | 
			
		||||
 | 
			
		||||
    // Create parser
 | 
			
		||||
    let parser = Parser::new_ext(markdown_content, options);
 | 
			
		||||
 | 
			
		||||
    // Render to HTML
 | 
			
		||||
    let mut html_output = String::new();
 | 
			
		||||
    html::push_html(&mut html_output, parser);
 | 
			
		||||
 | 
			
		||||
    html_output
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Renders a template with error handling
 | 
			
		||||
///
 | 
			
		||||
/// This function attempts to render a template and handles any errors by rendering
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										200
									
								
								actix_mvc_app/src/views/contracts/add_signer.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								actix_mvc_app/src/views/contracts/add_signer.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,200 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Add Signer - {{ contract.title }}{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container-fluid">
 | 
			
		||||
    <!-- Header -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col">
 | 
			
		||||
            <nav aria-label="breadcrumb">
 | 
			
		||||
                <ol class="breadcrumb">
 | 
			
		||||
                    <li class="breadcrumb-item"><a href="/contracts">Contracts</a></li>
 | 
			
		||||
                    <li class="breadcrumb-item"><a href="/contracts/{{ contract.id }}">{{ contract.title }}</a></li>
 | 
			
		||||
                    <li class="breadcrumb-item active" aria-current="page">Add Signer</li>
 | 
			
		||||
                </ol>
 | 
			
		||||
            </nav>
 | 
			
		||||
            <div class="d-flex justify-content-between align-items-center">
 | 
			
		||||
                <div>
 | 
			
		||||
                    <h1 class="h3 mb-0">Add Signer</h1>
 | 
			
		||||
                    <p class="text-muted mb-0">Add a new signer to "{{ contract.title }}"</p>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div>
 | 
			
		||||
                    <a href="/contracts/{{ contract.id }}" class="btn btn-outline-secondary">
 | 
			
		||||
                        <i class="bi bi-arrow-left me-1"></i> Back to Contract
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <!-- Add Signer Form -->
 | 
			
		||||
        <div class="col-lg-8">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">
 | 
			
		||||
                        <i class="bi bi-person-plus me-2"></i>Signer Information
 | 
			
		||||
                    </h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% if error %}
 | 
			
		||||
                    <div class="alert alert-danger" role="alert">
 | 
			
		||||
                        <i class="bi bi-exclamation-triangle me-2"></i>{{ error }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
                    <form method="post" action="/contracts/{{ contract.id }}/add-signer">
 | 
			
		||||
                        <div class="row">
 | 
			
		||||
                            <div class="col-md-6">
 | 
			
		||||
                                <div class="mb-3">
 | 
			
		||||
                                    <label for="name" class="form-label">
 | 
			
		||||
                                        Full Name <span class="text-danger">*</span>
 | 
			
		||||
                                    </label>
 | 
			
		||||
                                    <input type="text" class="form-control" id="name" name="name" 
 | 
			
		||||
                                           placeholder="Enter signer's full name" required>
 | 
			
		||||
                                    <div class="form-text">The full legal name of the person who will sign</div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="col-md-6">
 | 
			
		||||
                                <div class="mb-3">
 | 
			
		||||
                                    <label for="email" class="form-label">
 | 
			
		||||
                                        Email Address <span class="text-danger">*</span>
 | 
			
		||||
                                    </label>
 | 
			
		||||
                                    <input type="email" class="form-control" id="email" name="email" 
 | 
			
		||||
                                           placeholder="Enter signer's email address" required>
 | 
			
		||||
                                    <div class="form-text">Email where signing instructions will be sent</div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="mb-4">
 | 
			
		||||
                            <div class="alert alert-info">
 | 
			
		||||
                                <i class="bi bi-info-circle me-2"></i>
 | 
			
		||||
                                <strong>Note:</strong> The signer will receive an email with a secure link to sign the contract once you send it for signatures.
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="d-flex gap-2">
 | 
			
		||||
                            <button type="submit" class="btn btn-primary">
 | 
			
		||||
                                <i class="bi bi-person-plus me-1"></i> Add Signer
 | 
			
		||||
                            </button>
 | 
			
		||||
                            <a href="/contracts/{{ contract.id }}" class="btn btn-secondary">
 | 
			
		||||
                                Cancel
 | 
			
		||||
                            </a>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </form>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- Contract Summary -->
 | 
			
		||||
        <div class="col-lg-4">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Contract Summary</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h6 class="card-title">{{ contract.title }}</h6>
 | 
			
		||||
                    <p class="card-text text-muted">{{ contract.description }}</p>
 | 
			
		||||
                    
 | 
			
		||||
                    <hr>
 | 
			
		||||
                    
 | 
			
		||||
                    <div class="row text-center">
 | 
			
		||||
                        <div class="col-6">
 | 
			
		||||
                            <div class="border-end">
 | 
			
		||||
                                <div class="h4 mb-0 text-primary">{{ contract.signers|length }}</div>
 | 
			
		||||
                                <small class="text-muted">Current Signers</small>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-6">
 | 
			
		||||
                            <div class="h4 mb-0 text-success">{{ contract.signed_signers }}</div>
 | 
			
		||||
                            <small class="text-muted">Signed</small>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- Current Signers List -->
 | 
			
		||||
            {% if contract.signers|length > 0 %}
 | 
			
		||||
            <div class="card mt-3">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h6 class="mb-0">Current Signers</h6>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body p-0">
 | 
			
		||||
                    <ul class="list-group list-group-flush">
 | 
			
		||||
                        {% for signer in contract.signers %}
 | 
			
		||||
                        <li class="list-group-item d-flex justify-content-between align-items-center">
 | 
			
		||||
                            <div>
 | 
			
		||||
                                <div class="fw-medium">{{ signer.name }}</div>
 | 
			
		||||
                                <small class="text-muted">{{ signer.email }}</small>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <span class="badge {% if signer.status == 'Signed' %}bg-success{% elif signer.status == 'Rejected' %}bg-danger{% else %}bg-warning text-dark{% endif %}">
 | 
			
		||||
                                {{ signer.status }}
 | 
			
		||||
                            </span>
 | 
			
		||||
                        </li>
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                    </ul>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block extra_js %}
 | 
			
		||||
<script>
 | 
			
		||||
    document.addEventListener('DOMContentLoaded', function() {
 | 
			
		||||
        // Form validation
 | 
			
		||||
        const form = document.querySelector('form');
 | 
			
		||||
        const nameInput = document.getElementById('name');
 | 
			
		||||
        const emailInput = document.getElementById('email');
 | 
			
		||||
 | 
			
		||||
        form.addEventListener('submit', function(e) {
 | 
			
		||||
            let isValid = true;
 | 
			
		||||
            
 | 
			
		||||
            // Clear previous validation states
 | 
			
		||||
            nameInput.classList.remove('is-invalid');
 | 
			
		||||
            emailInput.classList.remove('is-invalid');
 | 
			
		||||
            
 | 
			
		||||
            // Validate name
 | 
			
		||||
            if (nameInput.value.trim().length < 2) {
 | 
			
		||||
                nameInput.classList.add('is-invalid');
 | 
			
		||||
                isValid = false;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Validate email format
 | 
			
		||||
            const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
 | 
			
		||||
            if (!emailRegex.test(emailInput.value)) {
 | 
			
		||||
                emailInput.classList.add('is-invalid');
 | 
			
		||||
                isValid = false;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (!isValid) {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Real-time validation feedback
 | 
			
		||||
        nameInput.addEventListener('input', function() {
 | 
			
		||||
            if (this.value.trim().length >= 2) {
 | 
			
		||||
                this.classList.remove('is-invalid');
 | 
			
		||||
                this.classList.add('is-valid');
 | 
			
		||||
            } else {
 | 
			
		||||
                this.classList.remove('is-valid');
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        emailInput.addEventListener('input', function() {
 | 
			
		||||
            const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
 | 
			
		||||
            if (emailRegex.test(this.value)) {
 | 
			
		||||
                this.classList.remove('is-invalid');
 | 
			
		||||
                this.classList.add('is-valid');
 | 
			
		||||
            } else {
 | 
			
		||||
                this.classList.remove('is-valid');
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										128
									
								
								actix_mvc_app/src/views/contracts/all_activities.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								actix_mvc_app/src/views/contracts/all_activities.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}All Contract Activities{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container-fluid">
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <!-- Header -->
 | 
			
		||||
            <div class="row mb-4">
 | 
			
		||||
                <div class="col-12">
 | 
			
		||||
                    <h1 class="display-5 mb-3">Contract Activities</h1>
 | 
			
		||||
                    <p class="lead">Complete history of contract actions and events across your organization.</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- Activities List -->
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="card-title mb-0">
 | 
			
		||||
                        <i class="bi bi-activity"></i> Contract Activity History
 | 
			
		||||
                    </h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% if activities %}
 | 
			
		||||
                    <div class="row">
 | 
			
		||||
                        <div class="col-12">
 | 
			
		||||
                            <div class="table-responsive">
 | 
			
		||||
                                <table class="table table-hover">
 | 
			
		||||
                                    <thead>
 | 
			
		||||
                                        <tr>
 | 
			
		||||
                                            <th width="50">Type</th>
 | 
			
		||||
                                            <th>User</th>
 | 
			
		||||
                                            <th>Action</th>
 | 
			
		||||
                                            <th>Contract</th>
 | 
			
		||||
                                            <th width="150">Date</th>
 | 
			
		||||
                                        </tr>
 | 
			
		||||
                                    </thead>
 | 
			
		||||
                                    <tbody>
 | 
			
		||||
                                        {% for activity in activities %}
 | 
			
		||||
                                        <tr>
 | 
			
		||||
                                            <td>
 | 
			
		||||
                                                <i class="{{ activity.icon }}"></i>
 | 
			
		||||
                                            </td>
 | 
			
		||||
                                            <td>
 | 
			
		||||
                                                <strong>{{ activity.user }}</strong>
 | 
			
		||||
                                            </td>
 | 
			
		||||
                                            <td>
 | 
			
		||||
                                                {{ activity.action }}
 | 
			
		||||
                                            </td>
 | 
			
		||||
                                            <td>
 | 
			
		||||
                                                <span class="text-decoration-none">
 | 
			
		||||
                                                    {{ activity.contract_title }}
 | 
			
		||||
                                                </span>
 | 
			
		||||
                                            </td>
 | 
			
		||||
                                            <td>
 | 
			
		||||
                                                <small class="text-muted">
 | 
			
		||||
                                                    {{ activity.created_at | date(format="%Y-%m-%d %H:%M") }}
 | 
			
		||||
                                                </small>
 | 
			
		||||
                                            </td>
 | 
			
		||||
                                        </tr>
 | 
			
		||||
                                        {% endfor %}
 | 
			
		||||
                                    </tbody>
 | 
			
		||||
                                </table>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                    <div class="text-center py-5">
 | 
			
		||||
                        <i class="bi bi-activity display-1 text-muted"></i>
 | 
			
		||||
                        <h4 class="mt-3">No Activities Yet</h4>
 | 
			
		||||
                        <p class="text-muted">
 | 
			
		||||
                            Contract activities will appear here as users create contracts and add signers.
 | 
			
		||||
                        </p>
 | 
			
		||||
                        <a href="/contracts/create" class="btn btn-primary">
 | 
			
		||||
                            <i class="bi bi-plus-circle"></i> Create First Contract
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- Activity Statistics -->
 | 
			
		||||
            {% if activities %}
 | 
			
		||||
            <div class="row mt-4">
 | 
			
		||||
                <div class="col-md-4">
 | 
			
		||||
                    <div class="card text-center">
 | 
			
		||||
                        <div class="card-body">
 | 
			
		||||
                            <h5 class="card-title">{{ activities | length }}</h5>
 | 
			
		||||
                            <p class="card-text text-muted">Total Activities</p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-md-4">
 | 
			
		||||
                    <div class="card text-center">
 | 
			
		||||
                        <div class="card-body">
 | 
			
		||||
                            <h5 class="card-title">
 | 
			
		||||
                                <i class="bi bi-file-earmark-text text-primary"></i>
 | 
			
		||||
                            </h5>
 | 
			
		||||
                            <p class="card-text text-muted">Contract Timeline</p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-md-4">
 | 
			
		||||
                    <div class="card text-center">
 | 
			
		||||
                        <div class="card-body">
 | 
			
		||||
                            <h5 class="card-title">
 | 
			
		||||
                                <i class="bi bi-people text-success"></i>
 | 
			
		||||
                            </h5>
 | 
			
		||||
                            <p class="card-text text-muted">Team Collaboration</p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
 | 
			
		||||
            <!-- Back to Dashboard -->
 | 
			
		||||
            <div class="row mt-4">
 | 
			
		||||
                <div class="col-12 text-center">
 | 
			
		||||
                    <a href="/contracts" class="btn btn-outline-secondary">
 | 
			
		||||
                        <i class="bi bi-arrow-left"></i> Back to Contracts Dashboard
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -36,27 +36,41 @@
 | 
			
		||||
                            <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="PendingSignatures">Pending Signatures</option>
 | 
			
		||||
                                <option value="Signed">Signed</option>
 | 
			
		||||
                                <option value="Expired">Expired</option>
 | 
			
		||||
                                <option value="Cancelled">Cancelled</option>
 | 
			
		||||
                                <option value="Draft" {% if current_status_filter=="Draft" %}selected{% endif %}>Draft
 | 
			
		||||
                                </option>
 | 
			
		||||
                                <option value="PendingSignatures" {% if current_status_filter=="PendingSignatures"
 | 
			
		||||
                                    %}selected{% endif %}>Pending Signatures</option>
 | 
			
		||||
                                <option value="Signed" {% if current_status_filter=="Signed" %}selected{% endif %}>
 | 
			
		||||
                                    Signed</option>
 | 
			
		||||
                                <option value="Expired" {% if current_status_filter=="Expired" %}selected{% endif %}>
 | 
			
		||||
                                    Expired</option>
 | 
			
		||||
                                <option value="Cancelled" {% if current_status_filter=="Cancelled" %}selected{% endif
 | 
			
		||||
                                    %}>Cancelled</option>
 | 
			
		||||
                            </select>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-md-3">
 | 
			
		||||
                            <label for="type" class="form-label">Contract Type</label>
 | 
			
		||||
                            <select class="form-select" id="type" name="type">
 | 
			
		||||
                                <option value="">All Types</option>
 | 
			
		||||
                                <option value="Service">Service Agreement</option>
 | 
			
		||||
                                <option value="Employment">Employment Contract</option>
 | 
			
		||||
                                <option value="NDA">Non-Disclosure Agreement</option>
 | 
			
		||||
                                <option value="SLA">Service Level Agreement</option>
 | 
			
		||||
                                <option value="Other">Other</option>
 | 
			
		||||
                                <option value="Service Agreement" {% if current_type_filter=="Service Agreement"
 | 
			
		||||
                                    %}selected{% endif %}>Service Agreement</option>
 | 
			
		||||
                                <option value="Employment Contract" {% if current_type_filter=="Employment Contract"
 | 
			
		||||
                                    %}selected{% endif %}>Employment Contract</option>
 | 
			
		||||
                                <option value="Non-Disclosure Agreement" {% if
 | 
			
		||||
                                    current_type_filter=="Non-Disclosure Agreement" %}selected{% endif %}>Non-Disclosure
 | 
			
		||||
                                    Agreement</option>
 | 
			
		||||
                                <option value="Service Level Agreement" {% if
 | 
			
		||||
                                    current_type_filter=="Service Level Agreement" %}selected{% endif %}>Service Level
 | 
			
		||||
                                    Agreement</option>
 | 
			
		||||
                                <option value="Other" {% if current_type_filter=="Other" %}selected{% endif %}>Other
 | 
			
		||||
                                </option>
 | 
			
		||||
                            </select>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-md-3">
 | 
			
		||||
                            <label for="search" class="form-label">Search</label>
 | 
			
		||||
                            <input type="text" class="form-control" id="search" name="search" placeholder="Search by title or description">
 | 
			
		||||
                            <input type="text" class="form-control" id="search" name="search"
 | 
			
		||||
                                placeholder="Search by title or description"
 | 
			
		||||
                                value="{{ current_search_filter | default(value='') }}">
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-md-3 d-flex align-items-end">
 | 
			
		||||
                            <button type="submit" class="btn btn-primary w-100">Apply Filters</button>
 | 
			
		||||
@@ -98,7 +112,8 @@
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>{{ contract.contract_type }}</td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <span class="badge {% if contract.status == 'Signed' %}bg-success{% elif contract.status == 'PendingSignatures' %}bg-warning text-dark{% elif contract.status == 'Draft' %}bg-secondary{% elif contract.status == 'Expired' %}bg-danger{% else %}bg-dark{% endif %}">
 | 
			
		||||
                                        <span
 | 
			
		||||
                                            class="badge {% if contract.status == 'Signed' %}bg-success{% elif contract.status == 'PendingSignatures' %}bg-warning text-dark{% elif contract.status == 'Draft' %}bg-secondary{% elif contract.status == 'Expired' %}bg-danger{% else %}bg-dark{% endif %}">
 | 
			
		||||
                                            {{ contract.status }}
 | 
			
		||||
                                        </span>
 | 
			
		||||
                                    </td>
 | 
			
		||||
@@ -112,9 +127,14 @@
 | 
			
		||||
                                                <i class="bi bi-eye"></i>
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                            {% if contract.status == 'Draft' %}
 | 
			
		||||
                                            <a href="/contracts/{{ contract.id }}/edit" class="btn btn-sm btn-outline-secondary">
 | 
			
		||||
                                            <a href="/contracts/{{ contract.id }}/edit"
 | 
			
		||||
                                                class="btn btn-sm btn-outline-secondary">
 | 
			
		||||
                                                <i class="bi bi-pencil"></i>
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                            <button class="btn btn-sm btn-outline-danger"
 | 
			
		||||
                                                onclick="deleteContract({{ contract.id }}, '{{ contract.title | replace(from="'", to="\\'") }}')">
 | 
			
		||||
                                                <i class="bi bi-trash"></i>
 | 
			
		||||
                                            </button>
 | 
			
		||||
                                            {% endif %}
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </td>
 | 
			
		||||
@@ -137,4 +157,70 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- Delete Confirmation Modal -->
 | 
			
		||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
 | 
			
		||||
    <div class="modal-dialog">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <h5 class="modal-title" id="deleteModalLabel">Delete Contract</h5>
 | 
			
		||||
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-body">
 | 
			
		||||
                <div class="alert alert-danger">
 | 
			
		||||
                    <i class="bi bi-exclamation-triangle me-2"></i>
 | 
			
		||||
                    <strong>Warning:</strong> This action cannot be undone!
 | 
			
		||||
                </div>
 | 
			
		||||
                <p>Are you sure you want to delete the contract "<strong id="contractTitle"></strong>"?</p>
 | 
			
		||||
                <p>This will permanently remove the contract and all its associated data.</p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
 | 
			
		||||
                <button type="button" class="btn btn-danger" id="confirmDeleteBtn">
 | 
			
		||||
                    <i class="bi bi-trash me-1"></i> Delete Contract
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block extra_js %}
 | 
			
		||||
<script>
 | 
			
		||||
    console.log('Contracts list scripts loading...');
 | 
			
		||||
 | 
			
		||||
    // Delete function using Bootstrap modal
 | 
			
		||||
    window.deleteContract = function (contractId, contractTitle) {
 | 
			
		||||
        console.log('Delete function called:', contractId, contractTitle);
 | 
			
		||||
 | 
			
		||||
        // Set the contract title in the modal
 | 
			
		||||
        document.getElementById('contractTitle').textContent = contractTitle;
 | 
			
		||||
 | 
			
		||||
        // Store the contract ID for later use
 | 
			
		||||
        window.currentDeleteContractId = contractId;
 | 
			
		||||
 | 
			
		||||
        // Show the modal
 | 
			
		||||
        const deleteModal = new bootstrap.Modal(document.getElementById('deleteModal'));
 | 
			
		||||
        deleteModal.show();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    console.log('deleteContract function defined:', typeof window.deleteContract);
 | 
			
		||||
 | 
			
		||||
    document.addEventListener('DOMContentLoaded', function () {
 | 
			
		||||
        // Handle confirm delete button click
 | 
			
		||||
        document.getElementById('confirmDeleteBtn').addEventListener('click', function () {
 | 
			
		||||
            console.log('User confirmed deletion, submitting form...');
 | 
			
		||||
 | 
			
		||||
            // Create and submit form
 | 
			
		||||
            const form = document.createElement('form');
 | 
			
		||||
            form.method = 'POST';
 | 
			
		||||
            form.action = '/contracts/' + window.currentDeleteContractId + '/delete';
 | 
			
		||||
            form.style.display = 'none';
 | 
			
		||||
 | 
			
		||||
            document.body.appendChild(form);
 | 
			
		||||
            form.submit();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -25,12 +25,14 @@
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <form action="/contracts/create" method="post">
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="title" class="form-label">Contract Title <span class="text-danger">*</span></label>
 | 
			
		||||
                            <label for="title" class="form-label">Contract Title <span
 | 
			
		||||
                                    class="text-danger">*</span></label>
 | 
			
		||||
                            <input type="text" class="form-control" id="title" name="title" required>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        
 | 
			
		||||
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="contract_type" class="form-label">Contract Type <span class="text-danger">*</span></label>
 | 
			
		||||
                            <label for="contract_type" class="form-label">Contract Type <span
 | 
			
		||||
                                    class="text-danger">*</span></label>
 | 
			
		||||
                            <select class="form-select" id="contract_type" name="contract_type" required>
 | 
			
		||||
                                <option value="" selected disabled>Select a contract type</option>
 | 
			
		||||
                                {% for type in contract_types %}
 | 
			
		||||
@@ -38,28 +40,59 @@
 | 
			
		||||
                                {% endfor %}
 | 
			
		||||
                            </select>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        
 | 
			
		||||
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="description" class="form-label">Description <span class="text-danger">*</span></label>
 | 
			
		||||
                            <textarea class="form-control" id="description" name="description" rows="3" required></textarea>
 | 
			
		||||
                            <label for="description" class="form-label">Description <span
 | 
			
		||||
                                    class="text-danger">*</span></label>
 | 
			
		||||
                            <textarea class="form-control" id="description" name="description" rows="3"
 | 
			
		||||
                                required></textarea>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        
 | 
			
		||||
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="content" class="form-label">Contract Content</label>
 | 
			
		||||
                            <textarea class="form-control" id="content" name="content" rows="10"></textarea>
 | 
			
		||||
                            <div class="form-text">You can leave this blank and add content later.</div>
 | 
			
		||||
                            <label for="content" class="form-label">Contract Content (Markdown)</label>
 | 
			
		||||
                            <textarea class="form-control" id="content" name="content" rows="10" placeholder="# Contract Title
 | 
			
		||||
 | 
			
		||||
## 1. Introduction
 | 
			
		||||
This contract outlines the terms and conditions...
 | 
			
		||||
 | 
			
		||||
## 2. Scope of Work
 | 
			
		||||
- Task 1
 | 
			
		||||
- Task 2
 | 
			
		||||
- Task 3
 | 
			
		||||
 | 
			
		||||
## 3. Payment Terms
 | 
			
		||||
Payment will be made according to the following schedule:
 | 
			
		||||
 | 
			
		||||
| Milestone | Amount | Due Date |
 | 
			
		||||
|-----------|--------|----------|
 | 
			
		||||
| Start | $1,000 | Upon signing |
 | 
			
		||||
| Completion | $2,000 | Upon delivery |
 | 
			
		||||
 | 
			
		||||
## 4. Terms and Conditions
 | 
			
		||||
**Important:** All parties must agree to these terms.
 | 
			
		||||
 | 
			
		||||
> This is a blockquote for important notices.
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
*For questions, contact [support@example.com](mailto:support@example.com)*"></textarea>
 | 
			
		||||
                            <div class="form-text">
 | 
			
		||||
                                <strong>Markdown Support:</strong> You can use markdown formatting including headers
 | 
			
		||||
                                (#), lists (-), tables (|), bold (**text**), italic (*text*), links, and more.
 | 
			
		||||
                                <a href="/editor" target="_blank">Open Markdown Editor</a> for a live preview.
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        
 | 
			
		||||
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="effective_date" class="form-label">Effective Date</label>
 | 
			
		||||
                            <input type="date" class="form-control" id="effective_date" name="effective_date">
 | 
			
		||||
                        </div>
 | 
			
		||||
                        
 | 
			
		||||
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="expiration_date" class="form-label">Expiration Date</label>
 | 
			
		||||
                            <input type="date" class="form-control" id="expiration_date" name="expiration_date">
 | 
			
		||||
                        </div>
 | 
			
		||||
                        
 | 
			
		||||
 | 
			
		||||
                        <div class="d-grid gap-2 d-md-flex justify-content-md-end">
 | 
			
		||||
                            <a href="/contracts" class="btn btn-outline-secondary me-md-2">Cancel</a>
 | 
			
		||||
                            <button type="submit" class="btn btn-primary">Create Contract</button>
 | 
			
		||||
@@ -68,14 +101,15 @@
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        <div class="col-lg-4">
 | 
			
		||||
            <div class="card mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Tips</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <p>Creating a new contract is just the first step. After creating the contract, you'll be able to:</p>
 | 
			
		||||
                    <p>Creating a new contract is just the first step. After creating the contract, you'll be able to:
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <ul>
 | 
			
		||||
                        <li>Add signers who need to approve the contract</li>
 | 
			
		||||
                        <li>Edit the contract content</li>
 | 
			
		||||
@@ -85,7 +119,7 @@
 | 
			
		||||
                    <p>The contract will be in <strong>Draft</strong> status until you send it for signatures.</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Contract Templates</h5>
 | 
			
		||||
@@ -93,16 +127,20 @@
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <p>You can use one of our pre-defined templates to get started quickly:</p>
 | 
			
		||||
                    <div class="list-group">
 | 
			
		||||
                        <button type="button" class="list-group-item list-group-item-action" onclick="loadTemplate('nda')">
 | 
			
		||||
                        <button type="button" class="list-group-item list-group-item-action"
 | 
			
		||||
                            onclick="loadTemplate('nda')">
 | 
			
		||||
                            Non-Disclosure Agreement
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <button type="button" class="list-group-item list-group-item-action" onclick="loadTemplate('service')">
 | 
			
		||||
                        <button type="button" class="list-group-item list-group-item-action"
 | 
			
		||||
                            onclick="loadTemplate('service')">
 | 
			
		||||
                            Service Agreement
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <button type="button" class="list-group-item list-group-item-action" onclick="loadTemplate('employment')">
 | 
			
		||||
                        <button type="button" class="list-group-item list-group-item-action"
 | 
			
		||||
                            onclick="loadTemplate('employment')">
 | 
			
		||||
                            Employment Contract
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <button type="button" class="list-group-item list-group-item-action" onclick="loadTemplate('sla')">
 | 
			
		||||
                        <button type="button" class="list-group-item list-group-item-action"
 | 
			
		||||
                            onclick="loadTemplate('sla')">
 | 
			
		||||
                            Service Level Agreement
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
@@ -121,19 +159,101 @@
 | 
			
		||||
        let description = '';
 | 
			
		||||
        let content = '';
 | 
			
		||||
        let contractType = '';
 | 
			
		||||
        
 | 
			
		||||
        switch(type) {
 | 
			
		||||
 | 
			
		||||
        switch (type) {
 | 
			
		||||
            case 'nda':
 | 
			
		||||
                title = 'Non-Disclosure Agreement';
 | 
			
		||||
                description = 'Standard NDA for protecting confidential information';
 | 
			
		||||
                contractType = 'Non-Disclosure Agreement';
 | 
			
		||||
                content = 'This Non-Disclosure Agreement (the "Agreement") is entered into as of [DATE] by and between [PARTY A] and [PARTY B].\n\n1. Definition of Confidential Information\n2. Obligations of Receiving Party\n3. Term\n...';
 | 
			
		||||
                content = `# Non-Disclosure Agreement
 | 
			
		||||
 | 
			
		||||
This Non-Disclosure Agreement (the "**Agreement**") is entered into as of **[DATE]** by and between **[PARTY A]** and **[PARTY B]**.
 | 
			
		||||
 | 
			
		||||
## 1. Definition of Confidential Information
 | 
			
		||||
 | 
			
		||||
"Confidential Information" means any and all information disclosed by either party to the other party, whether orally or in writing, whether or not marked, designated or otherwise identified as "confidential."
 | 
			
		||||
 | 
			
		||||
## 2. Obligations of Receiving Party
 | 
			
		||||
 | 
			
		||||
The receiving party agrees to:
 | 
			
		||||
- Hold all Confidential Information in strict confidence
 | 
			
		||||
- Not disclose any Confidential Information to third parties
 | 
			
		||||
- Use Confidential Information solely for the purpose of evaluating potential business relationships
 | 
			
		||||
 | 
			
		||||
## 3. Term
 | 
			
		||||
 | 
			
		||||
This Agreement shall remain in effect for a period of **[DURATION]** years from the date first written above.
 | 
			
		||||
 | 
			
		||||
## 4. Return of Materials
 | 
			
		||||
 | 
			
		||||
Upon termination of this Agreement, each party shall promptly return all documents and materials containing Confidential Information.
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
**IN WITNESS WHEREOF**, the parties have executed this Agreement as of the date first written above.
 | 
			
		||||
 | 
			
		||||
**[PARTY A]**                    **[PARTY B]**
 | 
			
		||||
 | 
			
		||||
_____________________           _____________________
 | 
			
		||||
Signature                       Signature
 | 
			
		||||
 | 
			
		||||
_____________________           _____________________
 | 
			
		||||
Print Name                      Print Name
 | 
			
		||||
 | 
			
		||||
_____________________           _____________________
 | 
			
		||||
Date                           Date`;
 | 
			
		||||
                break;
 | 
			
		||||
            case 'service':
 | 
			
		||||
                title = 'Service Agreement';
 | 
			
		||||
                description = 'Agreement for providing professional services';
 | 
			
		||||
                contractType = 'Service Agreement';
 | 
			
		||||
                content = 'This Service Agreement (the "Agreement") is made and entered into as of [DATE] by and between [SERVICE PROVIDER] and [CLIENT].\n\n1. Services to be Provided\n2. Compensation\n3. Term and Termination\n...';
 | 
			
		||||
                content = `# Service Agreement
 | 
			
		||||
 | 
			
		||||
This Service Agreement (the "**Agreement**") is made and entered into as of **[DATE]** by and between **[SERVICE PROVIDER]** and **[CLIENT]**.
 | 
			
		||||
 | 
			
		||||
## 1. Services to be Provided
 | 
			
		||||
 | 
			
		||||
The Service Provider agrees to provide the following services:
 | 
			
		||||
 | 
			
		||||
- **[SERVICE 1]**: Description of service
 | 
			
		||||
- **[SERVICE 2]**: Description of service
 | 
			
		||||
- **[SERVICE 3]**: Description of service
 | 
			
		||||
 | 
			
		||||
## 2. Compensation
 | 
			
		||||
 | 
			
		||||
| Service | Rate | Payment Terms |
 | 
			
		||||
|---------|------|---------------|
 | 
			
		||||
| [SERVICE 1] | $[AMOUNT] | [TERMS] |
 | 
			
		||||
| [SERVICE 2] | $[AMOUNT] | [TERMS] |
 | 
			
		||||
 | 
			
		||||
**Total Contract Value**: $[TOTAL_AMOUNT]
 | 
			
		||||
 | 
			
		||||
## 3. Payment Schedule
 | 
			
		||||
 | 
			
		||||
- **Deposit**: [PERCENTAGE]% upon signing
 | 
			
		||||
- **Milestone 1**: [PERCENTAGE]% upon [MILESTONE]
 | 
			
		||||
- **Final Payment**: [PERCENTAGE]% upon completion
 | 
			
		||||
 | 
			
		||||
## 4. Term and Termination
 | 
			
		||||
 | 
			
		||||
This Agreement shall commence on **[START_DATE]** and shall continue until **[END_DATE]** unless terminated earlier.
 | 
			
		||||
 | 
			
		||||
> **Important**: Either party may terminate this agreement with [NUMBER] days written notice.
 | 
			
		||||
 | 
			
		||||
## 5. Deliverables
 | 
			
		||||
 | 
			
		||||
The Service Provider shall deliver:
 | 
			
		||||
 | 
			
		||||
1. [DELIVERABLE 1]
 | 
			
		||||
2. [DELIVERABLE 2]
 | 
			
		||||
3. [DELIVERABLE 3]
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
**Service Provider**              **Client**
 | 
			
		||||
 | 
			
		||||
_____________________           _____________________
 | 
			
		||||
Signature                       Signature`;
 | 
			
		||||
                break;
 | 
			
		||||
            case 'employment':
 | 
			
		||||
                title = 'Employment Contract';
 | 
			
		||||
@@ -148,19 +268,19 @@
 | 
			
		||||
                content = 'This Service Level Agreement (the "SLA") is made and entered into as of [DATE] by and between [SERVICE PROVIDER] and [CLIENT].\n\n1. Service Levels\n2. Performance Metrics\n3. Remedies for Failure\n...';
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        document.getElementById('title').value = title;
 | 
			
		||||
        document.getElementById('description').value = description;
 | 
			
		||||
        document.getElementById('content').value = content;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Set the select option
 | 
			
		||||
        const selectElement = document.getElementById('contract_type');
 | 
			
		||||
        for(let i = 0; i < selectElement.options.length; i++) {
 | 
			
		||||
            if(selectElement.options[i].text === contractType) {
 | 
			
		||||
        for (let i = 0; i < selectElement.options.length; i++) {
 | 
			
		||||
            if (selectElement.options[i].text === contractType) {
 | 
			
		||||
                selectElement.selectedIndex = i;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										215
									
								
								actix_mvc_app/src/views/contracts/edit_contract.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								actix_mvc_app/src/views/contracts/edit_contract.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,215 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Edit Contract{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container-fluid">
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <nav aria-label="breadcrumb">
 | 
			
		||||
                <ol class="breadcrumb">
 | 
			
		||||
                    <li class="breadcrumb-item"><a href="/contracts">Contracts Dashboard</a></li>
 | 
			
		||||
                    <li class="breadcrumb-item"><a href="/contracts/{{ contract.id }}">{{ contract.title }}</a></li>
 | 
			
		||||
                    <li class="breadcrumb-item active" aria-current="page">Edit Contract</li>
 | 
			
		||||
                </ol>
 | 
			
		||||
            </nav>
 | 
			
		||||
            <h1 class="display-5 mb-3">Edit Contract</h1>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="col-lg-8">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Contract Details</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <form action="/contracts/{{ contract.id }}/edit" method="post">
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="title" class="form-label">Contract Title <span
 | 
			
		||||
                                    class="text-danger">*</span></label>
 | 
			
		||||
                            <input type="text" class="form-control" id="title" name="title" value="{{ contract.title }}"
 | 
			
		||||
                                required>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="contract_type" class="form-label">Contract Type <span
 | 
			
		||||
                                    class="text-danger">*</span></label>
 | 
			
		||||
                            <select class="form-select" id="contract_type" name="contract_type" required>
 | 
			
		||||
                                {% for type in contract_types %}
 | 
			
		||||
                                <option value="{{ type }}" {% if contract.contract_type==type %}selected{% endif %}>{{
 | 
			
		||||
                                    type }}</option>
 | 
			
		||||
                                {% endfor %}
 | 
			
		||||
                            </select>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="description" class="form-label">Description <span
 | 
			
		||||
                                    class="text-danger">*</span></label>
 | 
			
		||||
                            <textarea class="form-control" id="description" name="description" rows="3"
 | 
			
		||||
                                required>{{ contract.description }}</textarea>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="content" class="form-label">Contract Content</label>
 | 
			
		||||
                            <textarea class="form-control" id="content" name="content"
 | 
			
		||||
                                rows="10">{{ contract.terms_and_conditions | default(value='') }}</textarea>
 | 
			
		||||
                            <div class="form-text">Edit the contract content as needed.</div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="effective_date" class="form-label">Effective Date</label>
 | 
			
		||||
                            <input type="date" class="form-control" id="effective_date" name="effective_date"
 | 
			
		||||
                                value="{% if contract.start_date %}{{ contract.start_date | date(format='%Y-%m-%d') }}{% endif %}">
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="expiration_date" class="form-label">Expiration Date</label>
 | 
			
		||||
                            <input type="date" class="form-control" id="expiration_date" name="expiration_date"
 | 
			
		||||
                                value="{% if contract.end_date %}{{ contract.end_date | date(format='%Y-%m-%d') }}{% endif %}">
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="d-grid gap-2 d-md-flex justify-content-md-end">
 | 
			
		||||
                            <a href="/contracts/{{ contract.id }}" class="btn btn-outline-secondary me-md-2">Cancel</a>
 | 
			
		||||
                            <button type="submit" class="btn btn-primary">Update Contract</button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </form>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="col-lg-4">
 | 
			
		||||
            <div class="card mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Contract Info</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <p><strong>Status:</strong>
 | 
			
		||||
                        <span class="badge bg-secondary">{{ contract.status }}</span>
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <p><strong>Created:</strong> {{ contract.created_at | date(format="%Y-%m-%d %H:%M") }}</p>
 | 
			
		||||
                    <p><strong>Last Updated:</strong> {{ contract.updated_at | date(format="%Y-%m-%d %H:%M") }}</p>
 | 
			
		||||
                    <p><strong>Version:</strong> {{ contract.current_version }}</p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="card mb-4">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Edit Notes</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="alert alert-info">
 | 
			
		||||
                        <i class="bi bi-info-circle me-2"></i>
 | 
			
		||||
                        <strong>Note:</strong> Only contracts in <strong>Draft</strong> status can be edited.
 | 
			
		||||
                        Once a contract is sent for signatures, you'll need to create a new revision instead.
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <p>After updating the contract:</p>
 | 
			
		||||
                    <ul>
 | 
			
		||||
                        <li>The contract will remain in Draft status</li>
 | 
			
		||||
                        <li>You can continue to make changes</li>
 | 
			
		||||
                        <li>Add signers when ready</li>
 | 
			
		||||
                        <li>Send for signatures when complete</li>
 | 
			
		||||
                    </ul>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Quick Actions</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="d-grid gap-2">
 | 
			
		||||
                        <a href="/contracts/{{ contract.id }}" class="btn btn-outline-primary">
 | 
			
		||||
                            <i class="bi bi-eye me-1"></i> View Contract
 | 
			
		||||
                        </a>
 | 
			
		||||
                        <a href="/contracts/{{ contract.id }}/add-signer" class="btn btn-outline-success">
 | 
			
		||||
                            <i class="bi bi-person-plus me-1"></i> Add Signer
 | 
			
		||||
                        </a>
 | 
			
		||||
                        <button class="btn btn-outline-danger"
 | 
			
		||||
                            onclick="deleteContract({{ contract.id }}, '{{ contract.title | replace(from="'", to="\\'") }}')">
 | 
			
		||||
                            <i class="bi bi-trash me-1"></i> Delete Contract
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- Delete Confirmation Modal -->
 | 
			
		||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
 | 
			
		||||
    <div class="modal-dialog">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <h5 class="modal-title" id="deleteModalLabel">Delete Contract</h5>
 | 
			
		||||
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-body">
 | 
			
		||||
                <div class="alert alert-danger">
 | 
			
		||||
                    <i class="bi bi-exclamation-triangle me-2"></i>
 | 
			
		||||
                    <strong>Warning:</strong> This action cannot be undone!
 | 
			
		||||
                </div>
 | 
			
		||||
                <p>Are you sure you want to delete the contract "<strong id="contractTitle"></strong>"?</p>
 | 
			
		||||
                <p>This will permanently remove the contract and all its associated data.</p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
 | 
			
		||||
                <button type="button" class="btn btn-danger" id="confirmDeleteBtn">
 | 
			
		||||
                    <i class="bi bi-trash me-1"></i> Delete Contract
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block extra_js %}
 | 
			
		||||
<script>
 | 
			
		||||
    console.log('Edit contract scripts loading...');
 | 
			
		||||
 | 
			
		||||
    // Delete function using Bootstrap modal
 | 
			
		||||
    window.deleteContract = function (contractId, contractTitle) {
 | 
			
		||||
        console.log('Delete function called:', contractId, contractTitle);
 | 
			
		||||
 | 
			
		||||
        // Set the contract title in the modal
 | 
			
		||||
        document.getElementById('contractTitle').textContent = contractTitle;
 | 
			
		||||
 | 
			
		||||
        // Store the contract ID for later use
 | 
			
		||||
        window.currentDeleteContractId = contractId;
 | 
			
		||||
 | 
			
		||||
        // Show the modal
 | 
			
		||||
        const deleteModal = new bootstrap.Modal(document.getElementById('deleteModal'));
 | 
			
		||||
        deleteModal.show();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    console.log('deleteContract function defined:', typeof window.deleteContract);
 | 
			
		||||
 | 
			
		||||
    document.addEventListener('DOMContentLoaded', function () {
 | 
			
		||||
        // Handle confirm delete button click
 | 
			
		||||
        document.getElementById('confirmDeleteBtn').addEventListener('click', function () {
 | 
			
		||||
            console.log('User confirmed deletion, submitting form...');
 | 
			
		||||
 | 
			
		||||
            // Create and submit form
 | 
			
		||||
            const form = document.createElement('form');
 | 
			
		||||
            form.method = 'POST';
 | 
			
		||||
            form.action = '/contracts/' + window.currentDeleteContractId + '/delete';
 | 
			
		||||
            form.style.display = 'none';
 | 
			
		||||
 | 
			
		||||
            document.body.appendChild(form);
 | 
			
		||||
            form.submit();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Auto-resize textarea
 | 
			
		||||
        const textarea = document.getElementById('content');
 | 
			
		||||
        if (textarea) {
 | 
			
		||||
            textarea.addEventListener('input', function () {
 | 
			
		||||
                this.style.height = 'auto';
 | 
			
		||||
                this.style.height = this.scrollHeight + 'px';
 | 
			
		||||
            });
 | 
			
		||||
            // Initial resize
 | 
			
		||||
            textarea.style.height = textarea.scrollHeight + 'px';
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -11,58 +11,108 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    {% if stats.total_contracts > 0 %}
 | 
			
		||||
    <!-- Statistics Cards -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-md-2 mb-3">
 | 
			
		||||
            <div class="card text-white bg-primary h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h5 class="card-title">Total</h5>
 | 
			
		||||
                    <p class="display-4">{{ stats.total_contracts }}</p>
 | 
			
		||||
                <div class="card-body text-center">
 | 
			
		||||
                    <h5 class="card-title mb-1">Total</h5>
 | 
			
		||||
                    <h3 class="mb-0">{{ stats.total_contracts }}</h3>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-2 mb-3">
 | 
			
		||||
            <div class="card text-white bg-secondary h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h5 class="card-title">Draft</h5>
 | 
			
		||||
                    <p class="display-4">{{ stats.draft_contracts }}</p>
 | 
			
		||||
                <div class="card-body text-center">
 | 
			
		||||
                    <h5 class="card-title mb-1">Draft</h5>
 | 
			
		||||
                    <h3 class="mb-0">{{ stats.draft_contracts }}</h3>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-2 mb-3">
 | 
			
		||||
            <div class="card text-white bg-warning h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h5 class="card-title">Pending</h5>
 | 
			
		||||
                    <p class="display-4">{{ stats.pending_signature_contracts }}</p>
 | 
			
		||||
                <div class="card-body text-center">
 | 
			
		||||
                    <h5 class="card-title mb-1">Pending</h5>
 | 
			
		||||
                    <h3 class="mb-0">{{ stats.pending_signature_contracts }}</h3>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-2 mb-3">
 | 
			
		||||
            <div class="card text-white bg-success h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h5 class="card-title">Signed</h5>
 | 
			
		||||
                    <p class="display-4">{{ stats.signed_contracts }}</p>
 | 
			
		||||
                <div class="card-body text-center">
 | 
			
		||||
                    <h5 class="card-title mb-1">Signed</h5>
 | 
			
		||||
                    <h3 class="mb-0">{{ stats.signed_contracts }}</h3>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-2 mb-3">
 | 
			
		||||
            <div class="card text-white bg-danger h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h5 class="card-title">Expired</h5>
 | 
			
		||||
                    <p class="display-4">{{ stats.expired_contracts }}</p>
 | 
			
		||||
                <div class="card-body text-center">
 | 
			
		||||
                    <h5 class="card-title mb-1">Expired</h5>
 | 
			
		||||
                    <h3 class="mb-0">{{ stats.expired_contracts }}</h3>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-2 mb-3">
 | 
			
		||||
            <div class="card text-white bg-dark h-100">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <h5 class="card-title">Cancelled</h5>
 | 
			
		||||
                    <p class="display-4">{{ stats.cancelled_contracts }}</p>
 | 
			
		||||
                <div class="card-body text-center">
 | 
			
		||||
                    <h5 class="card-title mb-1">Cancelled</h5>
 | 
			
		||||
                    <h3 class="mb-0">{{ stats.cancelled_contracts }}</h3>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% else %}
 | 
			
		||||
    <!-- Empty State Welcome Message -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card border-0 bg-light">
 | 
			
		||||
                <div class="card-body text-center py-5">
 | 
			
		||||
                    <div class="mb-4">
 | 
			
		||||
                        <i class="bi bi-file-earmark-text display-1 text-muted"></i>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <h3 class="text-muted mb-3">Welcome to Contract Management</h3>
 | 
			
		||||
                    <p class="lead text-muted mb-4">
 | 
			
		||||
                        You haven't created any contracts yet. Get started by creating your first contract to manage
 | 
			
		||||
                        legal agreements and track signatures.
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <div class="row justify-content-center">
 | 
			
		||||
                        <div class="col-md-6">
 | 
			
		||||
                            <div class="row g-3">
 | 
			
		||||
                                <div class="col-md-6">
 | 
			
		||||
                                    <div class="card h-100 border-primary">
 | 
			
		||||
                                        <div class="card-body text-center">
 | 
			
		||||
                                            <i class="bi bi-plus-circle text-primary fs-2 mb-2"></i>
 | 
			
		||||
                                            <h6 class="card-title">Create Contract</h6>
 | 
			
		||||
                                            <p class="card-text small text-muted">Start with a new legal agreement</p>
 | 
			
		||||
                                            <a href="/contracts/create" class="btn btn-primary btn-sm">Get Started</a>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="col-md-6">
 | 
			
		||||
                                    <div class="card h-100 border-success">
 | 
			
		||||
                                        <div class="card-body text-center">
 | 
			
		||||
                                            <i class="bi bi-question-circle text-success fs-2 mb-2"></i>
 | 
			
		||||
                                            <h6 class="card-title">Need Help?</h6>
 | 
			
		||||
                                            <p class="card-text small text-muted">Learn how to use the system</p>
 | 
			
		||||
                                            <button class="btn btn-outline-success btn-sm"
 | 
			
		||||
                                                onclick="showHelpModal()">Learn More</button>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    {% if stats.total_contracts > 0 %}
 | 
			
		||||
    <!-- Quick Actions -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
@@ -86,6 +136,7 @@
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
    <!-- Pending Signature Contracts -->
 | 
			
		||||
    {% if pending_signature_contracts and pending_signature_contracts | length > 0 %}
 | 
			
		||||
@@ -168,7 +219,8 @@
 | 
			
		||||
                                            <a href="/contracts/{{ contract.id }}" class="btn btn-sm btn-primary">
 | 
			
		||||
                                                <i class="bi bi-eye"></i>
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                            <a href="/contracts/{{ contract.id }}/edit" class="btn btn-sm btn-outline-secondary">
 | 
			
		||||
                                            <a href="/contracts/{{ contract.id }}/edit"
 | 
			
		||||
                                                class="btn btn-sm btn-outline-secondary">
 | 
			
		||||
                                                <i class="bi bi-pencil"></i>
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                        </div>
 | 
			
		||||
@@ -183,5 +235,115 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
    <!-- Recent Activity Section -->
 | 
			
		||||
    {% if recent_activities and recent_activities | length > 0 %}
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Recent Activity</h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body p-0">
 | 
			
		||||
                    <div class="list-group list-group-flush">
 | 
			
		||||
                        {% for activity in recent_activities %}
 | 
			
		||||
                        <div class="list-group-item border-start-0 border-end-0 py-3">
 | 
			
		||||
                            <div class="d-flex">
 | 
			
		||||
                                <div class="me-3">
 | 
			
		||||
                                    <i class="{{ activity.icon }} fs-5"></i>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="flex-grow-1">
 | 
			
		||||
                                    <div class="d-flex justify-content-between align-items-center">
 | 
			
		||||
                                        <strong>{{ activity.user }}</strong>
 | 
			
		||||
                                        <small class="text-muted">{{ activity.timestamp | date(format="%H:%M")
 | 
			
		||||
                                            }}</small>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <p class="mb-1">{{ activity.description }}</p>
 | 
			
		||||
                                    <small class="text-muted">{{ activity.title }}</small>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-footer text-center">
 | 
			
		||||
                    <a href="/contracts/activities" class="btn btn-sm btn-outline-info">See More Activities</a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- Help Modal -->
 | 
			
		||||
<div class="modal fade" id="helpModal" tabindex="-1" aria-labelledby="helpModalLabel" aria-hidden="true">
 | 
			
		||||
    <div class="modal-dialog modal-lg">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <h5 class="modal-title" id="helpModalLabel">
 | 
			
		||||
                    <i class="bi bi-question-circle me-2"></i>Getting Started with Contract Management
 | 
			
		||||
                </h5>
 | 
			
		||||
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-body">
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col-md-6">
 | 
			
		||||
                        <h6><i class="bi bi-1-circle text-primary me-2"></i>Create Your First Contract</h6>
 | 
			
		||||
                        <p class="small text-muted mb-3">
 | 
			
		||||
                            Start by creating a new contract. Choose from various contract types like Service
 | 
			
		||||
                            Agreements, NDAs, or Employment Contracts.
 | 
			
		||||
                        </p>
 | 
			
		||||
 | 
			
		||||
                        <h6><i class="bi bi-2-circle text-primary me-2"></i>Add Contract Details</h6>
 | 
			
		||||
                        <p class="small text-muted mb-3">
 | 
			
		||||
                            Fill in the contract title, description, and terms. You can use Markdown formatting for rich
 | 
			
		||||
                            text content.
 | 
			
		||||
                        </p>
 | 
			
		||||
 | 
			
		||||
                        <h6><i class="bi bi-3-circle text-primary me-2"></i>Add Signers</h6>
 | 
			
		||||
                        <p class="small text-muted mb-3">
 | 
			
		||||
                            Add people who need to sign the contract. Each signer will receive a unique signing link.
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col-md-6">
 | 
			
		||||
                        <h6><i class="bi bi-4-circle text-success me-2"></i>Send for Signatures</h6>
 | 
			
		||||
                        <p class="small text-muted mb-3">
 | 
			
		||||
                            Once your contract is ready, send it for signatures. Signers can review and sign digitally.
 | 
			
		||||
                        </p>
 | 
			
		||||
 | 
			
		||||
                        <h6><i class="bi bi-5-circle text-success me-2"></i>Track Progress</h6>
 | 
			
		||||
                        <p class="small text-muted mb-3">
 | 
			
		||||
                            Monitor signature progress, send reminders, and view signed documents from the dashboard.
 | 
			
		||||
                        </p>
 | 
			
		||||
 | 
			
		||||
                        <h6><i class="bi bi-6-circle text-success me-2"></i>Manage Contracts</h6>
 | 
			
		||||
                        <p class="small text-muted mb-3">
 | 
			
		||||
                            View all contracts, filter by status, and manage the complete contract lifecycle.
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="alert alert-info mt-3">
 | 
			
		||||
                    <i class="bi bi-lightbulb me-2"></i>
 | 
			
		||||
                    <strong>Tip:</strong> You can save contracts as drafts and come back to edit them later before
 | 
			
		||||
                    sending for signatures.
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
 | 
			
		||||
                <a href="/contracts/create" class="btn btn-primary">
 | 
			
		||||
                    <i class="bi bi-plus-circle me-1"></i> Create My First Contract
 | 
			
		||||
                </a>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block extra_js %}
 | 
			
		||||
<script>
 | 
			
		||||
    function showHelpModal() {
 | 
			
		||||
        const helpModal = new bootstrap.Modal(document.getElementById('helpModal'));
 | 
			
		||||
        helpModal.show();
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -13,7 +13,10 @@
 | 
			
		||||
                </ol>
 | 
			
		||||
            </nav>
 | 
			
		||||
            <div class="d-flex justify-content-between align-items-center">
 | 
			
		||||
                <h1 class="display-5 mb-0">My Contracts</h1>
 | 
			
		||||
                <div>
 | 
			
		||||
                    <h1 class="display-5 mb-0">My Contracts</h1>
 | 
			
		||||
                    <p class="text-muted mb-0">Manage and track your personal contracts</p>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="btn-group">
 | 
			
		||||
                    <a href="/contracts/create" class="btn btn-primary">
 | 
			
		||||
                        <i class="bi bi-plus-circle me-1"></i> Create New Contract
 | 
			
		||||
@@ -23,41 +26,136 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Quick Stats -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-md-3">
 | 
			
		||||
            <div class="card bg-primary text-white">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="d-flex justify-content-between">
 | 
			
		||||
                        <div>
 | 
			
		||||
                            <h6 class="card-title">Total Contracts</h6>
 | 
			
		||||
                            <h3 class="mb-0">{{ contracts|length }}</h3>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="align-self-center">
 | 
			
		||||
                            <i class="bi bi-file-earmark-text fs-2"></i>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-3">
 | 
			
		||||
            <div class="card bg-warning text-white">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="d-flex justify-content-between">
 | 
			
		||||
                        <div>
 | 
			
		||||
                            <h6 class="card-title">Pending Signatures</h6>
 | 
			
		||||
                            <h3 class="mb-0" id="pending-count">0</h3>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="align-self-center">
 | 
			
		||||
                            <i class="bi bi-clock fs-2"></i>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-3">
 | 
			
		||||
            <div class="card bg-success text-white">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="d-flex justify-content-between">
 | 
			
		||||
                        <div>
 | 
			
		||||
                            <h6 class="card-title">Signed</h6>
 | 
			
		||||
                            <h3 class="mb-0" id="signed-count">0</h3>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="align-self-center">
 | 
			
		||||
                            <i class="bi bi-check-circle fs-2"></i>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-3">
 | 
			
		||||
            <div class="card bg-secondary text-white">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="d-flex justify-content-between">
 | 
			
		||||
                        <div>
 | 
			
		||||
                            <h6 class="card-title">Drafts</h6>
 | 
			
		||||
                            <h3 class="mb-0" id="draft-count">0</h3>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="align-self-center">
 | 
			
		||||
                            <i class="bi bi-pencil fs-2"></i>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Filters -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">Filters</h5>
 | 
			
		||||
                <div class="card-header d-flex justify-content-between align-items-center">
 | 
			
		||||
                    <h5 class="mb-0">
 | 
			
		||||
                        <i class="bi bi-funnel me-1"></i> Filters & Search
 | 
			
		||||
                    </h5>
 | 
			
		||||
                    <button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse"
 | 
			
		||||
                        data-bs-target="#filtersCollapse" aria-expanded="false" aria-controls="filtersCollapse">
 | 
			
		||||
                        <i class="bi bi-chevron-down"></i> Toggle Filters
 | 
			
		||||
                    </button>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <form action="/contracts/my-contracts" 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="PendingSignatures">Pending Signatures</option>
 | 
			
		||||
                                <option value="Signed">Signed</option>
 | 
			
		||||
                                <option value="Expired">Expired</option>
 | 
			
		||||
                                <option value="Cancelled">Cancelled</option>
 | 
			
		||||
                            </select>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-md-4">
 | 
			
		||||
                            <label for="type" class="form-label">Contract Type</label>
 | 
			
		||||
                            <select class="form-select" id="type" name="type">
 | 
			
		||||
                                <option value="">All Types</option>
 | 
			
		||||
                                <option value="Service">Service Agreement</option>
 | 
			
		||||
                                <option value="Employment">Employment Contract</option>
 | 
			
		||||
                                <option value="NDA">Non-Disclosure Agreement</option>
 | 
			
		||||
                                <option value="SLA">Service Level Agreement</option>
 | 
			
		||||
                                <option value="Other">Other</option>
 | 
			
		||||
                            </select>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-md-4 d-flex align-items-end">
 | 
			
		||||
                            <button type="submit" class="btn btn-primary w-100">Apply Filters</button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </form>
 | 
			
		||||
                <div class="collapse show" id="filtersCollapse">
 | 
			
		||||
                    <div class="card-body">
 | 
			
		||||
                        <form action="/contracts/my-contracts" method="get" class="row g-3">
 | 
			
		||||
                            <div class="col-md-3">
 | 
			
		||||
                                <label for="status" class="form-label">Status</label>
 | 
			
		||||
                                <select class="form-select" id="status" name="status">
 | 
			
		||||
                                    <option value="">All Statuses</option>
 | 
			
		||||
                                    <option value="Draft" {% if current_status_filter=="Draft" %}selected{% endif %}>
 | 
			
		||||
                                        Draft</option>
 | 
			
		||||
                                    <option value="PendingSignatures" {% if current_status_filter=="PendingSignatures"
 | 
			
		||||
                                        %}selected{% endif %}>Pending Signatures</option>
 | 
			
		||||
                                    <option value="Signed" {% if current_status_filter=="Signed" %}selected{% endif %}>
 | 
			
		||||
                                        Signed</option>
 | 
			
		||||
                                    <option value="Active" {% if current_status_filter=="Active" %}selected{% endif %}>
 | 
			
		||||
                                        Active</option>
 | 
			
		||||
                                    <option value="Expired" {% if current_status_filter=="Expired" %}selected{% endif
 | 
			
		||||
                                        %}>Expired</option>
 | 
			
		||||
                                    <option value="Cancelled" {% if current_status_filter=="Cancelled" %}selected{%
 | 
			
		||||
                                        endif %}>Cancelled</option>
 | 
			
		||||
                                </select>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="col-md-3">
 | 
			
		||||
                                <label for="type" class="form-label">Contract Type</label>
 | 
			
		||||
                                <select class="form-select" id="type" name="type">
 | 
			
		||||
                                    <option value="">All Types</option>
 | 
			
		||||
                                    <option value="Service Agreement" {% if current_type_filter=="Service Agreement"
 | 
			
		||||
                                        %}selected{% endif %}>Service Agreement</option>
 | 
			
		||||
                                    <option value="Employment Contract" {% if current_type_filter=="Employment Contract"
 | 
			
		||||
                                        %}selected{% endif %}>Employment Contract</option>
 | 
			
		||||
                                    <option value="Non-Disclosure Agreement" {% if
 | 
			
		||||
                                        current_type_filter=="Non-Disclosure Agreement" %}selected{% endif %}>
 | 
			
		||||
                                        Non-Disclosure Agreement</option>
 | 
			
		||||
                                    <option value="Service Level Agreement" {% if
 | 
			
		||||
                                        current_type_filter=="Service Level Agreement" %}selected{% endif %}>Service
 | 
			
		||||
                                        Level Agreement</option>
 | 
			
		||||
                                    <option value="Partnership Agreement" {% if
 | 
			
		||||
                                        current_type_filter=="Partnership Agreement" %}selected{% endif %}>Partnership
 | 
			
		||||
                                        Agreement</option>
 | 
			
		||||
                                    <option value="Other" {% if current_type_filter=="Other" %}selected{% endif %}>Other
 | 
			
		||||
                                    </option>
 | 
			
		||||
                                </select>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="col-md-3">
 | 
			
		||||
                                <label for="search" class="form-label">Search</label>
 | 
			
		||||
                                <input type="text" class="form-control" id="search" name="search"
 | 
			
		||||
                                    placeholder="Search by title or description"
 | 
			
		||||
                                    value="{{ current_search_filter | default(value='') }}">
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="col-md-3 d-flex align-items-end">
 | 
			
		||||
                                <button type="submit" class="btn btn-primary w-100">Apply Filters</button>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </form>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -67,48 +165,122 @@
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">My Contracts</h5>
 | 
			
		||||
                <div class="card-header d-flex justify-content-between align-items-center">
 | 
			
		||||
                    <h5 class="mb-0">
 | 
			
		||||
                        <i class="bi bi-file-earmark-text me-1"></i> My Contracts
 | 
			
		||||
                        {% if contracts and contracts | length > 0 %}
 | 
			
		||||
                        <span class="badge bg-primary ms-2">{{ contracts|length }}</span>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    </h5>
 | 
			
		||||
                    <div class="btn-group">
 | 
			
		||||
                        <a href="/contracts/statistics" class="btn btn-sm btn-outline-secondary">
 | 
			
		||||
                            <i class="bi bi-graph-up me-1"></i> Statistics
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% if contracts and contracts | length > 0 %}
 | 
			
		||||
                    <div class="table-responsive">
 | 
			
		||||
                        <table class="table table-hover">
 | 
			
		||||
                            <thead>
 | 
			
		||||
                        <table class="table table-hover align-middle">
 | 
			
		||||
                            <thead class="table-light">
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <th>Contract Title</th>
 | 
			
		||||
                                    <th>Type</th>
 | 
			
		||||
                                    <th>Status</th>
 | 
			
		||||
                                    <th>Signers</th>
 | 
			
		||||
                                    <th>Created</th>
 | 
			
		||||
                                    <th>Updated</th>
 | 
			
		||||
                                    <th>Actions</th>
 | 
			
		||||
                                    <th scope="col">
 | 
			
		||||
                                        <div class="d-flex align-items-center">
 | 
			
		||||
                                            Contract Title
 | 
			
		||||
                                            <i class="bi bi-arrow-down-up ms-1 text-muted" style="cursor: pointer;"
 | 
			
		||||
                                                onclick="sortTable(0)"></i>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </th>
 | 
			
		||||
                                    <th scope="col">Type</th>
 | 
			
		||||
                                    <th scope="col">Status</th>
 | 
			
		||||
                                    <th scope="col">Progress</th>
 | 
			
		||||
                                    <th scope="col">
 | 
			
		||||
                                        <div class="d-flex align-items-center">
 | 
			
		||||
                                            Created
 | 
			
		||||
                                            <i class="bi bi-arrow-down-up ms-1 text-muted" style="cursor: pointer;"
 | 
			
		||||
                                                onclick="sortTable(4)"></i>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </th>
 | 
			
		||||
                                    <th scope="col">Last Updated</th>
 | 
			
		||||
                                    <th scope="col" class="text-center">Actions</th>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                            </thead>
 | 
			
		||||
                            <tbody>
 | 
			
		||||
                                {% for contract in contracts %}
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                <tr
 | 
			
		||||
                                    class="{% if contract.status == 'Expired' %}table-danger{% elif contract.status == 'PendingSignatures' %}table-warning{% elif contract.status == 'Signed' %}table-success{% endif %}">
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <a href="/contracts/{{ contract.id }}">{{ contract.title }}</a>
 | 
			
		||||
                                        <div>
 | 
			
		||||
                                            <a href="/contracts/{{ contract.id }}" class="fw-bold text-decoration-none">
 | 
			
		||||
                                                {{ contract.title }}
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                            {% if contract.description %}
 | 
			
		||||
                                            <div class="small text-muted">{{ contract.description }}</div>
 | 
			
		||||
                                            {% endif %}
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>{{ contract.contract_type }}</td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <span class="badge {% if contract.status == 'Signed' %}bg-success{% elif contract.status == 'PendingSignatures' %}bg-warning text-dark{% elif contract.status == 'Draft' %}bg-secondary{% elif contract.status == 'Expired' %}bg-danger{% else %}bg-dark{% endif %}">
 | 
			
		||||
                                        <span class="badge bg-light text-dark">{{ contract.contract_type }}</span>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <span
 | 
			
		||||
                                            class="badge {% if contract.status == 'Signed' or contract.status == 'Active' %}bg-success{% elif contract.status == 'PendingSignatures' %}bg-warning text-dark{% elif contract.status == 'Draft' %}bg-secondary{% elif contract.status == 'Expired' %}bg-danger{% elif contract.status == 'Cancelled' %}bg-dark{% else %}bg-info{% endif %}">
 | 
			
		||||
                                            {% if contract.status == 'PendingSignatures' %}
 | 
			
		||||
                                            <i class="bi bi-clock me-1"></i>
 | 
			
		||||
                                            {% elif contract.status == 'Signed' %}
 | 
			
		||||
                                            <i class="bi bi-check-circle me-1"></i>
 | 
			
		||||
                                            {% elif contract.status == 'Draft' %}
 | 
			
		||||
                                            <i class="bi bi-pencil me-1"></i>
 | 
			
		||||
                                            {% elif contract.status == 'Expired' %}
 | 
			
		||||
                                            <i class="bi bi-exclamation-triangle me-1"></i>
 | 
			
		||||
                                            {% endif %}
 | 
			
		||||
                                            {{ contract.status }}
 | 
			
		||||
                                        </span>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>{{ contract.signed_signers }}/{{ contract.signers|length }}</td>
 | 
			
		||||
                                    <td>{{ contract.created_at | date(format="%Y-%m-%d") }}</td>
 | 
			
		||||
                                    <td>{{ contract.updated_at | date(format="%Y-%m-%d") }}</td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        {% if contract.signers|length > 0 %}
 | 
			
		||||
                                        <div class="d-flex align-items-center">
 | 
			
		||||
                                            <div class="progress me-2" style="width: 60px; height: 8px;">
 | 
			
		||||
                                                <div class="progress-bar bg-success" role="progressbar"
 | 
			
		||||
                                                    style="width: 0%" data-contract-id="{{ contract.id }}">
 | 
			
		||||
                                                </div>
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                            <small class="text-muted">{{ contract.signed_signers }}/{{
 | 
			
		||||
                                                contract.signers|length }}</small>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                        {% else %}
 | 
			
		||||
                                        <span class="text-muted small">No signers</span>
 | 
			
		||||
                                        {% endif %}
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <div class="small">
 | 
			
		||||
                                            {{ contract.created_at | date(format="%b %d, %Y") }}
 | 
			
		||||
                                            <div class="text-muted">{{ contract.created_at | date(format="%I:%M %p") }}
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td>
 | 
			
		||||
                                        <div class="small">
 | 
			
		||||
                                            {{ contract.updated_at | date(format="%b %d, %Y") }}
 | 
			
		||||
                                            <div class="text-muted">{{ contract.updated_at | date(format="%I:%M %p") }}
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                    <td class="text-center">
 | 
			
		||||
                                        <div class="btn-group">
 | 
			
		||||
                                            <a href="/contracts/{{ contract.id }}" class="btn btn-sm btn-primary">
 | 
			
		||||
                                            <a href="/contracts/{{ contract.id }}" class="btn btn-sm btn-primary"
 | 
			
		||||
                                                title="View Details">
 | 
			
		||||
                                                <i class="bi bi-eye"></i>
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                            {% if contract.status == 'Draft' %}
 | 
			
		||||
                                            <a href="/contracts/{{ contract.id }}/edit" class="btn btn-sm btn-outline-secondary">
 | 
			
		||||
                                            <a href="/contracts/{{ contract.id }}/edit"
 | 
			
		||||
                                                class="btn btn-sm btn-outline-secondary" title="Edit Contract">
 | 
			
		||||
                                                <i class="bi bi-pencil"></i>
 | 
			
		||||
                                            </a>
 | 
			
		||||
                                            <button class="btn btn-sm btn-outline-danger" title="Delete Contract"
 | 
			
		||||
                                                onclick="deleteContract('{{ contract.id }}', '{{ contract.title }}')">
 | 
			
		||||
                                                <i class="bi bi-trash"></i>
 | 
			
		||||
                                            </button>
 | 
			
		||||
                                            {% endif %}
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </td>
 | 
			
		||||
@@ -119,11 +291,20 @@
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                    <div class="text-center py-5">
 | 
			
		||||
                        <i class="bi bi-file-earmark-text fs-1 text-muted"></i>
 | 
			
		||||
                        <p class="mt-3 text-muted">You don't have any contracts yet</p>
 | 
			
		||||
                        <a href="/contracts/create" class="btn btn-primary mt-2">
 | 
			
		||||
                            <i class="bi bi-plus-circle me-1"></i> Create Your First Contract
 | 
			
		||||
                        </a>
 | 
			
		||||
                        <div class="mb-4">
 | 
			
		||||
                            <i class="bi bi-file-earmark-text display-1 text-muted"></i>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <h4 class="text-muted mb-3">No Contracts Found</h4>
 | 
			
		||||
                        <p class="text-muted mb-4">You haven't created any contracts yet. Get started by creating your
 | 
			
		||||
                            first contract.</p>
 | 
			
		||||
                        <div class="d-flex justify-content-center gap-2">
 | 
			
		||||
                            <a href="/contracts/create" class="btn btn-primary">
 | 
			
		||||
                                <i class="bi bi-plus-circle me-1"></i> Create Your First Contract
 | 
			
		||||
                            </a>
 | 
			
		||||
                            <a href="/contracts" class="btn btn-outline-secondary">
 | 
			
		||||
                                <i class="bi bi-arrow-left me-1"></i> Back to Dashboard
 | 
			
		||||
                            </a>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
@@ -131,4 +312,166 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<!-- Delete Confirmation Modal -->
 | 
			
		||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
 | 
			
		||||
    <div class="modal-dialog">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <h5 class="modal-title" id="deleteModalLabel">Delete Contract</h5>
 | 
			
		||||
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-body">
 | 
			
		||||
                <div class="alert alert-danger">
 | 
			
		||||
                    <i class="bi bi-exclamation-triangle me-2"></i>
 | 
			
		||||
                    <strong>Warning:</strong> This action cannot be undone!
 | 
			
		||||
                </div>
 | 
			
		||||
                <p>Are you sure you want to delete the contract "<strong id="contractTitle"></strong>"?</p>
 | 
			
		||||
                <p>This will permanently remove:</p>
 | 
			
		||||
                <ul>
 | 
			
		||||
                    <li>The contract document and all its content</li>
 | 
			
		||||
                    <li>All signers and their signatures</li>
 | 
			
		||||
                    <li>All revisions and history</li>
 | 
			
		||||
                    <li>Any associated files or attachments</li>
 | 
			
		||||
                </ul>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-footer">
 | 
			
		||||
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
 | 
			
		||||
                <button type="button" class="btn btn-danger" id="confirmDeleteBtn">
 | 
			
		||||
                    <i class="bi bi-trash me-1"></i> Delete Contract
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block extra_js %}
 | 
			
		||||
<script>
 | 
			
		||||
    console.log('My Contracts page scripts loading...');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // Delete contract functionality using Bootstrap modal
 | 
			
		||||
    window.deleteContract = function (contractId, contractTitle) {
 | 
			
		||||
        console.log('Delete contract called:', contractId, contractTitle);
 | 
			
		||||
 | 
			
		||||
        // Set the contract title in the modal
 | 
			
		||||
        document.getElementById('contractTitle').textContent = contractTitle;
 | 
			
		||||
 | 
			
		||||
        // Store the contract ID for later use
 | 
			
		||||
        window.currentDeleteContractId = contractId;
 | 
			
		||||
 | 
			
		||||
        // Show the modal
 | 
			
		||||
        const deleteModal = new bootstrap.Modal(document.getElementById('deleteModal'));
 | 
			
		||||
        deleteModal.show();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Simple table sorting functionality
 | 
			
		||||
    window.sortTable = function (columnIndex) {
 | 
			
		||||
        console.log('Sorting table by column:', columnIndex);
 | 
			
		||||
        const table = document.querySelector('.table tbody');
 | 
			
		||||
        const rows = Array.from(table.querySelectorAll('tr'));
 | 
			
		||||
 | 
			
		||||
        // Toggle sort direction
 | 
			
		||||
        const isAscending = table.dataset.sortDirection !== 'asc';
 | 
			
		||||
        table.dataset.sortDirection = isAscending ? 'asc' : 'desc';
 | 
			
		||||
 | 
			
		||||
        rows.sort((a, b) => {
 | 
			
		||||
            const aText = a.cells[columnIndex].textContent.trim();
 | 
			
		||||
            const bText = b.cells[columnIndex].textContent.trim();
 | 
			
		||||
 | 
			
		||||
            // Handle date sorting for created/updated columns
 | 
			
		||||
            if (columnIndex === 4 || columnIndex === 5) {
 | 
			
		||||
                const aDate = new Date(aText);
 | 
			
		||||
                const bDate = new Date(bText);
 | 
			
		||||
                return isAscending ? aDate - bDate : bDate - aDate;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Handle text sorting
 | 
			
		||||
            return isAscending ? aText.localeCompare(bText) : bText.localeCompare(aText);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Re-append sorted rows
 | 
			
		||||
        rows.forEach(row => table.appendChild(row));
 | 
			
		||||
 | 
			
		||||
        // Update sort indicators
 | 
			
		||||
        document.querySelectorAll('.bi-arrow-down-up').forEach(icon => {
 | 
			
		||||
            icon.className = 'bi bi-arrow-down-up ms-1 text-muted';
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const currentIcon = document.querySelectorAll('.bi-arrow-down-up')[columnIndex === 4 ? 1 : 0];
 | 
			
		||||
        if (currentIcon) {
 | 
			
		||||
            currentIcon.className = `bi ${isAscending ? 'bi-arrow-up' : 'bi-arrow-down'} ms-1 text-primary`;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Calculate statistics and update progress bars
 | 
			
		||||
    function updateStatistics() {
 | 
			
		||||
        const rows = document.querySelectorAll('.table tbody tr');
 | 
			
		||||
        let totalContracts = rows.length;
 | 
			
		||||
        let pendingCount = 0;
 | 
			
		||||
        let signedCount = 0;
 | 
			
		||||
        let draftCount = 0;
 | 
			
		||||
 | 
			
		||||
        rows.forEach(row => {
 | 
			
		||||
            const statusCell = row.cells[2];
 | 
			
		||||
            const statusText = statusCell.textContent.trim();
 | 
			
		||||
 | 
			
		||||
            if (statusText.includes('PendingSignatures') || statusText.includes('Pending')) {
 | 
			
		||||
                pendingCount++;
 | 
			
		||||
            } else if (statusText.includes('Signed')) {
 | 
			
		||||
                signedCount++;
 | 
			
		||||
            } else if (statusText.includes('Draft')) {
 | 
			
		||||
                draftCount++;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Update progress bars
 | 
			
		||||
            const progressBar = row.querySelector('.progress-bar');
 | 
			
		||||
            if (progressBar) {
 | 
			
		||||
                const signersText = row.cells[3].textContent.trim();
 | 
			
		||||
                if (signersText !== 'No signers') {
 | 
			
		||||
                    const [signed, total] = signersText.split('/').map(n => parseInt(n));
 | 
			
		||||
                    const percentage = total > 0 ? Math.round((signed / total) * 100) : 0;
 | 
			
		||||
                    progressBar.style.width = percentage + '%';
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Update statistics cards
 | 
			
		||||
        document.getElementById('pending-count').textContent = pendingCount;
 | 
			
		||||
        document.getElementById('signed-count').textContent = signedCount;
 | 
			
		||||
        document.getElementById('draft-count').textContent = draftCount;
 | 
			
		||||
 | 
			
		||||
        // Update total count badge
 | 
			
		||||
        const badge = document.querySelector('.badge.bg-primary');
 | 
			
		||||
        if (badge) {
 | 
			
		||||
            badge.textContent = totalContracts;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    document.addEventListener('DOMContentLoaded', function () {
 | 
			
		||||
        // Calculate initial statistics
 | 
			
		||||
        updateStatistics();
 | 
			
		||||
 | 
			
		||||
        // Handle confirm delete button click
 | 
			
		||||
        document.getElementById('confirmDeleteBtn').addEventListener('click', function () {
 | 
			
		||||
            console.log('User confirmed deletion, submitting form...');
 | 
			
		||||
 | 
			
		||||
            // Create and submit form
 | 
			
		||||
            const form = document.createElement('form');
 | 
			
		||||
            form.method = 'POST';
 | 
			
		||||
            form.action = '/contracts/' + window.currentDeleteContractId + '/delete';
 | 
			
		||||
            form.style.display = 'none';
 | 
			
		||||
 | 
			
		||||
            document.body.appendChild(form);
 | 
			
		||||
            form.submit();
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    console.log('My Contracts page scripts loaded successfully');
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										370
									
								
								actix_mvc_app/src/views/contracts/signed_document.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										370
									
								
								actix_mvc_app/src/views/contracts/signed_document.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,370 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}{{ contract.title }} - Signed Contract{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container-fluid">
 | 
			
		||||
    <!-- Action Bar (hidden in print) -->
 | 
			
		||||
    <div class="row mb-4 no-print">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="d-flex justify-content-between align-items-center">
 | 
			
		||||
                <div>
 | 
			
		||||
                    <h1 class="h4 mb-1">
 | 
			
		||||
                        <i class="bi bi-file-earmark-check text-success me-2"></i>
 | 
			
		||||
                        Signed Contract Document
 | 
			
		||||
                    </h1>
 | 
			
		||||
                    <p class="text-muted mb-0">Official digitally signed copy</p>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="text-end">
 | 
			
		||||
                    <a href="/contracts/{{ contract.id }}" class="btn btn-outline-secondary">
 | 
			
		||||
                        <i class="bi bi-arrow-left me-1"></i> Back to Contract
 | 
			
		||||
                    </a>
 | 
			
		||||
                    <button class="btn btn-primary" onclick="window.print()">
 | 
			
		||||
                        <i class="bi bi-printer me-1"></i> Print Document
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button class="btn btn-outline-secondary" id="copyContentBtn"
 | 
			
		||||
                        title="Copy contract content to clipboard">
 | 
			
		||||
                        <i class="bi bi-clipboard" id="copyIcon"></i>
 | 
			
		||||
                        <div class="spinner-border spinner-border-sm d-none" id="copySpinner" role="status">
 | 
			
		||||
                            <span class="visually-hidden">Loading...</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Signature Verification Banner -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="alert alert-success border-success">
 | 
			
		||||
                <div class="row align-items-center">
 | 
			
		||||
                    <div class="col-md-1 text-center">
 | 
			
		||||
                        <i class="bi bi-shield-check text-success" style="font-size: 2rem;"></i>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col-md-11">
 | 
			
		||||
                        <h5 class="alert-heading mb-2">
 | 
			
		||||
                            <i class="bi bi-check-circle me-2"></i>Digitally Signed Document
 | 
			
		||||
                        </h5>
 | 
			
		||||
                        <p class="mb-1">
 | 
			
		||||
                            <strong>{{ signer.name }}</strong> ({{ signer.email }}) digitally signed this contract on
 | 
			
		||||
                            <strong>{{ signer.signed_at }}</strong>
 | 
			
		||||
                        </p>
 | 
			
		||||
                        {% if signer.comments %}
 | 
			
		||||
                        <p class="mb-0">
 | 
			
		||||
                            <strong>Signer Comments:</strong> {{ signer.comments }}
 | 
			
		||||
                        </p>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Contract Information -->
 | 
			
		||||
    <div class="row mb-4">
 | 
			
		||||
        <div class="col-md-8">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">
 | 
			
		||||
                        <i class="bi bi-info-circle me-2"></i>Contract Information
 | 
			
		||||
                    </h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="row">
 | 
			
		||||
                        <div class="col-md-6">
 | 
			
		||||
                            <p><strong>Contract ID:</strong> {{ contract.contract_id }}</p>
 | 
			
		||||
                            <p><strong>Title:</strong> {{ contract.title }}</p>
 | 
			
		||||
                            <p><strong>Type:</strong> {{ contract.contract_type }}</p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-md-6">
 | 
			
		||||
                            <p><strong>Status:</strong>
 | 
			
		||||
                                <span class="badge bg-success">{{ contract.status }}</span>
 | 
			
		||||
                            </p>
 | 
			
		||||
                            <p><strong>Created:</strong> {{ contract.created_at }}</p>
 | 
			
		||||
                            <p><strong>Version:</strong> {{ contract.current_version }}</p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% if contract.description %}
 | 
			
		||||
                    <p><strong>Description:</strong> {{ contract.description }}</p>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-md-4">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header">
 | 
			
		||||
                    <h5 class="mb-0">
 | 
			
		||||
                        <i class="bi bi-person-check me-2"></i>Signer Information
 | 
			
		||||
                    </h5>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <p><strong>Name:</strong> {{ signer.name }}</p>
 | 
			
		||||
                    <p><strong>Email:</strong> {{ signer.email }}</p>
 | 
			
		||||
                    <p><strong>Status:</strong>
 | 
			
		||||
                        <span class="badge bg-success">{{ signer.status }}</span>
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <p><strong>Signed At:</strong> {{ signer.signed_at }}</p>
 | 
			
		||||
                    {% if signer.comments %}
 | 
			
		||||
                    <p><strong>Comments:</strong></p>
 | 
			
		||||
                    <div class="bg-light p-2 rounded">
 | 
			
		||||
                        {{ signer.comments }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
                    <!-- Display Saved Signature -->
 | 
			
		||||
                    {% if signer.signature_data %}
 | 
			
		||||
                    <div class="mt-3">
 | 
			
		||||
                        <p><strong>Digital Signature:</strong></p>
 | 
			
		||||
                        <div class="signature-display bg-white border rounded p-3 text-center">
 | 
			
		||||
                            <img src="{{ signer.signature_data }}" alt="Digital Signature" class="signature-image" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Contract Content -->
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card">
 | 
			
		||||
                <div class="card-header d-flex justify-content-between align-items-center">
 | 
			
		||||
                    <h5 class="mb-0">
 | 
			
		||||
                        <i class="bi bi-file-text me-2"></i>Contract Terms & Conditions
 | 
			
		||||
                    </h5>
 | 
			
		||||
                    <div>
 | 
			
		||||
                        <button class="btn btn-outline-secondary btn-sm" id="copyContentBtn"
 | 
			
		||||
                            title="Copy contract content to clipboard">
 | 
			
		||||
                            <i class="bi bi-clipboard" id="copyIcon"></i>
 | 
			
		||||
                            <div class="spinner-border spinner-border-sm d-none" id="copySpinner" role="status">
 | 
			
		||||
                                <span class="visually-hidden">Loading...</span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    {% if contract_content_html %}
 | 
			
		||||
                    <!-- Hidden element containing raw markdown content for copying -->
 | 
			
		||||
                    <div id="rawContractContent" class="d-none">{{ contract.terms_and_conditions }}</div>
 | 
			
		||||
                    <div class="contract-content bg-white p-4 border rounded">
 | 
			
		||||
                        {{ contract_content_html | safe }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                    <div class="alert alert-info text-center py-5">
 | 
			
		||||
                        <i class="bi bi-file-text text-muted" style="font-size: 3rem;"></i>
 | 
			
		||||
                        <h5 class="mt-3">No Content Available</h5>
 | 
			
		||||
                        <p class="text-muted">This contract doesn't have any content.</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Digital Signature Footer -->
 | 
			
		||||
    <div class="row mt-4">
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <div class="card border-success">
 | 
			
		||||
                <div class="card-body text-center">
 | 
			
		||||
                    <h6 class="text-success mb-2">
 | 
			
		||||
                        <i class="bi bi-shield-check me-2"></i>Digital Signature Verification
 | 
			
		||||
                    </h6>
 | 
			
		||||
                    <p class="small text-muted mb-0">
 | 
			
		||||
                        This document has been digitally signed by {{ signer.name }} on {{ signer.signed_at }}.
 | 
			
		||||
                        The digital signature ensures the authenticity and integrity of this contract.
 | 
			
		||||
                    </p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block extra_css %}
 | 
			
		||||
<style>
 | 
			
		||||
    /* Print styles */
 | 
			
		||||
    @media print {
 | 
			
		||||
 | 
			
		||||
        .btn,
 | 
			
		||||
        .card-header .btn {
 | 
			
		||||
            display: none !important;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .alert {
 | 
			
		||||
            border: 2px solid #28a745 !important;
 | 
			
		||||
            background-color: #f8f9fa !important;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .card {
 | 
			
		||||
            border: 1px solid #dee2e6 !important;
 | 
			
		||||
            box-shadow: none !important;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .bg-light {
 | 
			
		||||
            background-color: #f8f9fa !important;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Markdown Content Styles */
 | 
			
		||||
    .contract-content h1,
 | 
			
		||||
    .contract-content h2,
 | 
			
		||||
    .contract-content h3,
 | 
			
		||||
    .contract-content h4,
 | 
			
		||||
    .contract-content h5,
 | 
			
		||||
    .contract-content h6 {
 | 
			
		||||
        margin-top: 1.5rem;
 | 
			
		||||
        margin-bottom: 1rem;
 | 
			
		||||
        font-weight: 600;
 | 
			
		||||
        line-height: 1.25;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .contract-content h1 {
 | 
			
		||||
        font-size: 2rem;
 | 
			
		||||
        border-bottom: 2px solid #e9ecef;
 | 
			
		||||
        padding-bottom: 0.5rem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .contract-content h2 {
 | 
			
		||||
        font-size: 1.5rem;
 | 
			
		||||
        border-bottom: 1px solid #e9ecef;
 | 
			
		||||
        padding-bottom: 0.3rem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .contract-content p {
 | 
			
		||||
        margin-bottom: 1rem;
 | 
			
		||||
        line-height: 1.6;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .contract-content ul,
 | 
			
		||||
    .contract-content ol {
 | 
			
		||||
        margin-bottom: 1rem;
 | 
			
		||||
        padding-left: 2rem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .contract-content table {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        margin-bottom: 1rem;
 | 
			
		||||
        border-collapse: collapse;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .contract-content table th,
 | 
			
		||||
    .contract-content table td {
 | 
			
		||||
        padding: 0.75rem;
 | 
			
		||||
        border: 1px solid #dee2e6;
 | 
			
		||||
        text-align: left;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .contract-content table th {
 | 
			
		||||
        background-color: #f8f9fa;
 | 
			
		||||
        font-weight: 600;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Signature Display Styles */
 | 
			
		||||
    .signature-display {
 | 
			
		||||
        min-height: 80px;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        justify-content: center;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .signature-image {
 | 
			
		||||
        max-width: 100%;
 | 
			
		||||
        max-height: 60px;
 | 
			
		||||
        border: 1px solid #dee2e6;
 | 
			
		||||
        border-radius: 4px;
 | 
			
		||||
        background: #fff;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* Copy button styles */
 | 
			
		||||
    #copyContentBtn {
 | 
			
		||||
        position: relative;
 | 
			
		||||
        min-width: 40px;
 | 
			
		||||
        min-height: 32px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #copyContentBtn:disabled {
 | 
			
		||||
        opacity: 0.7;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #copySpinner {
 | 
			
		||||
        width: 1rem;
 | 
			
		||||
        height: 1rem;
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block extra_js %}
 | 
			
		||||
<script>
 | 
			
		||||
    // Copy contract content functionality
 | 
			
		||||
    const copyContentBtn = document.getElementById('copyContentBtn');
 | 
			
		||||
    const copyIcon = document.getElementById('copyIcon');
 | 
			
		||||
    const copySpinner = document.getElementById('copySpinner');
 | 
			
		||||
 | 
			
		||||
    if (copyContentBtn) {
 | 
			
		||||
        copyContentBtn.addEventListener('click', async function () {
 | 
			
		||||
            const rawContent = document.getElementById('rawContractContent');
 | 
			
		||||
 | 
			
		||||
            if (!rawContent) {
 | 
			
		||||
                alert('No contract content available to copy.');
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Show loading state
 | 
			
		||||
            copyIcon.classList.add('d-none');
 | 
			
		||||
            copySpinner.classList.remove('d-none');
 | 
			
		||||
            copyContentBtn.disabled = true;
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                // Copy to clipboard
 | 
			
		||||
                await navigator.clipboard.writeText(rawContent.textContent);
 | 
			
		||||
 | 
			
		||||
                // Show success state
 | 
			
		||||
                copySpinner.classList.add('d-none');
 | 
			
		||||
                copyIcon.classList.remove('d-none');
 | 
			
		||||
                copyIcon.className = 'bi bi-check-circle text-success';
 | 
			
		||||
 | 
			
		||||
                // Initialize tooltip
 | 
			
		||||
                const tooltip = new bootstrap.Tooltip(copyContentBtn, {
 | 
			
		||||
                    title: 'Contract content copied to clipboard!',
 | 
			
		||||
                    placement: 'top',
 | 
			
		||||
                    trigger: 'manual'
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // Show tooltip
 | 
			
		||||
                tooltip.show();
 | 
			
		||||
 | 
			
		||||
                // Hide tooltip and reset icon after 2 seconds
 | 
			
		||||
                setTimeout(() => {
 | 
			
		||||
                    tooltip.hide();
 | 
			
		||||
                    copyIcon.className = 'bi bi-clipboard';
 | 
			
		||||
                    copyContentBtn.disabled = false;
 | 
			
		||||
 | 
			
		||||
                    // Dispose tooltip to prevent memory leaks
 | 
			
		||||
                    setTimeout(() => {
 | 
			
		||||
                        tooltip.dispose();
 | 
			
		||||
                    }, 300);
 | 
			
		||||
                }, 2000);
 | 
			
		||||
 | 
			
		||||
            } catch (err) {
 | 
			
		||||
                console.error('Failed to copy content: ', err);
 | 
			
		||||
 | 
			
		||||
                // Show error state
 | 
			
		||||
                copySpinner.classList.add('d-none');
 | 
			
		||||
                copyIcon.classList.remove('d-none');
 | 
			
		||||
                copyIcon.className = 'bi bi-x-circle text-danger';
 | 
			
		||||
 | 
			
		||||
                alert('Failed to copy content to clipboard. Please try again.');
 | 
			
		||||
 | 
			
		||||
                // Reset icon after 2 seconds
 | 
			
		||||
                setTimeout(() => {
 | 
			
		||||
                    copyIcon.className = 'bi bi-clipboard';
 | 
			
		||||
                    copyContentBtn.disabled = false;
 | 
			
		||||
                }, 2000);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										125
									
								
								actix_mvc_app/src/views/errors/404.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								actix_mvc_app/src/views/errors/404.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,125 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <title>Page Not Found - Zanzibar Digital Freezone</title>
 | 
			
		||||
    <link rel="stylesheet" href="/static/css/bootstrap.min.css">
 | 
			
		||||
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body class="bg-light">
 | 
			
		||||
    <div class="container-fluid min-vh-100 d-flex align-items-center justify-content-center">
 | 
			
		||||
        <div class="container-fluid">
 | 
			
		||||
            <div class="row justify-content-center">
 | 
			
		||||
                <div class="col-md-8 col-lg-6">
 | 
			
		||||
                    <div class="text-center py-5">
 | 
			
		||||
                        <!-- 404 Icon -->
 | 
			
		||||
                        <div class="mb-4">
 | 
			
		||||
                            <i class="bi bi-exclamation-triangle display-1 text-warning"></i>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- Error Code -->
 | 
			
		||||
                        <h1 class="display-1 fw-bold text-muted">404</h1>
 | 
			
		||||
 | 
			
		||||
                        <!-- Error Message -->
 | 
			
		||||
                        <h2 class="mb-3">{% if error_title %}{{ error_title }}{% else %}Page Not Found{% endif %}</h2>
 | 
			
		||||
                        <p class="lead text-muted mb-4">
 | 
			
		||||
                            {% if error_message %}{{ error_message }}{% else %}The page you're looking for doesn't exist
 | 
			
		||||
                            or has
 | 
			
		||||
                            been moved.{% endif %}
 | 
			
		||||
                        </p>
 | 
			
		||||
 | 
			
		||||
                        <!-- Suggestions -->
 | 
			
		||||
                        <div class="card bg-light border-0 mb-4">
 | 
			
		||||
                            <div class="card-body">
 | 
			
		||||
                                <h6 class="card-title">What can you do?</h6>
 | 
			
		||||
                                <ul class="list-unstyled mb-0">
 | 
			
		||||
                                    <li class="mb-2">
 | 
			
		||||
                                        <i class="bi bi-arrow-left text-primary me-2"></i>
 | 
			
		||||
                                        Go back to the previous page
 | 
			
		||||
                                    </li>
 | 
			
		||||
                                    <li class="mb-2">
 | 
			
		||||
                                        <i class="bi bi-house text-primary me-2"></i>
 | 
			
		||||
                                        Visit our homepage
 | 
			
		||||
                                    </li>
 | 
			
		||||
                                    <li class="mb-2">
 | 
			
		||||
                                        <i class="bi bi-search text-primary me-2"></i>
 | 
			
		||||
                                        Check the URL for typos
 | 
			
		||||
                                    </li>
 | 
			
		||||
                                    <li class="mb-2">
 | 
			
		||||
                                        <i class="bi bi-arrow-clockwise text-primary me-2"></i>
 | 
			
		||||
                                        Try refreshing the page
 | 
			
		||||
                                    </li>
 | 
			
		||||
                                </ul>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <!-- Action Buttons -->
 | 
			
		||||
                        <div class="d-flex flex-column flex-sm-row gap-2 justify-content-center">
 | 
			
		||||
                            <button onclick="history.back()" class="btn btn-outline-primary">
 | 
			
		||||
                                <i class="bi bi-arrow-left me-1"></i> Go Back
 | 
			
		||||
                            </button>
 | 
			
		||||
                            <a href="/" class="btn btn-primary">
 | 
			
		||||
                                <i class="bi bi-house me-1"></i> Go Home
 | 
			
		||||
                            </a>
 | 
			
		||||
                            {% if return_url %}
 | 
			
		||||
                            <a href="{{ return_url }}" class="btn btn-outline-secondary">
 | 
			
		||||
                                <i class="bi bi-arrow-return-left me-1"></i> {% if return_text %}{{ return_text }}{%
 | 
			
		||||
                                else
 | 
			
		||||
                                %}Return{% endif %}
 | 
			
		||||
                            </a>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                        <!-- Contact Support -->
 | 
			
		||||
                        <div class="mt-5 pt-4 border-top">
 | 
			
		||||
                            <p class="text-muted small">
 | 
			
		||||
                                Still having trouble?
 | 
			
		||||
                                <a href="/support" class="text-decoration-none">Contact Support</a>
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <script src="/static/js/bootstrap.bundle.min.js"></script>
 | 
			
		||||
    <script>
 | 
			
		||||
        // Auto-redirect after 10 seconds if no user interaction
 | 
			
		||||
        let redirectTimer;
 | 
			
		||||
        let countdown = 10;
 | 
			
		||||
 | 
			
		||||
        function startAutoRedirect() {
 | 
			
		||||
            redirectTimer = setInterval(() => {
 | 
			
		||||
                countdown--;
 | 
			
		||||
                if (countdown <= 0) {
 | 
			
		||||
                    clearInterval(redirectTimer);
 | 
			
		||||
                    window.location.href = '/';
 | 
			
		||||
                }
 | 
			
		||||
            }, 1000);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Cancel auto-redirect on any user interaction
 | 
			
		||||
        function cancelAutoRedirect() {
 | 
			
		||||
            if (redirectTimer) {
 | 
			
		||||
                clearInterval(redirectTimer);
 | 
			
		||||
                redirectTimer = null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Start auto-redirect after 5 seconds of no interaction
 | 
			
		||||
        setTimeout(startAutoRedirect, 5000);
 | 
			
		||||
 | 
			
		||||
        // Cancel auto-redirect on mouse movement, clicks, or key presses
 | 
			
		||||
        document.addEventListener('mousemove', cancelAutoRedirect);
 | 
			
		||||
        document.addEventListener('click', cancelAutoRedirect);
 | 
			
		||||
        document.addEventListener('keydown', cancelAutoRedirect);
 | 
			
		||||
    </script>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
		Reference in New Issue
	
	Block a user