initial commit
This commit is contained in:
583
platform/src/views/contracts_view.rs
Normal file
583
platform/src/views/contracts_view.rs
Normal file
@@ -0,0 +1,583 @@
|
||||
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={title.to_string()}
|
||||
description={description.to_string()}
|
||||
tabs={tabs}
|
||||
default_tab={"Contracts".to_string()}
|
||||
/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">{"Filters"}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<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">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">{"Contracts"}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{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">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">{"Contract Details"}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<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 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>
|
||||
<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">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">{"Contract Templates"}</h5>
|
||||
</div>
|
||||
<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">
|
||||
{"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()} />
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user