agdar/core/templates/clinic/consent_form.html
2025-11-02 14:35:35 +03:00

508 lines
18 KiB
HTML

{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load i18n static %}
{% block title %}{% if object %}{% trans "Edit Consent" %}{% else %}{% trans "New Consent" %}{% endif %} - {{ block.super }}{% endblock %}
{% block css %}
<link href="{% static 'plugins/select2/css/select2.min.css' %}" rel="stylesheet" />
<style>
/* Responsive container */
.consent-form-container {
padding: 1rem;
}
/* Header styling */
.consent-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
flex-wrap: wrap;
gap: 1rem;
}
.consent-header h1 {
margin: 0;
font-size: 1.5rem;
}
/* Card styling */
.consent-card {
border: none;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
border-radius: 0.5rem;
overflow: hidden;
}
.consent-card-body {
padding: 2rem;
}
/* Consent content display */
.consent-content-display {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
max-height: 400px;
overflow-y: auto;
white-space: pre-wrap;
font-size: 0.95rem;
line-height: 1.6;
}
/* Signature section */
.signature-section {
margin-top: 2rem;
padding: 1.5rem;
background-color: #f8f9fa;
border-radius: 0.375rem;
}
.signature-section h5 {
margin-bottom: 1rem;
color: #495057;
}
/* Signature pad */
.signature-pad-container {
margin-top: 1rem;
}
.signature-canvas {
border: 2px solid #dee2e6;
border-radius: 0.375rem;
background-color: white;
cursor: crosshair;
touch-action: none;
width: 100%;
max-width: 600px;
height: 200px;
}
.signature-canvas.signed {
border-color: #28a745;
}
.signature-controls {
margin-top: 0.75rem;
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.signature-preview {
margin-top: 1rem;
padding: 1rem;
background-color: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
display: none;
}
.signature-preview.show {
display: block;
}
.signature-preview img {
max-width: 100%;
height: auto;
border: 1px solid #dee2e6;
}
/* Form buttons */
.form-actions {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
margin-top: 2rem;
flex-wrap: wrap;
}
.form-actions .btn {
min-width: 100px;
}
/* Loading state */
.loading-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
justify-content: center;
align-items: center;
}
.loading-overlay.show {
display: flex;
}
.loading-spinner {
background-color: white;
padding: 2rem;
border-radius: 0.5rem;
text-align: center;
}
/* Mobile responsiveness */
@media (max-width: 768px) {
.consent-form-container {
padding: 0.5rem;
}
.consent-header {
flex-direction: column;
align-items: flex-start;
}
.consent-header h1 {
font-size: 1.25rem;
}
.consent-card-body {
padding: 1rem;
}
.signature-canvas {
height: 150px;
}
.form-actions {
flex-direction: column-reverse;
}
.form-actions .btn {
width: 100%;
}
}
/* Small mobile devices */
@media (max-width: 576px) {
.consent-header h1 {
font-size: 1.1rem;
}
.consent-header .btn {
width: 100%;
justify-content: center;
}
.signature-canvas {
height: 120px;
}
}
/* Tablet landscape */
@media (min-width: 769px) and (max-width: 1024px) {
.consent-card-body {
padding: 1.5rem;
}
}
/* Print styles */
@media print {
.consent-header .btn,
.form-actions,
.signature-controls {
display: none;
}
.consent-card {
box-shadow: none;
border: 1px solid #dee2e6;
}
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Header -->
<div class="consent-header">
<h1>
<i class="fa fa-file-signature me-2"></i>
{% if object %}{% trans "Edit Consent" %}{% else %}{% trans "New Consent" %}{% endif %}
</h1>
<a href="{% url 'core:consent_list' %}" class="btn btn-secondary">
<i class="fa fa-arrow-left me-1"></i>{% trans "Back to List" %}
</a>
</div>
<!-- Form Card -->
<div class="row">
<div class="col-12 col-lg-10 offset-lg-1 col-xl-8 offset-xl-2">
<div class="card consent-card">
<div class="card-body consent-card-body">
<form method="post" id="consentForm" enctype="multipart/form-data">
{% csrf_token %}
<!-- Patient and Consent Type Selection -->
<div class="row mb-3">
<div class="col-md-6">
<label for="id_patient" class="form-label">{% trans "Patient" %} <span class="text-danger">*</span></label>
{{ form.patient }}
</div>
<div class="col-md-6">
<label for="id_consent_type" class="form-label">{% trans "Consent Type" %} <span class="text-danger">*</span></label>
{{ form.consent_type }}
</div>
</div>
<!-- Consent Content Display (Read-only, auto-populated) -->
<div class="mb-3">
<label class="form-label">{% trans "Consent Content" %}</label>
<div id="consentContentDisplay" class="consent-content-display">
<p class="text-muted text-center">
<i class="fa fa-info-circle me-2"></i>
{% trans "Please select a patient and consent type to view the consent content." %}
</p>
</div>
<!-- Hidden field to store the actual content -->
<input type="hidden" name="content_text" id="id_content_text" value="{{ form.content_text.value|default:'' }}">
</div>
<!-- Signature Section -->
<div class="signature-section">
<h5><i class="fa fa-pen-nib me-2"></i>{% trans "Signature" %}</h5>
<!-- Signer Information -->
<div class="row mb-3">
<div class="col-md-6">
<label for="id_signed_by_name" class="form-label">{% trans "Full Name" %} <span class="text-danger">*</span></label>
<input type="text" name="signed_by_name" id="id_signed_by_name" class="form-control" required>
</div>
<div class="col-md-6">
<label for="id_signed_by_relationship" class="form-label">{% trans "Relationship to Patient" %}</label>
<input type="text" name="signed_by_relationship" id="id_signed_by_relationship" class="form-control" placeholder="{% trans 'e.g., Parent, Guardian, Self' %}">
</div>
</div>
<!-- Signature Pad -->
<div class="signature-pad-container">
<label class="form-label">{% trans "Draw Your Signature" %} <span class="text-danger">*</span></label>
<p class="text-muted small">{% trans "Please sign in the box below using your mouse, touchpad, or finger on touch devices." %}</p>
<canvas id="signatureCanvas" class="signature-canvas"></canvas>
<div class="signature-controls">
<button type="button" id="clearSignature" class="btn btn-outline-secondary btn-sm">
<i class="fa fa-eraser me-1"></i>{% trans "Clear" %}
</button>
<button type="button" id="undoSignature" class="btn btn-outline-secondary btn-sm">
<i class="fa fa-undo me-1"></i>{% trans "Undo" %}
</button>
<span class="text-muted small ms-2" id="signatureStatus">
<i class="fa fa-info-circle me-1"></i>{% trans "Signature required" %}
</span>
</div>
<!-- Hidden field to store signature data -->
<input type="hidden" name="signature_data" id="signatureData">
<input type="hidden" name="signature_method" value="DRAWN">
</div>
</div>
<!-- Form Actions -->
<div class="form-actions">
<a href="{% url 'core:consent_list' %}" class="btn btn-secondary">
<i class="fa fa-times me-1"></i>{% trans "Cancel" %}
</a>
<button type="submit" class="btn btn-primary" id="submitBtn" disabled>
<i class="fa fa-save me-1"></i>{% trans "Sign & Submit" %}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-spinner">
<div class="spinner-border text-primary mb-3" role="status">
<span class="visually-hidden">{% trans "Loading..." %}</span>
</div>
<p>{% trans "Processing consent..." %}</p>
</div>
</div>
{% endblock %}
{% block js %}
<script src="{% static 'plugins/select2/dist/js/select2.min.js' %}"></script>
<script src="https://cdn.jsdelivr.net/npm/signature_pad@4.1.7/dist/signature_pad.umd.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('consentForm');
const patientSelect = document.getElementById('id_patient');
const consentTypeSelect = document.getElementById('id_consent_type');
const contentDisplay = document.getElementById('consentContentDisplay');
const contentInput = document.getElementById('id_content_text');
const signedByNameInput = document.getElementById('id_signed_by_name');
const submitBtn = document.getElementById('submitBtn');
const loadingOverlay = document.getElementById('loadingOverlay');
$('#id_patient').select2({
placeholder: '{% trans "Select Patient" %}',
allowClear: true
});
// Signature Pad Setup
const canvas = document.getElementById('signatureCanvas');
const signaturePad = new SignaturePad(canvas, {
backgroundColor: 'rgb(255, 255, 255)',
penColor: 'rgb(0, 0, 0)',
minWidth: 1,
maxWidth: 2.5,
});
// Resize canvas to fit container
function resizeCanvas() {
const ratio = Math.max(window.devicePixelRatio || 1, 1);
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * ratio;
canvas.height = rect.height * ratio;
canvas.getContext('2d').scale(ratio, ratio);
signaturePad.clear();
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// Signature Pad Events
signaturePad.addEventListener('endStroke', function() {
canvas.classList.add('signed');
document.getElementById('signatureStatus').innerHTML = '<i class="fa fa-check-circle text-success me-1"></i>{% trans "Signature captured" %}';
validateForm();
});
// Clear signature
document.getElementById('clearSignature').addEventListener('click', function() {
signaturePad.clear();
canvas.classList.remove('signed');
document.getElementById('signatureStatus').innerHTML = '<i class="fa fa-info-circle me-1"></i>{% trans "Signature required" %}';
validateForm();
});
// Undo last stroke
document.getElementById('undoSignature').addEventListener('click', function() {
const data = signaturePad.toData();
if (data) {
data.pop();
signaturePad.fromData(data);
if (signaturePad.isEmpty()) {
canvas.classList.remove('signed');
document.getElementById('signatureStatus').innerHTML = '<i class="fa fa-info-circle me-1"></i>{% trans "Signature required" %}';
}
validateForm();
}
});
// Load consent content when patient and type are selected
function loadConsentContent() {
const patientId = patientSelect.value;
const consentType = consentTypeSelect.value;
if (!patientId || !consentType) {
contentDisplay.innerHTML = '<p class="text-muted text-center"><i class="fa fa-info-circle me-2"></i>{% trans "Please select a patient and consent type to view the consent content." %}</p>';
contentInput.value = '';
validateForm();
return;
}
// Show loading
contentDisplay.innerHTML = '<p class="text-muted text-center"><i class="fa fa-spinner fa-spin me-2"></i>{% trans "Loading consent content..." %}</p>';
// Fetch consent content via AJAX
fetch(`/api/consent-content/?patient_id=${patientId}&consent_type=${consentType}`)
.then(response => response.json())
.then(data => {
if (data.success) {
contentDisplay.textContent = data.content;
contentInput.value = data.content;
validateForm();
} else {
contentDisplay.innerHTML = `<p class="text-danger"><i class="fa fa-exclamation-triangle me-2"></i>${data.error || '{% trans "Error loading consent content" %}'}</p>`;
contentInput.value = '';
}
})
.catch(error => {
console.error('Error:', error);
contentDisplay.innerHTML = '<p class="text-danger"><i class="fa fa-exclamation-triangle me-2"></i>{% trans "Error loading consent content. Please try again." %}</p>';
contentInput.value = '';
});
}
// Event listeners for patient and consent type changes
patientSelect.addEventListener('change', loadConsentContent);
consentTypeSelect.addEventListener('change', loadConsentContent);
signedByNameInput.addEventListener('input', validateForm);
// Form validation
function validateForm() {
const hasPatient = patientSelect.value !== '';
const hasConsentType = consentTypeSelect.value !== '';
const hasContent = contentInput.value.trim() !== '';
const hasSignature = !signaturePad.isEmpty();
const hasName = signedByNameInput.value.trim() !== '';
const isValid = hasPatient && hasConsentType && hasContent && hasSignature && hasName;
submitBtn.disabled = !isValid;
}
// Form submission
form.addEventListener('submit', function(e) {
e.preventDefault();
if (signaturePad.isEmpty()) {
alert('{% trans "Please provide your signature before submitting." %}');
return;
}
if (!signedByNameInput.value.trim()) {
alert('{% trans "Please enter your full name." %}');
signedByNameInput.focus();
return;
}
// Get signature as base64 data URL
const signatureDataURL = signaturePad.toDataURL('image/png');
document.getElementById('signatureData').value = signatureDataURL;
// Show loading overlay
loadingOverlay.classList.add('show');
// Disable submit button
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fa fa-spinner fa-spin me-1"></i>{% trans "Submitting..." %}';
// Submit form
form.submit();
});
// Prevent double submission
let isSubmitting = false;
form.addEventListener('submit', function(e) {
if (isSubmitting) {
e.preventDefault();
return false;
}
isSubmitting = true;
});
// Load content if patient and type are pre-selected (edit mode)
if (patientSelect.value && consentTypeSelect.value) {
loadConsentContent();
}
});
</script>
{% endblock %}