hostbasket/actix_mvc_app/src/views/contracts/my_contracts.html
Mahmoud-Emad 464e253739 feat: Enhance contract management with new features
- Implement comprehensive contract listing with filtering by
  status and type, and search functionality.
- Add contract cloning, sharing, and cancellation features.
- Improve contract details view with enhanced UI and activity
  timeline.
- Implement signer management with add/update/delete and status
  updates, including signature data handling and rejection.
- Introduce contract creation and editing functionalities with
  markdown support.
- Add error handling for contract not found scenarios.
- Implement reminder system for pending signatures with rate
  limiting and status tracking.
- Add API endpoint for retrieving contract statistics.
- Improve logging with more descriptive messages.
- Refactor code for better structure and maintainability.
2025-06-12 13:53:33 +03:00

477 lines
24 KiB
HTML

{% extends "base.html" %}
{% block title %}My Contracts{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row mb-4">
<div class="col-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/contracts">Contracts Dashboard</a></li>
<li class="breadcrumb-item active" aria-current="page">My Contracts</li>
</ol>
</nav>
<div class="d-flex justify-content-between align-items-center">
<div>
<h1 class="display-5 mb-0">My Contracts</h1>
<p class="text-muted mb-0">Manage and track your personal contracts</p>
</div>
<div class="btn-group">
<a href="/contracts/create" class="btn btn-primary">
<i class="bi bi-plus-circle me-1"></i> Create New Contract
</a>
</div>
</div>
</div>
</div>
<!-- Quick Stats -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card bg-primary text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="card-title">Total Contracts</h6>
<h3 class="mb-0">{{ contracts|length }}</h3>
</div>
<div class="align-self-center">
<i class="bi bi-file-earmark-text fs-2"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="card-title">Pending Signatures</h6>
<h3 class="mb-0" id="pending-count">0</h3>
</div>
<div class="align-self-center">
<i class="bi bi-clock fs-2"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="card-title">Signed</h6>
<h3 class="mb-0" id="signed-count">0</h3>
</div>
<div class="align-self-center">
<i class="bi bi-check-circle fs-2"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-secondary text-white">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="card-title">Drafts</h6>
<h3 class="mb-0" id="draft-count">0</h3>
</div>
<div class="align-self-center">
<i class="bi bi-pencil fs-2"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Filters -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="bi bi-funnel me-1"></i> Filters & Search
</h5>
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse"
data-bs-target="#filtersCollapse" aria-expanded="false" aria-controls="filtersCollapse">
<i class="bi bi-chevron-down"></i> Toggle Filters
</button>
</div>
<div class="collapse show" id="filtersCollapse">
<div class="card-body">
<form action="/contracts/my-contracts" method="get" class="row g-3">
<div class="col-md-3">
<label for="status" class="form-label">Status</label>
<select class="form-select" id="status" name="status">
<option value="">All Statuses</option>
<option value="Draft" {% if current_status_filter=="Draft" %}selected{% endif %}>
Draft</option>
<option value="PendingSignatures" {% if current_status_filter=="PendingSignatures"
%}selected{% endif %}>Pending Signatures</option>
<option value="Signed" {% if current_status_filter=="Signed" %}selected{% endif %}>
Signed</option>
<option value="Active" {% if current_status_filter=="Active" %}selected{% endif %}>
Active</option>
<option value="Expired" {% if current_status_filter=="Expired" %}selected{% endif
%}>Expired</option>
<option value="Cancelled" {% if current_status_filter=="Cancelled" %}selected{%
endif %}>Cancelled</option>
</select>
</div>
<div class="col-md-3">
<label for="type" class="form-label">Contract Type</label>
<select class="form-select" id="type" name="type">
<option value="">All Types</option>
<option value="Service Agreement" {% if current_type_filter=="Service Agreement"
%}selected{% endif %}>Service Agreement</option>
<option value="Employment Contract" {% if current_type_filter=="Employment Contract"
%}selected{% endif %}>Employment Contract</option>
<option value="Non-Disclosure Agreement" {% if
current_type_filter=="Non-Disclosure Agreement" %}selected{% endif %}>
Non-Disclosure Agreement</option>
<option value="Service Level Agreement" {% if
current_type_filter=="Service Level Agreement" %}selected{% endif %}>Service
Level Agreement</option>
<option value="Partnership Agreement" {% if
current_type_filter=="Partnership Agreement" %}selected{% endif %}>Partnership
Agreement</option>
<option value="Other" {% if current_type_filter=="Other" %}selected{% endif %}>Other
</option>
</select>
</div>
<div class="col-md-3">
<label for="search" class="form-label">Search</label>
<input type="text" class="form-control" id="search" name="search"
placeholder="Search by title or description"
value="{{ current_search_filter | default(value='') }}">
</div>
<div class="col-md-3 d-flex align-items-end">
<button type="submit" class="btn btn-primary w-100">Apply Filters</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Contract List -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="bi bi-file-earmark-text me-1"></i> My Contracts
{% if contracts and contracts | length > 0 %}
<span class="badge bg-primary ms-2">{{ contracts|length }}</span>
{% endif %}
</h5>
<div class="btn-group">
<a href="/contracts/statistics" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-graph-up me-1"></i> Statistics
</a>
</div>
</div>
<div class="card-body">
{% if contracts and contracts | length > 0 %}
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th scope="col">
<div class="d-flex align-items-center">
Contract Title
<i class="bi bi-arrow-down-up ms-1 text-muted" style="cursor: pointer;"
onclick="sortTable(0)"></i>
</div>
</th>
<th scope="col">Type</th>
<th scope="col">Status</th>
<th scope="col">Progress</th>
<th scope="col">
<div class="d-flex align-items-center">
Created
<i class="bi bi-arrow-down-up ms-1 text-muted" style="cursor: pointer;"
onclick="sortTable(4)"></i>
</div>
</th>
<th scope="col">Last Updated</th>
<th scope="col" class="text-center">Actions</th>
</tr>
</thead>
<tbody>
{% for contract in contracts %}
<tr
class="{% if contract.status == 'Expired' %}table-danger{% elif contract.status == 'PendingSignatures' %}table-warning{% elif contract.status == 'Signed' %}table-success{% endif %}">
<td>
<div>
<a href="/contracts/{{ contract.id }}" class="fw-bold text-decoration-none">
{{ contract.title }}
</a>
{% if contract.description %}
<div class="small text-muted">{{ contract.description }}</div>
{% endif %}
</div>
</td>
<td>
<span class="badge bg-light text-dark">{{ contract.contract_type }}</span>
</td>
<td>
<span
class="badge {% if contract.status == 'Signed' or contract.status == 'Active' %}bg-success{% elif contract.status == 'PendingSignatures' %}bg-warning text-dark{% elif contract.status == 'Draft' %}bg-secondary{% elif contract.status == 'Expired' %}bg-danger{% elif contract.status == 'Cancelled' %}bg-dark{% else %}bg-info{% endif %}">
{% if contract.status == 'PendingSignatures' %}
<i class="bi bi-clock me-1"></i>
{% elif contract.status == 'Signed' %}
<i class="bi bi-check-circle me-1"></i>
{% elif contract.status == 'Draft' %}
<i class="bi bi-pencil me-1"></i>
{% elif contract.status == 'Expired' %}
<i class="bi bi-exclamation-triangle me-1"></i>
{% endif %}
{{ contract.status }}
</span>
</td>
<td>
{% if contract.signers|length > 0 %}
<div class="d-flex align-items-center">
<div class="progress me-2" style="width: 60px; height: 8px;">
<div class="progress-bar bg-success" role="progressbar"
style="width: 0%" data-contract-id="{{ contract.id }}">
</div>
</div>
<small class="text-muted">{{ contract.signed_signers }}/{{
contract.signers|length }}</small>
</div>
{% else %}
<span class="text-muted small">No signers</span>
{% endif %}
</td>
<td>
<div class="small">
{{ contract.created_at | date(format="%b %d, %Y") }}
<div class="text-muted">{{ contract.created_at | date(format="%I:%M %p") }}
</div>
</div>
</td>
<td>
<div class="small">
{{ contract.updated_at | date(format="%b %d, %Y") }}
<div class="text-muted">{{ contract.updated_at | date(format="%I:%M %p") }}
</div>
</div>
</td>
<td class="text-center">
<div class="btn-group">
<a href="/contracts/{{ contract.id }}" class="btn btn-sm btn-primary"
title="View Details">
<i class="bi bi-eye"></i>
</a>
{% if contract.status == 'Draft' %}
<a href="/contracts/{{ contract.id }}/edit"
class="btn btn-sm btn-outline-secondary" title="Edit Contract">
<i class="bi bi-pencil"></i>
</a>
<button class="btn btn-sm btn-outline-danger" title="Delete Contract"
onclick="deleteContract('{{ contract.id }}', '{{ contract.title }}')">
<i class="bi bi-trash"></i>
</button>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-5">
<div class="mb-4">
<i class="bi bi-file-earmark-text display-1 text-muted"></i>
</div>
<h4 class="text-muted mb-3">No Contracts Found</h4>
<p class="text-muted mb-4">You haven't created any contracts yet. Get started by creating your
first contract.</p>
<div class="d-flex justify-content-center gap-2">
<a href="/contracts/create" class="btn btn-primary">
<i class="bi bi-plus-circle me-1"></i> Create Your First Contract
</a>
<a href="/contracts" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-1"></i> Back to Dashboard
</a>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- Delete Confirmation Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">Delete Contract</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle me-2"></i>
<strong>Warning:</strong> This action cannot be undone!
</div>
<p>Are you sure you want to delete the contract "<strong id="contractTitle"></strong>"?</p>
<p>This will permanently remove:</p>
<ul>
<li>The contract document and all its content</li>
<li>All signers and their signatures</li>
<li>All revisions and history</li>
<li>Any associated files or attachments</li>
</ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" id="confirmDeleteBtn">
<i class="bi bi-trash me-1"></i> Delete Contract
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
console.log('My Contracts page scripts loading...');
// Delete contract functionality using Bootstrap modal
window.deleteContract = function (contractId, contractTitle) {
console.log('Delete contract called:', contractId, contractTitle);
// Set the contract title in the modal
document.getElementById('contractTitle').textContent = contractTitle;
// Store the contract ID for later use
window.currentDeleteContractId = contractId;
// Show the modal
const deleteModal = new bootstrap.Modal(document.getElementById('deleteModal'));
deleteModal.show();
};
// Simple table sorting functionality
window.sortTable = function (columnIndex) {
console.log('Sorting table by column:', columnIndex);
const table = document.querySelector('.table tbody');
const rows = Array.from(table.querySelectorAll('tr'));
// Toggle sort direction
const isAscending = table.dataset.sortDirection !== 'asc';
table.dataset.sortDirection = isAscending ? 'asc' : 'desc';
rows.sort((a, b) => {
const aText = a.cells[columnIndex].textContent.trim();
const bText = b.cells[columnIndex].textContent.trim();
// Handle date sorting for created/updated columns
if (columnIndex === 4 || columnIndex === 5) {
const aDate = new Date(aText);
const bDate = new Date(bText);
return isAscending ? aDate - bDate : bDate - aDate;
}
// Handle text sorting
return isAscending ? aText.localeCompare(bText) : bText.localeCompare(aText);
});
// Re-append sorted rows
rows.forEach(row => table.appendChild(row));
// Update sort indicators
document.querySelectorAll('.bi-arrow-down-up').forEach(icon => {
icon.className = 'bi bi-arrow-down-up ms-1 text-muted';
});
const currentIcon = document.querySelectorAll('.bi-arrow-down-up')[columnIndex === 4 ? 1 : 0];
if (currentIcon) {
currentIcon.className = `bi ${isAscending ? 'bi-arrow-up' : 'bi-arrow-down'} ms-1 text-primary`;
}
};
// Calculate statistics and update progress bars
function updateStatistics() {
const rows = document.querySelectorAll('.table tbody tr');
let totalContracts = rows.length;
let pendingCount = 0;
let signedCount = 0;
let draftCount = 0;
rows.forEach(row => {
const statusCell = row.cells[2];
const statusText = statusCell.textContent.trim();
if (statusText.includes('PendingSignatures') || statusText.includes('Pending')) {
pendingCount++;
} else if (statusText.includes('Signed')) {
signedCount++;
} else if (statusText.includes('Draft')) {
draftCount++;
}
// Update progress bars
const progressBar = row.querySelector('.progress-bar');
if (progressBar) {
const signersText = row.cells[3].textContent.trim();
if (signersText !== 'No signers') {
const [signed, total] = signersText.split('/').map(n => parseInt(n));
const percentage = total > 0 ? Math.round((signed / total) * 100) : 0;
progressBar.style.width = percentage + '%';
}
}
});
// Update statistics cards
document.getElementById('pending-count').textContent = pendingCount;
document.getElementById('signed-count').textContent = signedCount;
document.getElementById('draft-count').textContent = draftCount;
// Update total count badge
const badge = document.querySelector('.badge.bg-primary');
if (badge) {
badge.textContent = totalContracts;
}
}
document.addEventListener('DOMContentLoaded', function () {
// Calculate initial statistics
updateStatistics();
// Handle confirm delete button click
document.getElementById('confirmDeleteBtn').addEventListener('click', function () {
console.log('User confirmed deletion, submitting form...');
// Create and submit form
const form = document.createElement('form');
form.method = 'POST';
form.action = '/contracts/' + window.currentDeleteContractId + '/delete';
form.style.display = 'none';
document.body.appendChild(form);
form.submit();
});
});
console.log('My Contracts page scripts loaded successfully');
</script>
{% endblock %}