508 lines
18 KiB
HTML
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 %}
|