HH/templates/observations/observation_create.html
ismail 7369d08012
All checks were successful
Build and Push Docker Image / build (push) Successful in 4m14s
feat: unified reference numbers + feedback modules QA audit
Reference numbers (unified scheme PREFIX-YYYYMM-HOSP-NNNN, e.g. CMP-202606-HHN-0001):
- new ReferenceSequence model + generate_reference() helper (apps/core)
- Complaint/Inquiry/Observation/Appreciation/Suggestion emit unified refs via save()
- prefix-based auto-routing in public track API (CMP/INQ/OBS trackable; APR/SGT internal-only)
- removed legacy CMP-/INQ- generators in ui_views, integrations, px_sources
- migrations: core.0003_referencesequence, appreciation.0006, feedback.0008, observations.0012
- unit tests (format, sanitization, monthly reset, 40-thread concurrency)

QA audit:
- isolated E2E hospital sandbox mirroring HH-N + 10 role users (create_e2e_isolated_env)
- feedback-modules-audit.spec.ts + audit helper (headed, run-to-completion)
- reports/feedback-modules-qa-report.md

Also bundles accumulated in-progress work across complaints, observations,
organizations, templates, and other modules.
2026-06-14 14:29:23 +03:00

285 lines
11 KiB
HTML

{% extends "layouts/base.html" %}
{% load i18n %}
{% load static %}
{% block title %}{% trans "New Observation" %} - PX360{% endblock %}
{% block extra_css %}
<style>
.form-label {
display: block;
font-weight: 600;
color: #1e293b;
font-size: 0.9rem;
margin-bottom: 0.5rem;
}
.field-error {
color: #ef4444;
font-size: 0.75rem;
margin-top: 0.25rem;
}
.form-control, .form-select {
width: 100%;
padding: 0.75rem 1rem;
border: 2px solid #cbd5e1;
border-radius: 0.75rem;
font-size: 0.95rem;
transition: all 0.2s;
background: white;
color: #1e293b;
font-family: 'Inter', 'Noto Kufi Arabic', sans-serif;
}
.form-control:focus, .form-select:focus {
outline: none;
border-color: #005696;
box-shadow: 0 0 0 4px rgba(0, 86, 150, 0.1);
}
textarea.form-control {
resize: vertical;
min-height: 120px;
}
</style>
{% endblock %}
{% block content %}
<div class="mb-6">
<a href="{% url 'observations:observation_list' %}" class="text-blue-600 hover:text-blue-800 text-sm font-medium inline-flex items-center gap-1 mb-2">
<i data-lucide="arrow-left" class="w-4 h-4"></i>
{% trans "Back to Observations" %}
</a>
<h1 class="text-2xl font-bold text-gray-900">{% trans "New Observation" %}</h1>
</div>
{% if messages %}
{% for message in messages %}
<div class="bg-{% if message.tags == 'error' %}red{% elif message.tags == 'warning' %}yellow{% else %}green{% endif %}-50 border border-{% if message.tags == 'error' %}red{% elif message.tags == 'warning' %}yellow{% else %}green{% endif %}-200 rounded-xl p-4 mb-4 text-sm text-{% if message.tags == 'error' %}red{% elif message.tags == 'warning' %}yellow{% else %}green{% endif %}-800">
{{ message }}
</div>
{% endfor %}
{% endif %}
<div class="bg-white rounded-xl border border-gray-200 shadow-sm p-6">
<form method="post" enctype="multipart/form-data" id="observationForm">
{% csrf_token %}
{% if communication_request %}
<input type="hidden" name="comm_req" value="{{ communication_request.id }}">
{% endif %}
<div class="grid grid-cols-1 md:grid-cols-4 gap-5 mb-5">
<div>
<label class="form-label">{% trans "Hospital" %} <span class="text-red-500">*</span></label>
{{ form.hospital }}
{% if form.hospital.errors %}
<div class="field-error">{{ form.hospital.errors.0 }}</div>
{% endif %}
</div>
<div>
<label class="form-label">{% trans "Location Type" %} <span class="text-red-500">*</span></label>
{{ form.location_type }}
{% if form.location_type.errors %}
<div class="field-error">{{ form.location_type.errors.0 }}</div>
{% endif %}
</div>
<div>
<label class="form-label">{% trans "Area" %}</label>
{{ form.area }}
{% if form.area.errors %}
<div class="field-error">{{ form.area.errors.0 }}</div>
{% endif %}
</div>
<div>
<label class="form-label">{% trans "Department" %}</label>
{{ form.assigned_department }}
{% if form.assigned_department.errors %}
<div class="field-error">{{ form.assigned_department.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-5 mb-5">
<div>
<label class="form-label">{% trans "Section" %}</label>
{{ form.section }}
{% if form.section.errors %}
<div class="field-error">{{ form.section.errors.0 }}</div>
{% endif %}
</div>
<div>
<label class="form-label">{% trans "Assign To" %}</label>
{{ form.assigned_to }}
{% if form.assigned_to.errors %}
<div class="field-error">{{ form.assigned_to.errors.0 }}</div>
{% endif %}
</div>
<div>
<label class="form-label">{% trans "When did this occur?" %}</label>
{{ form.incident_datetime }}
{% if form.incident_datetime.errors %}
<div class="field-error">{{ form.incident_datetime.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-5 mb-5">
<div>
<label class="form-label">{% trans "Patient File Number" %}</label>
{{ form.patient_file_number }}
{% if form.patient_file_number.errors %}
<div class="field-error">{{ form.patient_file_number.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="mb-5">
<label class="form-label">{% trans "Description" %} <span class="text-red-500">*</span></label>
{{ form.description }}
{% if form.description.errors %}
<div class="field-error">{{ form.description.errors.0 }}</div>
{% endif %}
</div>
<div class="flex gap-3 justify-end pt-4 border-t border-gray-100">
<a href="{% url 'observations:observation_list' %}" class="px-5 py-2.5 border border-gray-300 text-gray-700 rounded-lg text-sm font-medium hover:bg-gray-50 transition">
{% trans "Cancel" %}
</a>
<button type="submit" class="px-6 py-2.5 bg-blue-700 text-white rounded-lg text-sm font-medium hover:bg-blue-800 transition">
{% trans "Create Observation" %}
</button>
</div>
</form>
</div>
{% endblock %}
{% block extra_js %}
<script>
{% trans "Select assignee (optional)" as trans_select_assignee %}
document.addEventListener('DOMContentLoaded', function() {
const selAssignee = '{{ trans_select_assignee|escapejs }}';
const locationTypeSelect = document.getElementById('locationTypeSelect');
const areaSelect = document.getElementById('areaSelect');
const deptSelect = document.getElementById('id_assigned_department');
const assigneeSelect = document.getElementById('id_assigned_to');
const sectionSelect = document.getElementById('sectionSelect');
function getHospitalId() {
const sel = document.getElementById('hospitalSelect');
return sel ? sel.value : null;
}
function loadAreas(locationType) {
if (!areaSelect) return;
areaSelect.innerHTML = '<option value="">{% trans "Loading..." %}</option>';
var hospitalId = getHospitalId();
if (!hospitalId) {
areaSelect.innerHTML = '<option value="">{% trans "Select Area (optional)" %}</option>';
return;
}
var url = '/organizations/dropdowns/areas/?hospital=' + encodeURIComponent(hospitalId);
if (locationType) url += '&location_type=' + encodeURIComponent(locationType);
fetch(url)
.then(function(r) { return r.json(); })
.then(function(data) {
areaSelect.innerHTML = '<option value="">{% trans "Select Area (optional)" %}</option>';
(data || []).forEach(function(a) {
var opt = document.createElement('option');
opt.value = a.id;
opt.textContent = a.name_en;
areaSelect.appendChild(opt);
});
})
.catch(function(err) {
console.error('Error loading areas:', err);
areaSelect.innerHTML = '<option value="">{% trans "Error loading areas" %}</option>';
});
}
function loadDepartments(hospitalId) {
if (!deptSelect) return;
deptSelect.innerHTML = '<option value="">{% trans "Loading..." %}</option>';
if (!hospitalId) {
deptSelect.innerHTML = '<option value="">{% trans "Select Department" %}</option>';
return;
}
fetch('/organizations/dropdowns/departments-by-category/?hospital=' + encodeURIComponent(hospitalId))
.then(function(r) { return r.json(); })
.then(function(data) {
deptSelect.innerHTML = '<option value="">{% trans "Select Department" %}</option>';
(data || []).forEach(function(d) {
var opt = document.createElement('option');
opt.value = d.id;
opt.textContent = d.name_en || d.name;
deptSelect.appendChild(opt);
});
})
.catch(function(err) {
console.error('Error loading departments:', err);
deptSelect.innerHTML = '<option value="">{% trans "Error loading departments" %}</option>';
});
}
if (locationTypeSelect) {
locationTypeSelect.addEventListener('change', function() {
loadAreas(this.value);
});
}
if (deptSelect) {
deptSelect.addEventListener('change', function() {
var deptId = this.value;
if (!deptId) {
if (assigneeSelect) assigneeSelect.innerHTML = '<option value="">' + selAssignee + '</option>';
if (sectionSelect) sectionSelect.innerHTML = '<option value="">{% trans "Select Section" %}</option>';
return;
}
if (assigneeSelect) {
fetch("{% url 'observations:get_users_by_department' %}?department_id=" + deptId)
.then(function(response) { return response.json(); })
.then(function(data) {
var html = '<option value="">' + selAssignee + '</option>';
data.users.forEach(function(user) {
html += '<option value="' + user.id + '">' + user.name + '</option>';
});
assigneeSelect.innerHTML = html;
})
.catch(function() { console.error('Failed to load users'); });
}
if (sectionSelect) {
sectionSelect.innerHTML = '<option value="">{% trans "Loading..." %}</option>';
fetch('/organizations/dropdowns/sections/' + deptId + '/')
.then(function(r) { return r.json(); })
.then(function(data) {
sectionSelect.innerHTML = '<option value="">{% trans "Select Section" %}</option>';
(data || []).forEach(function(s) {
var opt = document.createElement('option');
opt.value = s.id;
opt.textContent = s.name_en || s.name;
sectionSelect.appendChild(opt);
});
})
.catch(function(err) { console.error(err); });
}
});
}
var initHospitalId = getHospitalId();
if (initHospitalId) {
loadDepartments(initHospitalId);
loadAreas(null);
}
var hospitalSelect = document.getElementById('hospitalSelect');
if (hospitalSelect) {
hospitalSelect.addEventListener('change', function() {
loadDepartments(this.value);
loadAreas(null);
if (sectionSelect) sectionSelect.innerHTML = '<option value="">{% trans "Select Section" %}</option>';
if (assigneeSelect) assigneeSelect.innerHTML = '<option value="">' + selAssignee + '</option>';
});
}
});
</script>
{% endblock %}