add contract md folder support
This commit is contained in:
parent
457f3c8268
commit
c05803ff58
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();
|
||||
|
||||
@ -130,6 +136,50 @@ impl ContractController {
|
||||
// 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();
|
||||
context.insert("signed_signers", &signed_signers);
|
||||
@ -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,8 +504,57 @@ 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 {
|
||||
id: "signer-006".to_string(),
|
||||
@ -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">
|
||||
@ -63,73 +70,42 @@
|
||||
{% 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>
|
||||
{% 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>
|
||||
<div class="alert alert-warning text-center py-5">
|
||||
<p>
|
||||
{% if contract_section_content_error is defined %}
|
||||
{{ contract_section_content_error }}
|
||||
{% 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>
|
||||
No content or markdown sections could be loaded for this contract. Please check the contract's content directory and Table of Contents configuration.
|
||||
{% 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>
|
||||
</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>
|
||||
@ -224,6 +198,85 @@
|
||||
</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 %}
|
Loading…
Reference in New Issue
Block a user