477 lines
23 KiB
HTML
477 lines
23 KiB
HTML
{% extends "base.html" %}
|
|
{% load i18n static crispy_forms_tags patient_tags %}
|
|
|
|
{% block title %}{% if form.instance.pk %}{% trans "Edit OT Session" %}{% else %}{% trans "New OT Session" %}{% endif %} - Tenhal{% endblock %}
|
|
|
|
{% block css %}
|
|
<link href="{% static 'plugins/select2/css/select2.min.css' %}" rel="stylesheet" />
|
|
<style>
|
|
.skill-formset-item {
|
|
border-left: 3px solid #28a745;
|
|
transition: all 0.3s ease;
|
|
}
|
|
.skill-formset-item:hover {
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
}
|
|
.score-slider {
|
|
width: 100%;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<div>
|
|
<h1 class="page-header mb-0">
|
|
<i class="fas fa-clipboard-check me-2"></i>
|
|
{% if form.instance.pk %}{% trans "Edit OT Session" %}{% else %}{% trans "New OT Session (OT-F-3)" %}{% endif %}
|
|
</h1>
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">{% trans "Dashboard" %}</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'ot:session_list' %}">{% trans "OT Sessions" %}</a></li>
|
|
<li class="breadcrumb-item active">{% if form.instance.pk %}{% trans "Edit" %}{% else %}{% trans "New" %}{% endif %}</li>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="post" id="otSessionForm">
|
|
{% csrf_token %}
|
|
<div class="row">
|
|
<div class="col-lg-9">
|
|
<!-- Basic Information -->
|
|
<div class="card mb-3">
|
|
<div class="card-header bg-primary text-white">
|
|
<h5 class="mb-0"><i class="fas fa-info-circle me-2"></i>{% trans "Session Information" %}</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Hidden fields -->
|
|
<input type="hidden" name="patient" value="{% if patient %}{{ patient.id }}{% elif form.instance.patient_id %}{{ form.instance.patient_id }}{% endif %}">
|
|
<input type="hidden" name="appointment" value="{% if appointment %}{{ appointment.id }}{% elif form.instance.appointment_id %}{{ form.instance.appointment_id }}{% endif %}">
|
|
|
|
<!-- Patient Display (Read-only) -->
|
|
<div class="mb-3">
|
|
<label class="form-label">{% trans "Patient" %}</label>
|
|
<div class="form-control bg-light" readonly>
|
|
{% if patient %}
|
|
<i class="fas fa-user me-2"></i>
|
|
<strong>{% patient_name patient %}</strong> - {{ patient.mrn }}
|
|
{% elif form.instance.patient %}
|
|
<i class="fas fa-user me-2"></i>
|
|
<strong>{{ form.instance.patient.full_name_en }}</strong> - {{ form.instance.patient.mrn }}
|
|
{% else %}
|
|
<span class="text-muted">{% trans "No patient selected" %}</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Provider Display (Read-only, auto-populated with current user) -->
|
|
<div class="mb-3">
|
|
<label class="form-label">{% trans "Provider" %}</label>
|
|
<div class="form-control bg-light" readonly>
|
|
<i class="fas fa-user-md me-2"></i>
|
|
<strong>{% if form.instance.provider %}{{ form.instance.provider.get_full_name }}{% else %}{{ user.get_full_name }}{% endif %}</strong>
|
|
</div>
|
|
<input type="hidden" name="provider" value="{% if form.instance.provider_id %}{{ form.instance.provider_id }}{% else %}{{ user.id }}{% endif %}">
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">{% trans "Session Date" %} <span class="text-danger">*</span></label>
|
|
<input type="date" name="session_date" class="form-control" value="{{ form.instance.session_date|date:'Y-m-d' }}" required>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">{% trans "Session Type" %} <span class="text-danger">*</span></label>
|
|
<select name="session_type" class="form-control" required>
|
|
<option value="">{% trans "Select..." %}</option>
|
|
<option value="CONSULT" {% if form.instance.session_type == "CONSULT" %}selected{% endif %}>{% trans "Consultation" %}</option>
|
|
<option value="INDIVIDUAL" {% if form.instance.session_type == "INDIVIDUAL" %}selected{% endif %}>{% trans "Individual Session" %}</option>
|
|
<option value="GROUP" {% if form.instance.session_type == "GROUP" %}selected{% endif %}>{% trans "Group Session" %}</option>
|
|
<option value="PARENT_TRAINING" {% if form.instance.session_type == "PARENT_TRAINING" %}selected{% endif %}>{% trans "Parent Training" %}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Session Metrics -->
|
|
<div class="card mb-3">
|
|
<div class="card-header bg-success text-white">
|
|
<h5 class="mb-0"><i class="fas fa-chart-bar me-2"></i>{% trans "Session Metrics (1-4 Scale)" %}</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">{% trans "Cooperative Level" %}</label>
|
|
<input type="number" name="cooperative_level" class="form-control" min="1" max="4" value="{{ form.instance.cooperative_level|default:'' }}">
|
|
<small class="text-muted">1=Poor, 2=Fair, 3=Good, 4=Excellent</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">{% trans "Distraction Tolerance" %}</label>
|
|
<input type="number" name="distraction_tolerance" class="form-control" min="1" max="4" value="{{ form.instance.distraction_tolerance|default:'' }}">
|
|
<small class="text-muted">1=Low, 2=Moderate, 3=Good, 4=High</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Activities Checklist -->
|
|
<div class="card mb-3">
|
|
<div class="card-header bg-info text-white">
|
|
<h5 class="mb-0"><i class="fas fa-tasks me-2"></i>{% trans "Today We Work On..." %}</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="activities-container">
|
|
<!-- Dynamic activity inputs will be added here -->
|
|
</div>
|
|
<button type="button" class="btn btn-sm btn-outline-info" id="add-activity">
|
|
<i class="fas fa-plus me-1"></i>{% trans "Add Activity" %}
|
|
</button>
|
|
<textarea name="activities_checklist" id="activities_checklist_json" class="form-control mt-3" rows="2" style="display:none;">{{ form.instance.activities_checklist|default:"[]" }}</textarea>
|
|
<div class="mt-2">
|
|
<small class="text-muted">
|
|
<i class="fas fa-info-circle me-1"></i>
|
|
{% trans "Add activities worked on during this session" %}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Target Skills -->
|
|
<div class="card mb-3">
|
|
<div class="card-header bg-warning text-dark">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-bullseye me-2"></i>{% trans "Target Skills (0-10 Scoring)" %}
|
|
<span class="badge bg-light text-dark ms-2">{% trans "Minimum 1 required" %}</span>
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="text-muted">{% trans "Document target skills with 0-10 scoring (0=Not Achieved, 10=Mastered)" %}</p>
|
|
|
|
<div id="skills-container">
|
|
<!-- Dynamic skill inputs will be added here -->
|
|
</div>
|
|
<button type="button" class="btn btn-sm btn-outline-warning" id="add-skill">
|
|
<i class="fas fa-plus me-1"></i>{% trans "Add Skill" %}
|
|
</button>
|
|
<textarea name="target_skills" id="target_skills_json" class="form-control mt-3" rows="2" style="display:none;">{{ form.instance.target_skills_json|default:"[]" }}</textarea>
|
|
<div class="mt-2">
|
|
<small class="text-muted">
|
|
<i class="fas fa-info-circle me-1"></i>
|
|
{% trans "Add target skills and score them from 0 (Not Achieved) to 10 (Mastered)" %}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Session Notes -->
|
|
<div class="card mb-3">
|
|
<div class="card-header bg-secondary text-white">
|
|
<h5 class="mb-0"><i class="fas fa-notes-medical me-2"></i>{% trans "Session Notes" %}</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<label class="form-label">{% trans "Observations" %}</label>
|
|
<textarea name="observations" class="form-control" rows="3">{{ form.instance.observations }}</textarea>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">{% trans "Activities Performed" %}</label>
|
|
<textarea name="activities_performed" class="form-control" rows="3">{{ form.instance.activities_performed }}</textarea>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">{% trans "Recommendations" %}</label>
|
|
<textarea name="recommendations" class="form-control" rows="3">{{ form.instance.recommendations }}</textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Submit Buttons -->
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<button type="submit" class="btn btn-primary btn-lg">
|
|
<i class="fas fa-save me-2"></i>{% trans "Save OT Session" %}
|
|
</button>
|
|
<a href="{% url 'ot:session_list' %}" class="btn btn-outline-secondary btn-lg">
|
|
<i class="fas fa-times me-2"></i>{% trans "Cancel" %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<div class="col-lg-3">
|
|
<div class="card mb-3">
|
|
<div class="card-header">
|
|
<h6 class="mb-0"><i class="fas fa-question-circle me-2"></i>{% trans "Help" %}</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="small">{% trans "Complete the OT session form (OT-F-3)." %}</p>
|
|
<ul class="small">
|
|
<li>{% trans "Rate cooperative level (1-4)" %}</li>
|
|
<li>{% trans "Rate distraction tolerance (1-4)" %}</li>
|
|
<li>{% trans "List activities worked on" %}</li>
|
|
<li>{% trans "Score target skills (0-10)" %}</li>
|
|
<li>{% trans "Document observations" %}</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0"><i class="fas fa-info-circle me-2"></i>{% trans "Scoring Guide" %}</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="small fw-bold">{% trans "Target Skills (0-10)" %}:</p>
|
|
<ul class="small">
|
|
<li>0: {% trans "Not Achieved" %}</li>
|
|
<li>1-3: {% trans "Emerging" %}</li>
|
|
<li>4-6: {% trans "Developing" %}</li>
|
|
<li>7-9: {% trans "Proficient" %}</li>
|
|
<li>10: {% trans "Mastered" %}</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script src="{% static 'plugins/select2/dist/js/select2.min.js' %}"></script>
|
|
<script>
|
|
$(document).ready(function() {
|
|
// Activities Checklist Dynamic UI
|
|
let activityCounter = 0;
|
|
const activitiesContainer = $('#activities-container');
|
|
const activitiesJsonField = $('#activities_checklist_json');
|
|
|
|
// Parse existing activities from JSON
|
|
function loadExistingActivities() {
|
|
try {
|
|
const jsonData = activitiesJsonField.val().trim();
|
|
if (jsonData && jsonData !== '[]') {
|
|
const activities = JSON.parse(jsonData);
|
|
activities.forEach(activity => {
|
|
addActivityRow(activity);
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.error('Error parsing activities JSON:', e);
|
|
}
|
|
|
|
// If no activities loaded, add one empty row
|
|
if (activityCounter === 0) {
|
|
addActivityRow('');
|
|
}
|
|
}
|
|
|
|
// Add activity row
|
|
function addActivityRow(activity = '') {
|
|
activityCounter++;
|
|
const row = $(`
|
|
<div class="row mb-2 activity-row" data-id="${activityCounter}">
|
|
<div class="col-md-10">
|
|
<input type="text" class="form-control activity-name" placeholder="{% trans 'Activity name' %}" value="${activity}">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<button type="button" class="btn btn-sm btn-outline-danger remove-activity w-100">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`);
|
|
activitiesContainer.append(row);
|
|
updateActivitiesJson();
|
|
}
|
|
|
|
// Remove activity row
|
|
$(document).on('click', '.remove-activity', function() {
|
|
if ($('.activity-row').length > 1) {
|
|
$(this).closest('.activity-row').remove();
|
|
updateActivitiesJson();
|
|
} else {
|
|
showAlertModal('{% trans "At least one activity is required" %}', 'warning');
|
|
}
|
|
});
|
|
|
|
// Add activity button
|
|
$('#add-activity').on('click', function() {
|
|
addActivityRow('');
|
|
});
|
|
|
|
// Update JSON when inputs change
|
|
$(document).on('input', '.activity-name', function() {
|
|
updateActivitiesJson();
|
|
});
|
|
|
|
// Update hidden JSON field
|
|
function updateActivitiesJson() {
|
|
const activities = [];
|
|
$('.activity-row').each(function() {
|
|
const name = $(this).find('.activity-name').val().trim();
|
|
if (name) {
|
|
activities.push(name);
|
|
}
|
|
});
|
|
activitiesJsonField.val(JSON.stringify(activities));
|
|
}
|
|
|
|
// Load existing activities on page load
|
|
loadExistingActivities();
|
|
|
|
// Target Skills Dynamic UI
|
|
let skillCounter = 0;
|
|
const skillsContainer = $('#skills-container');
|
|
const skillsJsonField = $('#target_skills_json');
|
|
|
|
// Parse existing skills from JSON
|
|
function loadExistingSkills() {
|
|
try {
|
|
const jsonData = skillsJsonField.val().trim();
|
|
if (jsonData && jsonData !== '[]') {
|
|
const skills = JSON.parse(jsonData);
|
|
skills.forEach(skill => {
|
|
addSkillRow(skill.name, skill.score, skill.notes);
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.error('Error parsing skills JSON:', e);
|
|
}
|
|
|
|
// If no skills loaded, add one empty row
|
|
if (skillCounter === 0) {
|
|
addSkillRow('', 5, '');
|
|
}
|
|
}
|
|
|
|
// Add skill row
|
|
function addSkillRow(name = '', score = 5, notes = '') {
|
|
skillCounter++;
|
|
const row = $(`
|
|
<div class="skill-row border rounded p-3 mb-3 " data-id="${skillCounter}">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-target me-2"></i>
|
|
{% trans "Skill" %} #${skillCounter}
|
|
</h6>
|
|
<button type="button" class="btn btn-sm btn-outline-danger remove-skill">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">{% trans "Skill Name" %} <span class="text-danger">*</span></label>
|
|
<input type="text" class="form-control skill-name" placeholder="{% trans 'e.g., Fine motor coordination' %}" value="${name}">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">{% trans "Score (0-10)" %} <span class="text-danger">*</span></label>
|
|
<div class="d-flex align-items-center gap-2">
|
|
<input type="range" class="form-range skill-score flex-grow-1" min="0" max="10" value="${score}">
|
|
<output class="badge bg-primary skill-score-display" style="min-width: 64px;">${score}</output>
|
|
</div>
|
|
<small class="text-muted">
|
|
0=Not Achieved | 1-3=Emerging | 4-6=Developing | 7-9=Proficient | 10=Mastered
|
|
</small>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">{% trans "Notes" %}</label>
|
|
<textarea class="form-control skill-notes" rows="2" placeholder="{% trans 'Optional notes about this skill' %}">${notes}</textarea>
|
|
</div>
|
|
</div>
|
|
`);
|
|
skillsContainer.append(row);
|
|
|
|
// Update score display on slider change
|
|
row.find('.skill-score').on('input', function() {
|
|
$(this).siblings('.skill-score-display').text($(this).val());
|
|
updateSkillsJson();
|
|
});
|
|
|
|
updateSkillsJson();
|
|
}
|
|
|
|
// Remove skill row
|
|
$(document).on('click', '.remove-skill', function() {
|
|
if ($('.skill-row').length > 1) {
|
|
$(this).closest('.skill-row').remove();
|
|
updateSkillsJson();
|
|
// Renumber skills
|
|
$('.skill-row').each(function(index) {
|
|
$(this).find('h6').html(`<i class="fas fa-target me-2"></i>{% trans "Skill" %} #${index + 1}`);
|
|
});
|
|
} else {
|
|
showAlertModal('{% trans "At least one skill is required" %}', 'warning');
|
|
}
|
|
});
|
|
|
|
// Add skill button
|
|
$('#add-skill').on('click', function() {
|
|
addSkillRow('', 5, '');
|
|
});
|
|
|
|
// Update JSON when inputs change
|
|
$(document).on('input', '.skill-name, .skill-notes', function() {
|
|
updateSkillsJson();
|
|
});
|
|
|
|
// Update hidden JSON field
|
|
function updateSkillsJson() {
|
|
const skills = [];
|
|
$('.skill-row').each(function() {
|
|
const name = $(this).find('.skill-name').val().trim();
|
|
const score = parseInt($(this).find('.skill-score').val());
|
|
const notes = $(this).find('.skill-notes').val().trim();
|
|
if (name) {
|
|
skills.push({
|
|
name: name,
|
|
score: score,
|
|
notes: notes
|
|
});
|
|
}
|
|
});
|
|
skillsJsonField.val(JSON.stringify(skills));
|
|
}
|
|
|
|
// Load existing skills on page load
|
|
loadExistingSkills();
|
|
|
|
// Form validation
|
|
$('#otSessionForm').on('submit', function(e) {
|
|
// Validate patient is selected
|
|
const patientId = $('input[name="patient"]').val();
|
|
if (!patientId) {
|
|
e.preventDefault();
|
|
showAlertModal('{% trans "Patient is required. Please access this form from a patient record." %}', 'error');
|
|
return false;
|
|
}
|
|
|
|
// Validate at least one activity
|
|
const activities = JSON.parse(activitiesJsonField.val() || '[]');
|
|
if (activities.length === 0) {
|
|
e.preventDefault();
|
|
showAlertModal('{% trans "Please add at least one activity" %}', 'warning');
|
|
return false;
|
|
}
|
|
|
|
// Validate at least one skill
|
|
const skills = JSON.parse(skillsJsonField.val() || '[]');
|
|
if (skills.length === 0) {
|
|
e.preventDefault();
|
|
showAlertModal('{% trans "Please add at least one target skill" %}', 'warning');
|
|
return false;
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|