add contract md folder support
This commit is contained in:
		
							
								
								
									
										35
									
								
								actix_mvc_app/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										35
									
								
								actix_mvc_app/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -259,6 +259,7 @@ dependencies = [
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "log",
 | 
			
		||||
 "num_cpus",
 | 
			
		||||
 "pulldown-cmark",
 | 
			
		||||
 "redis",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
@@ -1075,6 +1076,15 @@ dependencies = [
 | 
			
		||||
 "version_check",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "getopts"
 | 
			
		||||
version = "0.2.21"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "unicode-width",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "getrandom"
 | 
			
		||||
version = "0.2.15"
 | 
			
		||||
@@ -1931,6 +1941,25 @@ dependencies = [
 | 
			
		||||
 "unicode-ident",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "pulldown-cmark"
 | 
			
		||||
version = "0.13.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bitflags",
 | 
			
		||||
 "getopts",
 | 
			
		||||
 "memchr",
 | 
			
		||||
 "pulldown-cmark-escape",
 | 
			
		||||
 "unicase",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "pulldown-cmark-escape"
 | 
			
		||||
version = "0.11.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "quote"
 | 
			
		||||
version = "1.0.40"
 | 
			
		||||
@@ -2622,6 +2651,12 @@ version = "1.12.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "unicode-width"
 | 
			
		||||
version = "0.1.14"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "unicode-xid"
 | 
			
		||||
version = "0.2.6"
 | 
			
		||||
 
 | 
			
		||||
@@ -23,3 +23,4 @@ uuid = { version = "1.6.1", features = ["v4", "serde"] }
 | 
			
		||||
lazy_static = "1.4.0"
 | 
			
		||||
redis = { version = "0.23.0", features = ["tokio-comp"] }
 | 
			
		||||
jsonwebtoken = "8.3.0"
 | 
			
		||||
pulldown-cmark = "0.13.0"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								actix_mvc_app/src/content/contract-003/1-purpose.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								actix_mvc_app/src/content/contract-003/1-purpose.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
## 1. Purpose
 | 
			
		||||
 | 
			
		||||
The purpose of this Agreement is to establish the terms and conditions for tokenizing real estate assets on the Zanzibar blockchain network.
 | 
			
		||||
@@ -0,0 +1,3 @@
 | 
			
		||||
## 2. Tokenization Process
 | 
			
		||||
 | 
			
		||||
Tokenizer shall create digital tokens representing ownership interests in the properties listed in Appendix A according to the specifications in Appendix B.
 | 
			
		||||
@@ -0,0 +1,3 @@
 | 
			
		||||
## 3. Revenue Sharing
 | 
			
		||||
 | 
			
		||||
Revenue generated from the tokenized properties shall be distributed according to the formula set forth in Appendix C.
 | 
			
		||||
							
								
								
									
										3
									
								
								actix_mvc_app/src/content/contract-003/4-governance.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								actix_mvc_app/src/content/contract-003/4-governance.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
## 4. Governance
 | 
			
		||||
 | 
			
		||||
Decisions regarding the management of tokenized properties shall be made according to the governance framework outlined in Appendix D.
 | 
			
		||||
							
								
								
									
										3
									
								
								actix_mvc_app/src/content/contract-003/appendix-a.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								actix_mvc_app/src/content/contract-003/appendix-a.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
### Appendix A: Properties
 | 
			
		||||
 | 
			
		||||
List of properties to be tokenized.
 | 
			
		||||
							
								
								
									
										3
									
								
								actix_mvc_app/src/content/contract-003/appendix-b.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								actix_mvc_app/src/content/contract-003/appendix-b.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
### Appendix B: Specifications
 | 
			
		||||
 | 
			
		||||
Technical specifications for tokenization.
 | 
			
		||||
							
								
								
									
										3
									
								
								actix_mvc_app/src/content/contract-003/appendix-c.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								actix_mvc_app/src/content/contract-003/appendix-c.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
### Appendix C: Revenue Formula
 | 
			
		||||
 | 
			
		||||
Formula for revenue distribution.
 | 
			
		||||
							
								
								
									
										3
									
								
								actix_mvc_app/src/content/contract-003/appendix-d.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								actix_mvc_app/src/content/contract-003/appendix-d.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
### Appendix D: Governance Framework
 | 
			
		||||
 | 
			
		||||
Governance framework for tokenized properties.
 | 
			
		||||
							
								
								
									
										3
									
								
								actix_mvc_app/src/content/contract-003/cover.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								actix_mvc_app/src/content/contract-003/cover.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
# Digital Asset Tokenization Agreement
 | 
			
		||||
 | 
			
		||||
This Digital Asset Tokenization Agreement (the "Agreement") is entered into between Zanzibar Property Consortium ("Tokenizer") and the property owners listed in Appendix A ("Owners").
 | 
			
		||||
@@ -3,8 +3,10 @@ use tera::{Context, Tera};
 | 
			
		||||
use chrono::{Utc, Duration};
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
use serde_json::json;
 | 
			
		||||
use actix_web::web::Query;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
use crate::models::contract::{Contract, ContractStatus, ContractType, ContractStatistics, ContractSigner, ContractRevision, SignerStatus};
 | 
			
		||||
use crate::models::contract::{Contract, ContractStatus, ContractType, ContractStatistics, ContractSigner, ContractRevision, SignerStatus, TocItem};
 | 
			
		||||
use crate::utils::render_template;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
@@ -105,7 +107,11 @@ impl ContractController {
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Display a specific contract
 | 
			
		||||
    pub async fn detail(tmpl: web::Data<Tera>, path: web::Path<String>) -> Result<HttpResponse, Error> {
 | 
			
		||||
    pub async fn detail(
 | 
			
		||||
        tmpl: web::Data<Tera>,
 | 
			
		||||
        path: web::Path<String>,
 | 
			
		||||
        query: Query<HashMap<String, String>>
 | 
			
		||||
    ) -> Result<HttpResponse, Error> {
 | 
			
		||||
        let contract_id = path.into_inner();
 | 
			
		||||
        let mut context = Context::new();
 | 
			
		||||
        
 | 
			
		||||
@@ -126,9 +132,53 @@ impl ContractController {
 | 
			
		||||
        
 | 
			
		||||
        // Convert contract to JSON
 | 
			
		||||
        let contract_json = Self::contract_to_json(contract);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Add contract to context
 | 
			
		||||
        context.insert("contract", &contract_json);
 | 
			
		||||
 | 
			
		||||
        // If this contract uses multi-page markdown, load the selected section
 | 
			
		||||
        println!("DEBUG: content_dir = {:?}, toc = {:?}", contract.content_dir, contract.toc);
 | 
			
		||||
        if let (Some(content_dir), Some(toc)) = (&contract.content_dir, &contract.toc) {
 | 
			
		||||
            use std::fs;
 | 
			
		||||
            use pulldown_cmark::{Parser, Options, html};
 | 
			
		||||
            // Helper to flatten toc recursively
 | 
			
		||||
            fn flatten_toc<'a>(items: &'a Vec<TocItem>, out: &mut Vec<&'a TocItem>) {
 | 
			
		||||
                for item in items {
 | 
			
		||||
                    out.push(item);
 | 
			
		||||
                    if !item.children.is_empty() {
 | 
			
		||||
                        flatten_toc(&item.children, out);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            let mut flat_toc = Vec::new();
 | 
			
		||||
            flatten_toc(&toc, &mut flat_toc);
 | 
			
		||||
            let section_param = query.get("section");
 | 
			
		||||
            let selected_file = section_param
 | 
			
		||||
                .and_then(|f| flat_toc.iter().find(|item| item.file == *f).map(|item| item.file.clone()))
 | 
			
		||||
                .unwrap_or_else(|| flat_toc.get(0).map(|item| item.file.clone()).unwrap_or_default());
 | 
			
		||||
            context.insert("section", &selected_file);
 | 
			
		||||
            let rel_path = format!("{}/{}", content_dir, selected_file);
 | 
			
		||||
            let abs_path = match std::env::current_dir() {
 | 
			
		||||
                Ok(dir) => dir.join(&rel_path),
 | 
			
		||||
                Err(_) => std::path::PathBuf::from(&rel_path),
 | 
			
		||||
            };
 | 
			
		||||
            println!("DEBUG: Attempting to read markdown file at absolute path: {:?}", abs_path);
 | 
			
		||||
            match fs::read_to_string(&abs_path) {
 | 
			
		||||
                Ok(md) => {
 | 
			
		||||
                    println!("DEBUG: Successfully read markdown file");
 | 
			
		||||
                    let parser = Parser::new_ext(&md, Options::all());
 | 
			
		||||
                    let mut html_output = String::new();
 | 
			
		||||
                    html::push_html(&mut html_output, parser);
 | 
			
		||||
                    context.insert("contract_section_content", &html_output);
 | 
			
		||||
                },
 | 
			
		||||
                Err(e) => {
 | 
			
		||||
                    let error_msg = format!("Error: Could not read contract section markdown at '{:?}': {}", abs_path, e);
 | 
			
		||||
                    println!("{}", error_msg);
 | 
			
		||||
                    context.insert("contract_section_content_error", &error_msg);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            context.insert("toc", &toc);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Count signed signers for the template
 | 
			
		||||
        let signed_signers = contract.signers.iter().filter(|s| s.status == SignerStatus::Signed).count();
 | 
			
		||||
@@ -327,6 +377,8 @@ impl ContractController {
 | 
			
		||||
        
 | 
			
		||||
        // Mock contract 1 - Signed Service Agreement
 | 
			
		||||
        let mut contract1 = Contract {
 | 
			
		||||
            content_dir: None,
 | 
			
		||||
            toc: None,
 | 
			
		||||
            id: "contract-001".to_string(),
 | 
			
		||||
            title: "Digital Hub Service Agreement".to_string(),
 | 
			
		||||
            description: "Service agreement for cloud hosting and digital infrastructure services provided by the Zanzibar Digital Hub.".to_string(),
 | 
			
		||||
@@ -381,6 +433,8 @@ impl ContractController {
 | 
			
		||||
        
 | 
			
		||||
        // Mock contract 2 - Pending Signatures
 | 
			
		||||
        let mut contract2 = Contract {
 | 
			
		||||
            content_dir: None,
 | 
			
		||||
            toc: None,
 | 
			
		||||
            id: "contract-002".to_string(),
 | 
			
		||||
            title: "Software Development Agreement".to_string(),
 | 
			
		||||
            description: "Agreement for custom software development services for the Zanzibar Digital Marketplace platform.".to_string(),
 | 
			
		||||
@@ -450,7 +504,56 @@ impl ContractController {
 | 
			
		||||
            signers: Vec::new(),
 | 
			
		||||
            revisions: Vec::new(),
 | 
			
		||||
            current_version: 1,
 | 
			
		||||
            content_dir: Some("src/content/contract-003".to_string()),
 | 
			
		||||
            toc: Some(vec![
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "Cover".to_string(),
 | 
			
		||||
                    file: "cover.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "1. Purpose".to_string(),
 | 
			
		||||
                    file: "1-purpose.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "2. Tokenization Process".to_string(),
 | 
			
		||||
                    file: "2-tokenization-process.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "3. Revenue Sharing".to_string(),
 | 
			
		||||
                    file: "3-revenue-sharing.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "4. Governance".to_string(),
 | 
			
		||||
                    file: "4-governance.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "Appendix A: Properties".to_string(),
 | 
			
		||||
                    file: "appendix-a.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "Appendix B: Technical Specs".to_string(),
 | 
			
		||||
                    file: "appendix-b.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "Appendix C: Revenue Formula".to_string(),
 | 
			
		||||
                    file: "appendix-c.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
                TocItem {
 | 
			
		||||
                    title: "Appendix D: Governance Framework".to_string(),
 | 
			
		||||
                    file: "appendix-d.md".to_string(),
 | 
			
		||||
                    children: vec![],
 | 
			
		||||
                },
 | 
			
		||||
            ]),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        // Add potential signers to contract 3 (still in draft)
 | 
			
		||||
        contract3.signers.push(ContractSigner {
 | 
			
		||||
@@ -471,17 +574,62 @@ impl ContractController {
 | 
			
		||||
            comments: None,
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        // Add revisions to contract 3
 | 
			
		||||
        contract3.revisions.push(ContractRevision {
 | 
			
		||||
            version: 1,
 | 
			
		||||
            content: "<h1>Digital Asset Tokenization Agreement</h1><p>This Digital Asset Tokenization Agreement (the \"Agreement\") is entered into between Zanzibar Property Consortium (\"Tokenizer\") and the property owners listed in Appendix A (\"Owners\").</p><h2>1. Purpose</h2><p>The purpose of this Agreement is to establish the terms and conditions for tokenizing real estate assets on the Zanzibar blockchain network.</p><h2>2. Tokenization Process</h2><p>Tokenizer shall create digital tokens representing ownership interests in the properties listed in Appendix A according to the specifications in Appendix B.</p><h2>3. Revenue Sharing</h2><p>Revenue generated from the tokenized properties shall be distributed according to the formula set forth in Appendix C.</p><h2>4. Governance</h2><p>Decisions regarding the management of tokenized properties shall be made according to the governance framework outlined in Appendix D.</p>".to_string(),
 | 
			
		||||
            created_at: Utc::now() - Duration::days(3),
 | 
			
		||||
            created_by: "Nala Okafor".to_string(),
 | 
			
		||||
            comments: Some("Initial draft of the tokenization agreement.".to_string()),
 | 
			
		||||
        });
 | 
			
		||||
        // Add ToC and content directory to contract 3
 | 
			
		||||
        contract3.content_dir = Some("src/content/contract-003".to_string());
 | 
			
		||||
        contract3.toc = Some(vec![
 | 
			
		||||
            TocItem {
 | 
			
		||||
                title: "Digital Asset Tokenization Agreement".to_string(),
 | 
			
		||||
                file: "cover.md".to_string(),
 | 
			
		||||
                children: vec![
 | 
			
		||||
                    TocItem {
 | 
			
		||||
                        title: "1. Purpose".to_string(),
 | 
			
		||||
                        file: "1-purpose.md".to_string(),
 | 
			
		||||
                        children: vec![],
 | 
			
		||||
                    },
 | 
			
		||||
                    TocItem {
 | 
			
		||||
                        title: "2. Tokenization Process".to_string(),
 | 
			
		||||
                        file: "2-tokenization-process.md".to_string(),
 | 
			
		||||
                        children: vec![],
 | 
			
		||||
                    },
 | 
			
		||||
                    TocItem {
 | 
			
		||||
                        title: "3. Revenue Sharing".to_string(),
 | 
			
		||||
                        file: "3-revenue-sharing.md".to_string(),
 | 
			
		||||
                        children: vec![],
 | 
			
		||||
                    },
 | 
			
		||||
                    TocItem {
 | 
			
		||||
                        title: "4. Governance".to_string(),
 | 
			
		||||
                        file: "4-governance.md".to_string(),
 | 
			
		||||
                        children: vec![],
 | 
			
		||||
                    },
 | 
			
		||||
                    TocItem {
 | 
			
		||||
                        title: "Appendix A: Properties".to_string(),
 | 
			
		||||
                        file: "appendix-a.md".to_string(),
 | 
			
		||||
                        children: vec![],
 | 
			
		||||
                    },
 | 
			
		||||
                    TocItem {
 | 
			
		||||
                        title: "Appendix B: Specifications".to_string(),
 | 
			
		||||
                        file: "appendix-b.md".to_string(),
 | 
			
		||||
                        children: vec![],
 | 
			
		||||
                    },
 | 
			
		||||
                    TocItem {
 | 
			
		||||
                        title: "Appendix C: Revenue Formula".to_string(),
 | 
			
		||||
                        file: "appendix-c.md".to_string(),
 | 
			
		||||
                        children: vec![],
 | 
			
		||||
                    },
 | 
			
		||||
                    TocItem {
 | 
			
		||||
                        title: "Appendix D: Governance Framework".to_string(),
 | 
			
		||||
                        file: "appendix-d.md".to_string(),
 | 
			
		||||
                        children: vec![],
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            }
 | 
			
		||||
        ]);
 | 
			
		||||
        // No revision content for contract 3, content is in markdown files.
 | 
			
		||||
        
 | 
			
		||||
        // Mock contract 4 - Rejected
 | 
			
		||||
        let mut contract4 = Contract {
 | 
			
		||||
            content_dir: None,
 | 
			
		||||
            toc: None,
 | 
			
		||||
            id: "contract-004".to_string(),
 | 
			
		||||
            title: "Data Sharing Agreement".to_string(),
 | 
			
		||||
            description: "Agreement governing the sharing of anonymized data between Zanzibar Digital Hub and research institutions.".to_string(),
 | 
			
		||||
@@ -528,6 +676,8 @@ impl ContractController {
 | 
			
		||||
        
 | 
			
		||||
        // Mock contract 5 - Active
 | 
			
		||||
        let mut contract5 = Contract {
 | 
			
		||||
            content_dir: None,
 | 
			
		||||
            toc: None,
 | 
			
		||||
            id: "contract-005".to_string(),
 | 
			
		||||
            title: "Digital Identity Verification Service Agreement".to_string(),
 | 
			
		||||
            description: "Agreement for providing digital identity verification services to businesses operating in the Zanzibar Autonomous Zone.".to_string(),
 | 
			
		||||
 
 | 
			
		||||
@@ -136,6 +136,14 @@ impl ContractRevision {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Table of Contents item for multi-page contracts
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct TocItem {
 | 
			
		||||
    pub title: String,
 | 
			
		||||
    pub file: String,
 | 
			
		||||
    pub children: Vec<TocItem>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Contract model
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct Contract {
 | 
			
		||||
@@ -153,6 +161,9 @@ pub struct Contract {
 | 
			
		||||
    pub revisions: Vec<ContractRevision>,
 | 
			
		||||
    pub current_version: u32,
 | 
			
		||||
    pub organization_id: Option<String>,
 | 
			
		||||
    // Multi-page markdown support
 | 
			
		||||
    pub content_dir: Option<String>,
 | 
			
		||||
    pub toc: Option<Vec<TocItem>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Contract {
 | 
			
		||||
@@ -171,8 +182,10 @@ impl Contract {
 | 
			
		||||
            expiration_date: None,
 | 
			
		||||
            signers: Vec::new(),
 | 
			
		||||
            revisions: Vec::new(),
 | 
			
		||||
            current_version: 0,
 | 
			
		||||
            current_version: 1,
 | 
			
		||||
            organization_id,
 | 
			
		||||
            content_dir: None,
 | 
			
		||||
            toc: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
{% import "contracts/macros/contract_macros.html" as contract_macros %}
 | 
			
		||||
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% block title %}Contract Details{% endblock %}
 | 
			
		||||
@@ -41,6 +43,11 @@
 | 
			
		||||
                <i class="bi bi-clock-history me-1"></i> Activity
 | 
			
		||||
            </button>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li class="nav-item" role="presentation">
 | 
			
		||||
            <button class="nav-link" id="signatures-tab" data-bs-toggle="tab" data-bs-target="#signatures" type="button" role="tab" aria-controls="signatures" aria-selected="false">
 | 
			
		||||
                <i class="bi bi-pencil-square me-1"></i> Signatures
 | 
			
		||||
            </button>
 | 
			
		||||
        </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
 | 
			
		||||
    <div class="tab-content" id="contractTabsContent">
 | 
			
		||||
@@ -53,83 +60,52 @@
 | 
			
		||||
                        <div class="card-header d-flex justify-content-between align-items-center">
 | 
			
		||||
                            <h5 class="mb-0">Contract Document</h5>
 | 
			
		||||
                            {% if contract.status == 'Signed' %}
 | 
			
		||||
                            <span class="badge bg-success">SIGNED</span>
 | 
			
		||||
                                <span class="badge bg-success">SIGNED</span>
 | 
			
		||||
                            {% elif contract.status == 'Active' %}
 | 
			
		||||
                            <span class="badge bg-success">ACTIVE</span>
 | 
			
		||||
                                <span class="badge bg-success">ACTIVE</span>
 | 
			
		||||
                            {% elif contract.status == 'PendingSignatures' %}
 | 
			
		||||
                            <span class="badge bg-warning text-dark">PENDING</span>
 | 
			
		||||
                                <span class="badge bg-warning text-dark">PENDING</span>
 | 
			
		||||
                            {% elif contract.status == 'Draft' %}
 | 
			
		||||
                            <span class="badge bg-secondary">DRAFT</span>
 | 
			
		||||
                                <span class="badge bg-secondary">DRAFT</span>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="card-body bg-light">
 | 
			
		||||
                            {% if contract.revisions|length > 0 %}
 | 
			
		||||
                            {% if contract_section_content_error is defined %}
 | 
			
		||||
                                <div class="alert alert-danger">{{ contract_section_content_error }}</div>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                            {% if contract_section_content is defined %}
 | 
			
		||||
                                <div class="row">
 | 
			
		||||
                                    <div class="col-md-3">
 | 
			
		||||
                                        <div class="list-group mb-3">
 | 
			
		||||
                                            {% set section_param = section | default(value=toc[0].file) %}
 | 
			
		||||
                                            {{ contract_macros::render_toc(items=toc, section_param=section_param) }}
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="col-md-9">
 | 
			
		||||
                                        <div class="bg-white p-4 border rounded">
 | 
			
		||||
                                            {{ contract_section_content | safe }}
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            {% elif contract.revisions|length > 0 %}
 | 
			
		||||
                                {% set latest_revision = contract.latest_revision %}
 | 
			
		||||
                                <div class="bg-white p-4 border rounded">
 | 
			
		||||
                                    {{ latest_revision.content|safe }}
 | 
			
		||||
                                </div>
 | 
			
		||||
                            {% else %}
 | 
			
		||||
                                <div class="text-center py-5 text-muted">
 | 
			
		||||
                                    <p>No content has been added to this contract yet.</p>
 | 
			
		||||
                                <div class="alert alert-warning text-center py-5">
 | 
			
		||||
                                    <p>
 | 
			
		||||
                                        {% if contract_section_content_error is defined %}
 | 
			
		||||
                                            {{ contract_section_content_error }}
 | 
			
		||||
                                        {% else %}
 | 
			
		||||
                                            No content or markdown sections could be loaded for this contract. Please check the contract's content directory and Table of Contents configuration.
 | 
			
		||||
                                        {% endif %}
 | 
			
		||||
                                    </p>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    
 | 
			
		||||
                    <!-- Signature Areas -->
 | 
			
		||||
                    <div class="card mb-4">
 | 
			
		||||
                        <div class="card-header">
 | 
			
		||||
                            <h5 class="mb-0">Signatures</h5>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="card-body">
 | 
			
		||||
                            <div class="row">
 | 
			
		||||
                                {% for signer in contract.signers %}
 | 
			
		||||
                                <div class="col-md-6 mb-4">
 | 
			
		||||
                                    <div class="card h-100 {% if signer.status == 'Signed' %}border-success{% elif signer.status == 'Rejected' %}border-danger{% else %}border-warning{% endif %}">
 | 
			
		||||
                                        <div class="card-header d-flex justify-content-between align-items-center">
 | 
			
		||||
                                            <h6 class="mb-0">{{ signer.name }}</h6>
 | 
			
		||||
                                            <span class="badge {% if signer.status == 'Signed' %}bg-success{% elif signer.status == 'Rejected' %}bg-danger{% else %}bg-warning text-dark{% endif %}">
 | 
			
		||||
                                                {{ signer.status }}
 | 
			
		||||
                                            </span>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                        <div class="card-body">
 | 
			
		||||
                                            <p class="text-muted mb-2">{{ signer.email }}</p>
 | 
			
		||||
                                            
 | 
			
		||||
                                            {% if signer.status == 'Signed' %}
 | 
			
		||||
                                                <div class="text-center border-top pt-3">
 | 
			
		||||
                                                    <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/Signature_of_John_Hancock.svg/1280px-Signature_of_John_Hancock.svg.png" alt="Signature" class="img-fluid" style="max-height: 60px;">
 | 
			
		||||
                                                    <div class="small text-muted mt-2">Signed on {{ signer.signed_at }}</div>
 | 
			
		||||
                                                </div>
 | 
			
		||||
                                            {% elif signer.status == 'Rejected' %}
 | 
			
		||||
                                                <div class="alert alert-danger mt-3">
 | 
			
		||||
                                                    <i class="bi bi-x-circle me-2"></i> Rejected on {{ signer.signed_at }}
 | 
			
		||||
                                                </div>
 | 
			
		||||
                                            {% else %}
 | 
			
		||||
                                                <div class="text-center mt-3">
 | 
			
		||||
                                                    <p class="text-muted mb-2">Waiting for signature...</p>
 | 
			
		||||
                                                    {% if not user_has_signed %}
 | 
			
		||||
                                                    <button class="btn btn-primary btn-sm btn-sign" data-signer-id="{{ signer.id }}">
 | 
			
		||||
                                                        <i class="bi bi-pen me-1"></i> Sign Here
 | 
			
		||||
                                                    </button>
 | 
			
		||||
                                                    {% endif %}
 | 
			
		||||
                                                </div>
 | 
			
		||||
                                            {% endif %}
 | 
			
		||||
                                            
 | 
			
		||||
                                            {% if signer.comments %}
 | 
			
		||||
                                                <div class="mt-3">
 | 
			
		||||
                                                    <p class="small text-muted mb-1">Comments:</p>
 | 
			
		||||
                                                    <p class="small">{{ signer.comments }}</p>
 | 
			
		||||
                                                </div>
 | 
			
		||||
                                            {% endif %}
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                {% endfor %}
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div class="col-md-3">
 | 
			
		||||
                    <div class="card mb-4">
 | 
			
		||||
                        <div class="card-header">
 | 
			
		||||
@@ -168,7 +144,6 @@
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    
 | 
			
		||||
                    <div class="card mb-4">
 | 
			
		||||
                        <div class="card-header">
 | 
			
		||||
                            <h5 class="mb-0">Signers Status</h5>
 | 
			
		||||
@@ -195,7 +170,6 @@
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    
 | 
			
		||||
                    <div class="card mb-4">
 | 
			
		||||
                        <div class="card-header">
 | 
			
		||||
                            <h5 class="mb-0">Contract Info</h5>
 | 
			
		||||
@@ -223,7 +197,86 @@
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        <!-- Signatures Tab -->
 | 
			
		||||
        <div class="tab-pane fade" id="signatures" role="tabpanel" aria-labelledby="signatures-tab">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <div class="col-md-12">
 | 
			
		||||
                    <div class="card mb-4">
 | 
			
		||||
                        <div class="card-header">
 | 
			
		||||
                            <h5 class="mb-0">Signatures</h5>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="card-body p-0">
 | 
			
		||||
                            <div class="table-responsive">
 | 
			
		||||
                                <table class="table table-hover align-middle mb-0">
 | 
			
		||||
                                    <thead>
 | 
			
		||||
                                        <tr>
 | 
			
		||||
                                            <th scope="col">Name</th>
 | 
			
		||||
                                            <th scope="col">Email</th>
 | 
			
		||||
                                            <th scope="col">Status</th>
 | 
			
		||||
                                            <th scope="col">Signed At</th>
 | 
			
		||||
                                            <th scope="col">Comments</th>
 | 
			
		||||
                                            <th scope="col">Actions</th>
 | 
			
		||||
                                        </tr>
 | 
			
		||||
                                    </thead>
 | 
			
		||||
                                    <tbody>
 | 
			
		||||
                                        {% for signer in contract.signers %}
 | 
			
		||||
                                        <tr class="{% if signer.status == 'Signed' %}table-success{% elif signer.status == 'Rejected' %}table-danger{% elif signer.status == 'Pending' %}table-warning{% endif %}">
 | 
			
		||||
                                            <td>{{ signer.name }}</td>
 | 
			
		||||
                                            <td>{{ signer.email }}</td>
 | 
			
		||||
                                            <td>
 | 
			
		||||
                                                <span class="badge {% if signer.status == 'Signed' %}bg-success{% elif signer.status == 'Rejected' %}bg-danger{% else %}bg-warning text-dark{% endif %}">
 | 
			
		||||
                                                    {{ signer.status }}
 | 
			
		||||
                                                </span>
 | 
			
		||||
                                            </td>
 | 
			
		||||
                                            <td>
 | 
			
		||||
                                                {% if signer.status == 'Signed' or signer.status == 'Rejected' %}
 | 
			
		||||
                                                    {{ signer.signed_at }}
 | 
			
		||||
                                                {% else %}
 | 
			
		||||
                                                    <span class="text-muted">--</span>
 | 
			
		||||
                                                {% endif %}
 | 
			
		||||
                                            </td>
 | 
			
		||||
                                            <td>
 | 
			
		||||
                                                {% if signer.comments %}
 | 
			
		||||
                                                    <span class="small">{{ signer.comments }}</span>
 | 
			
		||||
                                                {% else %}
 | 
			
		||||
                                                    <span class="text-muted">--</span>
 | 
			
		||||
                                                {% endif %}
 | 
			
		||||
                                            </td>
 | 
			
		||||
                                            <td>
 | 
			
		||||
                                                {% if signer.status == 'Signed' %}
 | 
			
		||||
                                                    <a href="/contracts/{{ contract.id }}/signed/{{ signer.id }}" class="btn btn-outline-primary btn-sm" target="_blank">
 | 
			
		||||
                                                        <i class="bi bi-eye"></i> View Signed Document
 | 
			
		||||
                                                    </a>
 | 
			
		||||
                                                {% elif signer.status == 'Rejected' %}
 | 
			
		||||
                                                    <button class="btn btn-outline-secondary btn-sm" disabled title="Rejected">
 | 
			
		||||
                                                        <i class="bi bi-x-circle"></i> Rejected
 | 
			
		||||
                                                    </button>
 | 
			
		||||
                                                    <button class="btn btn-outline-warning btn-sm">
 | 
			
		||||
                                                        <i class="bi bi-bell"></i> Remind to Sign
 | 
			
		||||
                                                    </button>
 | 
			
		||||
                                                {% else %}
 | 
			
		||||
                                                    {% if current_user is defined and not user_has_signed and signer.email == current_user.email %}
 | 
			
		||||
                                                        <button class="btn btn-primary btn-sm btn-sign" data-signer-id="{{ signer.id }}">
 | 
			
		||||
                                                            <i class="bi bi-pen"></i> Sign Here
 | 
			
		||||
                                                        </button>
 | 
			
		||||
                                                    {% endif %}
 | 
			
		||||
                                                    <button class="btn btn-outline-warning btn-sm">
 | 
			
		||||
                                                        <i class="bi bi-bell"></i> Remind to Sign
 | 
			
		||||
                                                    </button>
 | 
			
		||||
                                                {% endif %}
 | 
			
		||||
                                            </td>
 | 
			
		||||
                                        </tr>
 | 
			
		||||
                                        {% endfor %}
 | 
			
		||||
                                    </tbody>
 | 
			
		||||
                                </table>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- Details Tab -->
 | 
			
		||||
        <div class="tab-pane fade" id="details" role="tabpanel" aria-labelledby="details-tab">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
{% macro render_toc(items, section_param) %}
 | 
			
		||||
    {% for item in items %}
 | 
			
		||||
        <a href="?section={{ item.file }}" class="list-group-item list-group-item-action{% if section_param == item.file %} active{% endif %}">{{ item.title }}</a>
 | 
			
		||||
        {% if item.children and item.children | length > 0 %}
 | 
			
		||||
            <div class="ms-3">
 | 
			
		||||
                {{ self::render_toc(items=item.children, section_param=section_param) }}
 | 
			
		||||
            </div>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
{% endmacro %}
 | 
			
		||||
		Reference in New Issue
	
	Block a user