This repository has been archived on 2025-09-16. You can view files and clone it, but cannot push or open issues or pull requests.
Files
freezone/platform/src/views/contracts_view.rs
2025-06-30 17:01:40 +02:00

599 lines
26 KiB
Rust

use yew::prelude::*;
use std::collections::HashMap;
use crate::routing::ViewContext;
use crate::components::ViewComponent;
#[derive(Clone, PartialEq)]
pub enum ContractStatus {
Draft,
PendingSignatures,
Signed,
Active,
Expired,
Cancelled,
}
impl ContractStatus {
fn to_string(&self) -> &'static str {
match self {
ContractStatus::Draft => "Draft",
ContractStatus::PendingSignatures => "Pending Signatures",
ContractStatus::Signed => "Signed",
ContractStatus::Active => "Active",
ContractStatus::Expired => "Expired",
ContractStatus::Cancelled => "Cancelled",
}
}
fn badge_class(&self) -> &'static str {
match self {
ContractStatus::Draft => "bg-secondary",
ContractStatus::PendingSignatures => "bg-warning text-dark",
ContractStatus::Signed => "bg-success",
ContractStatus::Active => "bg-success",
ContractStatus::Expired => "bg-danger",
ContractStatus::Cancelled => "bg-dark",
}
}
}
#[derive(Clone, PartialEq)]
pub struct ContractSigner {
pub id: String,
pub name: String,
pub email: String,
pub status: String, // "Pending", "Signed", "Rejected"
pub signed_at: Option<String>,
pub comments: Option<String>,
}
#[derive(Clone, PartialEq)]
pub struct Contract {
pub id: String,
pub title: String,
pub description: String,
pub contract_type: String,
pub status: ContractStatus,
pub created_by: String,
pub created_at: String,
pub updated_at: String,
pub signers: Vec<ContractSigner>,
pub terms_and_conditions: Option<String>,
pub effective_date: Option<String>,
pub expiration_date: Option<String>,
}
impl Contract {
fn signed_signers(&self) -> usize {
self.signers.iter().filter(|s| s.status == "Signed").count()
}
fn pending_signers(&self) -> usize {
self.signers.iter().filter(|s| s.status == "Pending").count()
}
}
#[derive(Properties, PartialEq)]
pub struct ContractsViewProps {
pub context: ViewContext,
}
pub enum ContractsMsg {
CreateContract,
EditContract(String),
DeleteContract(String),
ViewContract(String),
FilterByStatus(String),
FilterByType(String),
SearchContracts(String),
}
pub struct ContractsViewComponent {
contracts: Vec<Contract>,
filtered_contracts: Vec<Contract>,
status_filter: String,
type_filter: String,
search_filter: String,
}
impl Component for ContractsViewComponent {
type Message = ContractsMsg;
type Properties = ContractsViewProps;
fn create(_ctx: &Context<Self>) -> Self {
let contracts = Self::get_sample_contracts();
let filtered_contracts = contracts.clone();
Self {
contracts,
filtered_contracts,
status_filter: String::new(),
type_filter: String::new(),
search_filter: String::new(),
}
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
ContractsMsg::CreateContract => {
// Handle create contract navigation
true
}
ContractsMsg::EditContract(id) => {
// Handle edit contract navigation
true
}
ContractsMsg::DeleteContract(id) => {
// Handle delete contract
self.contracts.retain(|c| c.id != id);
self.apply_filters();
true
}
ContractsMsg::ViewContract(id) => {
// Handle view contract navigation
true
}
ContractsMsg::FilterByStatus(status) => {
self.status_filter = status;
self.apply_filters();
true
}
ContractsMsg::FilterByType(contract_type) => {
self.type_filter = contract_type;
self.apply_filters();
true
}
ContractsMsg::SearchContracts(query) => {
self.search_filter = query;
self.apply_filters();
true
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let context = &ctx.props().context;
let (title, description) = match context {
ViewContext::Business => ("Legal Contracts", "Manage business agreements and legal documents"),
ViewContext::Person => ("Contracts", "Personal contracts and agreements"),
};
// Create tabs content
let mut tabs = HashMap::new();
// Contracts Tab
tabs.insert("Contracts".to_string(), self.render_contracts_tab(ctx));
// Create Contract Tab
tabs.insert("Create Contract".to_string(), self.render_create_contract_tab(ctx));
html! {
<ViewComponent
title={Some(title.to_string())}
description={Some(description.to_string())}
tabs={Some(tabs)}
default_tab={Some("Contracts".to_string())}
use_modern_header={true}
/>
}
}
}
impl ContractsViewComponent {
fn get_sample_contracts() -> Vec<Contract> {
vec![
Contract {
id: "1".to_string(),
title: "Service Agreement - Web Development".to_string(),
description: "Development of company website and maintenance".to_string(),
contract_type: "Service Agreement".to_string(),
status: ContractStatus::PendingSignatures,
created_by: "John Smith".to_string(),
created_at: "2024-01-15".to_string(),
updated_at: "2024-01-16".to_string(),
signers: vec![
ContractSigner {
id: "s1".to_string(),
name: "Alice Johnson".to_string(),
email: "alice@example.com".to_string(),
status: "Signed".to_string(),
signed_at: Some("2024-01-16 10:30".to_string()),
comments: Some("Looks good!".to_string()),
},
ContractSigner {
id: "s2".to_string(),
name: "Bob Wilson".to_string(),
email: "bob@example.com".to_string(),
status: "Pending".to_string(),
signed_at: None,
comments: None,
},
],
terms_and_conditions: Some("# Service Agreement\n\nThis agreement outlines...".to_string()),
effective_date: Some("2024-02-01".to_string()),
expiration_date: Some("2024-12-31".to_string()),
},
Contract {
id: "2".to_string(),
title: "Non-Disclosure Agreement".to_string(),
description: "Confidentiality agreement for project collaboration".to_string(),
contract_type: "Non-Disclosure Agreement".to_string(),
status: ContractStatus::Signed,
created_by: "Sarah Davis".to_string(),
created_at: "2024-01-10".to_string(),
updated_at: "2024-01-12".to_string(),
signers: vec![
ContractSigner {
id: "s3".to_string(),
name: "Mike Brown".to_string(),
email: "mike@example.com".to_string(),
status: "Signed".to_string(),
signed_at: Some("2024-01-12 14:20".to_string()),
comments: None,
},
ContractSigner {
id: "s4".to_string(),
name: "Lisa Green".to_string(),
email: "lisa@example.com".to_string(),
status: "Signed".to_string(),
signed_at: Some("2024-01-12 16:45".to_string()),
comments: Some("Agreed to all terms".to_string()),
},
],
terms_and_conditions: Some("# Non-Disclosure Agreement\n\nThe parties agree...".to_string()),
effective_date: Some("2024-01-12".to_string()),
expiration_date: Some("2026-01-12".to_string()),
},
Contract {
id: "3".to_string(),
title: "Employment Contract - Software Engineer".to_string(),
description: "Full-time employment agreement".to_string(),
contract_type: "Employment Contract".to_string(),
status: ContractStatus::Draft,
created_by: "HR Department".to_string(),
created_at: "2024-01-20".to_string(),
updated_at: "2024-01-20".to_string(),
signers: vec![],
terms_and_conditions: Some("# Employment Contract\n\nPosition: Software Engineer...".to_string()),
effective_date: Some("2024-02-15".to_string()),
expiration_date: None,
},
]
}
fn apply_filters(&mut self) {
self.filtered_contracts = self.contracts
.iter()
.filter(|contract| {
// Status filter
if !self.status_filter.is_empty() && contract.status.to_string() != self.status_filter {
return false;
}
// Type filter
if !self.type_filter.is_empty() && contract.contract_type != self.type_filter {
return false;
}
// Search filter
if !self.search_filter.is_empty() {
let query = self.search_filter.to_lowercase();
if !contract.title.to_lowercase().contains(&query) &&
!contract.description.to_lowercase().contains(&query) {
return false;
}
}
true
})
.cloned()
.collect();
}
fn render_contracts_tab(&self, _ctx: &Context<Self>) -> Html {
html! {
<div>
// Filters Section
<div class="row mb-4">
<div class="col-12">
<div class="card shadow-sm" style="border: none;">
<div class="card-body p-4">
<div class="d-flex align-items-center mb-3">
<div class="bg-primary bg-opacity-10 rounded-3 p-2 me-3">
<i class="bi bi-funnel text-primary fs-5"></i>
</div>
<h5 class="mb-0">{"Filters"}</h5>
</div>
<div class="row g-3">
<div class="col-md-3">
<label for="status" class="form-label">{"Status"}</label>
<select class="form-select" id="status">
<option value="">{"All Statuses"}</option>
<option value="Draft">{"Draft"}</option>
<option value="Pending Signatures">{"Pending Signatures"}</option>
<option value="Signed">{"Signed"}</option>
<option value="Active">{"Active"}</option>
<option value="Expired">{"Expired"}</option>
<option value="Cancelled">{"Cancelled"}</option>
</select>
</div>
<div class="col-md-3">
<label for="type" class="form-label">{"Contract Type"}</label>
<select class="form-select" id="type">
<option value="">{"All Types"}</option>
<option value="Service Agreement">{"Service Agreement"}</option>
<option value="Employment Contract">{"Employment Contract"}</option>
<option value="Non-Disclosure Agreement">{"Non-Disclosure Agreement"}</option>
<option value="Service Level Agreement">{"Service Level Agreement"}</option>
<option value="Other">{"Other"}</option>
</select>
</div>
<div class="col-md-4">
<label for="search" class="form-label">{"Search"}</label>
<input type="text" class="form-control" id="search"
placeholder="Search by title or description" />
</div>
<div class="col-md-2 d-flex align-items-end">
<a href="#" class="btn btn-primary w-100">
<i class="bi bi-plus-circle me-1"></i>{"Create New"}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
// Contracts Table
<div class="row">
<div class="col-12">
<div class="card shadow-sm" style="border: none;">
<div class="card-body p-4">
<div class="d-flex align-items-center mb-3">
<div class="bg-success bg-opacity-10 rounded-3 p-2 me-3">
<i class="bi bi-file-earmark-text text-success fs-5"></i>
</div>
<h5 class="mb-0">{"Contracts"}</h5>
</div>
{self.render_contracts_table(_ctx)}
</div>
</div>
</div>
</div>
</div>
}
}
fn render_contracts_table(&self, ctx: &Context<Self>) -> Html {
if self.filtered_contracts.is_empty() {
return html! {
<div class="text-center py-5">
<i class="bi bi-file-earmark-text fs-1 text-muted"></i>
<p class="mt-3 text-muted">{"No contracts found"}</p>
<a href="#" class="btn btn-primary mt-2">
<i class="bi bi-plus-circle me-1"></i>{"Create New Contract"}
</a>
</div>
};
}
html! {
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>{"Contract Title"}</th>
<th>{"Type"}</th>
<th>{"Status"}</th>
<th>{"Created By"}</th>
<th>{"Signers"}</th>
<th>{"Created"}</th>
<th>{"Updated"}</th>
<th>{"Actions"}</th>
</tr>
</thead>
<tbody>
{for self.filtered_contracts.iter().map(|contract| self.render_contract_row(contract, ctx))}
</tbody>
</table>
</div>
}
}
fn render_contract_row(&self, contract: &Contract, _ctx: &Context<Self>) -> Html {
html! {
<tr>
<td>
<a href="#" class="text-decoration-none">
{&contract.title}
</a>
</td>
<td>{&contract.contract_type}</td>
<td>
<span class={format!("badge {}", contract.status.badge_class())}>
{contract.status.to_string()}
</span>
</td>
<td>{&contract.created_by}</td>
<td>{format!("{}/{}", contract.signed_signers(), contract.signers.len())}</td>
<td>{&contract.created_at}</td>
<td>{&contract.updated_at}</td>
<td>
<div class="btn-group">
<a href="#" class="btn btn-sm btn-primary" title="View">
<i class="bi bi-eye"></i>
</a>
{if matches!(contract.status, ContractStatus::Draft) {
html! {
<>
<a href="#" class="btn btn-sm btn-outline-secondary" title="Edit">
<i class="bi bi-pencil"></i>
</a>
<a href="#" class="btn btn-sm btn-outline-danger" title="Delete">
<i class="bi bi-trash"></i>
</a>
</>
}
} else {
html! {}
}}
</div>
</td>
</tr>
}
}
fn render_create_contract_tab(&self, _ctx: &Context<Self>) -> Html {
html! {
<div class="row">
<div class="col-lg-8">
<div class="card shadow-sm" style="border: none;">
<div class="card-body p-4">
<div class="d-flex align-items-center mb-4">
<div class="bg-primary bg-opacity-10 rounded-3 p-2 me-3">
<i class="bi bi-file-earmark-plus text-primary fs-5"></i>
</div>
<h5 class="mb-0">{"Contract Details"}</h5>
</div>
<form>
<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" required=true />
</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=true>
<option value="" selected=true disabled=true>{"Select a contract type"}</option>
<option value="Service Agreement">{"Service Agreement"}</option>
<option value="Employment Contract">{"Employment Contract"}</option>
<option value="Non-Disclosure Agreement">{"Non-Disclosure Agreement"}</option>
<option value="Service Level Agreement">{"Service Level Agreement"}</option>
<option value="Partnership Agreement">{"Partnership Agreement"}</option>
<option value="Other">{"Other"}</option>
</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=true></textarea>
</div>
<div class="mb-3">
<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."}
</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">
<button type="button" class="btn btn-outline-secondary me-md-2">{"Cancel"}</button>
<button type="submit" class="btn btn-primary">{"Create Contract"}</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card shadow-sm mb-4" style="border: none;">
<div class="card-body p-4">
<div class="d-flex align-items-center mb-3">
<div class="bg-info bg-opacity-10 rounded-3 p-2 me-3">
<i class="bi bi-lightbulb text-info fs-5"></i>
</div>
<h5 class="mb-0">{"Tips"}</h5>
</div>
<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>
<li>{"Send the contract for signatures"}</li>
<li>{"Track the signing progress"}</li>
</ul>
<p>{"The contract will be in "}<strong>{"Draft"}</strong>{" status until you send it for signatures."}</p>
</div>
</div>
<div class="card shadow-sm" style="border: none;">
<div class="card-body p-4">
<div class="d-flex align-items-center mb-3">
<div class="bg-warning bg-opacity-10 rounded-3 p-2 me-3">
<i class="bi bi-file-earmark-code text-warning fs-5"></i>
</div>
<h5 class="mb-0">{"Contract Templates"}</h5>
</div>
<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">
{"Non-Disclosure Agreement"}
</button>
<button type="button" class="list-group-item list-group-item-action">
{"Service Agreement"}
</button>
<button type="button" class="list-group-item list-group-item-action">
{"Employment Contract"}
</button>
<button type="button" class="list-group-item list-group-item-action">
{"Service Level Agreement"}
</button>
</div>
</div>
</div>
</div>
</div>
}
}
}
#[function_component(ContractsView)]
pub fn contracts_view(props: &ContractsViewProps) -> Html {
html! {
<ContractsViewComponent context={props.context.clone()} />
}
}