567 lines
27 KiB
HTML
567 lines
27 KiB
HTML
{% extends "base.html" %}
|
||
{% load static %}
|
||
|
||
{% block title %}{% if object %}Edit{% else %}Add{% endif %} Operating Room - {{ block.super }}{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container-fluid">
|
||
<!-- Page Header -->
|
||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||
<div>
|
||
<h1 class="h3 mb-1">
|
||
<i class="fas fa-procedures me-2"></i>{% if object %}Edit{% else %}Add{% endif %} Operating Room
|
||
</h1>
|
||
<nav aria-label="breadcrumb">
|
||
<ol class="breadcrumb mb-0">
|
||
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
||
<li class="breadcrumb-item"><a href="{% url 'operating_theatre:dashboard' %}">Operating Theatre</a></li>
|
||
<li class="breadcrumb-item"><a href="{% url 'operating_theatre:operating_room_list' %}">Operating Rooms</a></li>
|
||
{% if object %}
|
||
<li class="breadcrumb-item"><a href="{% url 'operating_theatre:operating_room_detail' object.pk %}">{{ object.room_number }}</a></li>
|
||
<li class="breadcrumb-item active">Edit</li>
|
||
{% else %}
|
||
<li class="breadcrumb-item active">Add</li>
|
||
{% endif %}
|
||
</ol>
|
||
</nav>
|
||
</div>
|
||
<div class="btn-group">
|
||
<a href="{% if object %}{% url 'operating_theatre:operating_room_detail' object.pk %}{% else %}{% url 'operating_theatre:operating_room_list' %}{% endif %}" class="btn btn-outline-secondary">
|
||
<i class="fas fa-arrow-left me-2"></i>Back
|
||
</a>
|
||
{% if object %}
|
||
<a href="{% url 'operating_theatre:operating_room_list' %}" class="btn btn-outline-primary">
|
||
<i class="fas fa-list me-2"></i>All Rooms
|
||
</a>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<form method="post" id="roomForm" novalidate>
|
||
{% csrf_token %}
|
||
<div class="row">
|
||
<!-- Main Form -->
|
||
<div class="col-lg-8">
|
||
|
||
<!-- Basic Information -->
|
||
<div class="panel panel-inverse mb-4">
|
||
<div class="panel-heading">
|
||
<h4 class="panel-title"><i class="fas fa-info-circle me-2"></i>Basic Information</h4>
|
||
<div class="panel-heading-btn">
|
||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||
</div>
|
||
</div>
|
||
<div class="panel-body">
|
||
<div class="row">
|
||
<div class="col-md-6 mb-3">
|
||
<label for="{{ form.room_number.id_for_label }}" class="form-label">Room Number <span class="text-danger">*</span></label>
|
||
{{ form.room_number }}
|
||
{% if form.room_number.errors %}<div class="invalid-feedback d-block">{{ form.room_number.errors.0 }}</div>{% endif %}
|
||
<div class="form-text">Unique identifier for the operating room</div>
|
||
</div>
|
||
<div class="col-md-6 mb-3">
|
||
<label for="{{ form.room_name.id_for_label }}" class="form-label">Room Name <span class="text-danger">*</span></label>
|
||
{{ form.room_name }}
|
||
{% if form.room_name.errors %}<div class="invalid-feedback d-block">{{ form.room_name.errors.0 }}</div>{% endif %}
|
||
<div class="form-text">Descriptive name for the operating room</div>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="col-md-6 mb-3">
|
||
<label for="{{ form.room_type.id_for_label }}" class="form-label">Room Type <span class="text-danger">*</span></label>
|
||
{{ form.room_type }}
|
||
{% if form.room_type.errors %}<div class="invalid-feedback d-block">{{ form.room_type.errors.0 }}</div>{% endif %}
|
||
<div class="form-text">Primary surgical specialty for this room</div>
|
||
</div>
|
||
<div class="col-md-6 mb-3">
|
||
<label for="{{ form.status.id_for_label }}" class="form-label">Status <span class="text-danger">*</span></label>
|
||
{{ form.status }}
|
||
{% if form.status.errors %}<div class="invalid-feedback d-block">{{ form.status.errors.0 }}</div>{% endif %}
|
||
<div class="form-text">Current operational status</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Physical Characteristics -->
|
||
<div class="panel panel-inverse mb-4">
|
||
<div class="panel-heading">
|
||
<h4 class="panel-title"><i class="fas fa-ruler-combined me-2"></i>Physical Characteristics</h4>
|
||
<div class="panel-heading-btn">
|
||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||
</div>
|
||
</div>
|
||
<div class="panel-body">
|
||
<div class="row">
|
||
<div class="col-md-4 mb-3">
|
||
<label for="{{ form.floor_number.id_for_label }}" class="form-label">Floor Number <span class="text-danger">*</span></label>
|
||
{{ form.floor_number }}
|
||
{% if form.floor_number.errors %}<div class="invalid-feedback d-block">{{ form.floor_number.errors.0 }}</div>{% endif %}
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label for="{{ form.building.id_for_label }}" class="form-label">Building</label>
|
||
{{ form.building }}
|
||
{% if form.building.errors %}<div class="invalid-feedback d-block">{{ form.building.errors.0 }}</div>{% endif %}
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label for="{{ form.wing.id_for_label }}" class="form-label">Wing/Section</label>
|
||
{{ form.wing }}
|
||
{% if form.wing.errors %}<div class="invalid-feedback d-block">{{ form.wing.errors.0 }}</div>{% endif %}
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="col-md-6 mb-3">
|
||
<label for="{{ form.room_size.id_for_label }}" class="form-label">Room Size (m²)</label>
|
||
{{ form.room_size }}
|
||
{% if form.room_size.errors %}<div class="invalid-feedback d-block">{{ form.room_size.errors.0 }}</div>{% endif %}
|
||
<div class="form-text">Room area in square meters</div>
|
||
</div>
|
||
<div class="col-md-6 mb-3">
|
||
<label for="{{ form.ceiling_height.id_for_label }}" class="form-label">Ceiling Height (m)</label>
|
||
{{ form.ceiling_height }}
|
||
{% if form.ceiling_height.errors %}<div class="invalid-feedback d-block">{{ form.ceiling_height.errors.0 }}</div>{% endif %}
|
||
<div class="form-text">Ceiling height in meters</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Environmental Controls -->
|
||
<div class="panel panel-inverse mb-4">
|
||
<div class="panel-heading">
|
||
<h4 class="panel-title"><i class="fas fa-thermometer-half me-2"></i>Environmental Controls</h4>
|
||
<div class="panel-heading-btn">
|
||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||
</div>
|
||
</div>
|
||
<div class="panel-body">
|
||
<div class="row">
|
||
<div class="col-md-6 mb-3">
|
||
<label class="form-label">Temperature Range (°C)</label>
|
||
<div class="row">
|
||
<div class="col-6">
|
||
<label for="{{ form.temperature_min.id_for_label }}" class="form-label small">Minimum</label>
|
||
{{ form.temperature_min }}
|
||
{% if form.temperature_min.errors %}<div class="invalid-feedback d-block">{{ form.temperature_min.errors.0 }}</div>{% endif %}
|
||
</div>
|
||
<div class="col-6">
|
||
<label for="{{ form.temperature_max.id_for_label }}" class="form-label small">Maximum</label>
|
||
{{ form.temperature_max }}
|
||
{% if form.temperature_max.errors %}<div class="invalid-feedback d-block">{{ form.temperature_max.errors.0 }}</div>{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6 mb-3">
|
||
<label class="form-label">Humidity Range (%)</label>
|
||
<div class="row">
|
||
<div class="col-6">
|
||
<label for="{{ form.humidity_min.id_for_label }}" class="form-label small">Minimum</label>
|
||
{{ form.humidity_min }}
|
||
{% if form.humidity_min.errors %}<div class="invalid-feedback d-block">{{ form.humidity_min.errors.0 }}</div>{% endif %}
|
||
</div>
|
||
<div class="col-6">
|
||
<label for="{{ form.humidity_max.id_for_label }}" class="form-label small">Maximum</label>
|
||
{{ form.humidity_max }}
|
||
{% if form.humidity_max.errors %}<div class="invalid-feedback d-block">{{ form.humidity_max.errors.0 }}</div>{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="col-md-6 mb-3">
|
||
<label for="{{ form.air_changes_per_hour.id_for_label }}" class="form-label">Air Changes per Hour</label>
|
||
{{ form.air_changes_per_hour }}
|
||
{% if form.air_changes_per_hour.errors %}<div class="invalid-feedback d-block">{{ form.air_changes_per_hour.errors.0 }}</div>{% endif %}
|
||
<div class="form-text">Recommended: 20+ for operating rooms</div>
|
||
</div>
|
||
<div class="col-md-6 mb-3">
|
||
<label class="form-label">Room Pressure</label>
|
||
<div class="form-check">
|
||
{{ form.positive_pressure }}
|
||
<label for="{{ form.positive_pressure.id_for_label }}" class="form-check-label">Positive Pressure Room</label>
|
||
</div>
|
||
<div class="form-text">Most ORs should maintain positive pressure</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Capabilities -->
|
||
<div class="panel panel-inverse mb-4">
|
||
<div class="panel-heading">
|
||
<h4 class="panel-title"><i class="fas fa-cogs me-2"></i>Surgical Capabilities</h4>
|
||
<div class="panel-heading-btn">
|
||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||
</div>
|
||
</div>
|
||
<div class="panel-body">
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<h6 class="fw-semibold mb-3">Surgical Approaches</h6>
|
||
<div class="form-check mb-2">
|
||
{{ form.supports_robotic }}
|
||
<label for="{{ form.supports_robotic.id_for_label }}" class="form-check-label">Robotic Surgery</label>
|
||
</div>
|
||
<div class="form-check mb-2">
|
||
{{ form.supports_laparoscopic }}
|
||
<label for="{{ form.supports_laparoscopic.id_for_label }}" class="form-check-label">Laparoscopic Surgery</label>
|
||
</div>
|
||
<div class="form-check mb-2">
|
||
{{ form.supports_microscopy }}
|
||
<label for="{{ form.supports_microscopy.id_for_label }}" class="form-check-label">Surgical Microscopy</label>
|
||
</div>
|
||
<div class="form-check mb-3">
|
||
{{ form.supports_laser }}
|
||
<label for="{{ form.supports_laser.id_for_label }}" class="form-check-label">Laser Surgery</label>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<h6 class="fw-semibold mb-3">Imaging Capabilities</h6>
|
||
<div class="form-check mb-2">
|
||
{{ form.has_c_arm }}
|
||
<label for="{{ form.has_c_arm.id_for_label }}" class="form-check-label">C-Arm Fluoroscopy</label>
|
||
</div>
|
||
<div class="form-check mb-2">
|
||
{{ form.has_ct }}
|
||
<label for="{{ form.has_ct.id_for_label }}" class="form-check-label">Intraoperative CT</label>
|
||
</div>
|
||
<div class="form-check mb-2">
|
||
{{ form.has_mri }}
|
||
<label for="{{ form.has_mri.id_for_label }}" class="form-check-label">Intraoperative MRI</label>
|
||
</div>
|
||
<div class="form-check mb-2">
|
||
{{ form.has_ultrasound }}
|
||
<label for="{{ form.has_ultrasound.id_for_label }}" class="form-check-label">Ultrasound</label>
|
||
</div>
|
||
<div class="form-check mb-3">
|
||
{{ form.has_neuromonitoring }}
|
||
<label for="{{ form.has_neuromonitoring.id_for_label }}" class="form-check-label">Neuromonitoring</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Scheduling Configuration -->
|
||
<div class="panel panel-inverse mb-4">
|
||
<div class="panel-heading">
|
||
<h4 class="panel-title"><i class="fas fa-clock me-2"></i>Scheduling Configuration</h4>
|
||
<div class="panel-heading-btn">
|
||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||
</div>
|
||
</div>
|
||
<div class="panel-body">
|
||
<div class="row">
|
||
<div class="col-md-4 mb-3">
|
||
<label for="{{ form.max_case_duration.id_for_label }}" class="form-label">Max Case Duration (minutes)</label>
|
||
{{ form.max_case_duration }}
|
||
{% if form.max_case_duration.errors %}<div class="invalid-feedback d-block">{{ form.max_case_duration.errors.0 }}</div>{% endif %}
|
||
<div class="form-text">Maximum allowed case duration</div>
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label for="{{ form.turnover_time.id_for_label }}" class="form-label">Turnover Time (minutes)</label>
|
||
{{ form.turnover_time }}
|
||
{% if form.turnover_time.errors %}<div class="invalid-feedback d-block">{{ form.turnover_time.errors.0 }}</div>{% endif %}
|
||
<div class="form-text">Standard time between cases</div>
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label for="{{ form.cleaning_time.id_for_label }}" class="form-label">Deep Cleaning Time (minutes)</label>
|
||
{{ form.cleaning_time }}
|
||
{% if form.cleaning_time.errors %}<div class="invalid-feedback d-block">{{ form.cleaning_time.errors.0 }}</div>{% endif %}
|
||
<div class="form-text">Time required for deep cleaning</div>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="col-md-6 mb-3">
|
||
<label for="{{ form.required_nurses.id_for_label }}" class="form-label">Required Nurses</label>
|
||
{{ form.required_nurses }}
|
||
{% if form.required_nurses.errors %}<div class="invalid-feedback d-block">{{ form.required_nurses.errors.0 }}</div>{% endif %}
|
||
<div class="form-text">Minimum nursing staff required</div>
|
||
</div>
|
||
<div class="col-md-6 mb-3">
|
||
<label for="{{ form.required_techs.id_for_label }}" class="form-label">Required Technicians</label>
|
||
{{ form.required_techs }}
|
||
{% if form.required_techs.errors %}<div class="invalid-feedback d-block">{{ form.required_techs.errors.0 }}</div>{% endif %}
|
||
<div class="form-text">Minimum technical staff required</div>
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="col-md-6 mb-3">
|
||
<div class="form-check">
|
||
{{ form.is_active }}
|
||
<label for="{{ form.is_active.id_for_label }}" class="form-check-label">Room is Active</label>
|
||
</div>
|
||
<div class="form-text">Available for scheduling</div>
|
||
</div>
|
||
<div class="col-md-6 mb-3">
|
||
<div class="form-check">
|
||
{{ form.accepts_emergency }}
|
||
<label for="{{ form.accepts_emergency.id_for_label }}" class="form-check-label">Accepts Emergency Cases</label>
|
||
</div>
|
||
<div class="form-text">Can be used for emergency procedures</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- Sidebar -->
|
||
<div class="col-lg-4">
|
||
|
||
<!-- Actions -->
|
||
<div class="panel panel-inverse mb-4">
|
||
<div class="panel-heading">
|
||
<h4 class="panel-title"><i class="fas fa-save me-2"></i>Actions</h4>
|
||
<div class="panel-heading-btn">
|
||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||
</div>
|
||
</div>
|
||
<div class="panel-body">
|
||
<div class="d-grid gap-2">
|
||
<button type="submit" class="btn btn-primary">
|
||
<i class="fas fa-save me-2"></i>{% if object %}Update{% else %}Create{% endif %} Operating Room
|
||
</button>
|
||
<button type="button" class="btn btn-outline-secondary" onclick="saveDraft()">
|
||
<i class="fas fa-file-alt me-2"></i>Save as Draft
|
||
</button>
|
||
<a href="{% if object %}{% url 'operating_theatre:operating_room_detail' object.pk %}{% else %}{% url 'operating_theatre:operating_room_list' %}{% endif %}" class="btn btn-outline-danger">
|
||
<i class="fas fa-times me-2"></i>Cancel
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Help & Guidelines -->
|
||
<div class="panel panel-inverse mb-4">
|
||
<div class="panel-heading">
|
||
<h4 class="panel-title"><i class="fas fa-question-circle me-2"></i>Help & Guidelines</h4>
|
||
<div class="panel-heading-btn">
|
||
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
||
</div>
|
||
</div>
|
||
<div class="panel-body">
|
||
<div class="accordion" id="helpAccordion">
|
||
<div class="accordion-item">
|
||
<h2 class="accordion-header">
|
||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#roomTypes">Room Types</button>
|
||
</h2>
|
||
<div id="roomTypes" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
||
<div class="accordion-body small">
|
||
<ul class="list-unstyled">
|
||
<li><strong>General:</strong> Multi-purpose surgical procedures</li>
|
||
<li><strong>Cardiac:</strong> Heart and vascular surgery</li>
|
||
<li><strong>Neuro:</strong> Brain and spine surgery</li>
|
||
<li><strong>Trauma:</strong> Emergency trauma cases</li>
|
||
<li><strong>Robotic:</strong> Robot-assisted procedures</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="accordion-item">
|
||
<h2 class="accordion-header">
|
||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#environmental">Environmental Standards</button>
|
||
</h2>
|
||
<div id="environmental" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
||
<div class="accordion-body small">
|
||
<ul class="list-unstyled">
|
||
<li><strong>Temperature:</strong> 18–26°C typical range</li>
|
||
<li><strong>Humidity:</strong> 30–60% recommended</li>
|
||
<li><strong>Air Changes:</strong> Minimum 20 per hour</li>
|
||
<li><strong>Pressure:</strong> Positive pressure preferred</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="accordion-item">
|
||
<h2 class="accordion-header">
|
||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#scheduling">Scheduling Guidelines</button>
|
||
</h2>
|
||
<div id="scheduling" class="accordion-collapse collapse" data-bs-parent="#helpAccordion">
|
||
<div class="accordion-body small">
|
||
<ul class="list-unstyled">
|
||
<li><strong>Turnover:</strong> 15–45 minutes typical</li>
|
||
<li><strong>Cleaning:</strong> 30–60 minutes for deep clean</li>
|
||
<li><strong>Staffing:</strong> Minimum 2 nurses, 1 tech</li>
|
||
<li><strong>Max Duration:</strong> 8 hours typical limit</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Validation Summary -->
|
||
<div class="panel panel-inverse" id="validationSummary" style="display: none;">
|
||
<div class="panel-heading bg-danger text-white">
|
||
<h4 class="panel-title"><i class="fas fa-exclamation-triangle me-2"></i>Validation Errors</h4>
|
||
</div>
|
||
<div class="panel-body">
|
||
<ul id="errorList" class="list-unstyled mb-0"></ul>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<script>
|
||
// CSRF from cookie (aligns with detail template pattern)
|
||
function getCookie(name) {
|
||
const value = `; ${document.cookie}`;
|
||
const parts = value.split(`; ${name}=`);
|
||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||
}
|
||
|
||
// Form validation
|
||
document.getElementById('roomForm').addEventListener('submit', function(e) {
|
||
const errors = [];
|
||
|
||
// Required field validation
|
||
const requiredFields = ['room_number', 'room_name', 'room_type', 'floor_number'];
|
||
requiredFields.forEach(field => {
|
||
const input = document.getElementById(`id_${field}`);
|
||
if (input && !String(input.value || '').trim()) {
|
||
errors.push(`${field.replace('_', ' ')} is required`);
|
||
}
|
||
});
|
||
|
||
// Temperature range validation
|
||
const tmin = parseFloat(document.getElementById('id_temperature_min').value);
|
||
const tmax = parseFloat(document.getElementById('id_temperature_max').value);
|
||
if (!isNaN(tmin) && !isNaN(tmax) && tmin >= tmax) {
|
||
errors.push('Maximum temperature must be greater than minimum temperature');
|
||
}
|
||
|
||
// Humidity range validation
|
||
const hmin = parseFloat(document.getElementById('id_humidity_min').value);
|
||
const hmax = parseFloat(document.getElementById('id_humidity_max').value);
|
||
if (!isNaN(hmin) && !isNaN(hmax) && hmin >= hmax) {
|
||
errors.push('Maximum humidity must be greater than minimum humidity');
|
||
}
|
||
|
||
if (errors.length > 0) {
|
||
e.preventDefault();
|
||
showValidationErrors(errors);
|
||
}
|
||
});
|
||
|
||
function showValidationErrors(errors) {
|
||
const errorList = document.getElementById('errorList');
|
||
const validationSummary = document.getElementById('validationSummary');
|
||
errorList.innerHTML = '';
|
||
errors.forEach(err => {
|
||
const li = document.createElement('li');
|
||
li.innerHTML = `<i class="fas fa-times text-danger me-2"></i>${err}`;
|
||
errorList.appendChild(li);
|
||
});
|
||
validationSummary.style.display = 'block';
|
||
validationSummary.scrollIntoView({ behavior: 'smooth' });
|
||
}
|
||
|
||
// Save draft (uses cookie CSRF, no reliance on hidden input)
|
||
function saveDraft() {
|
||
const form = document.getElementById('roomForm');
|
||
const formData = new FormData(form);
|
||
formData.append('save_draft', 'true');
|
||
|
||
fetch(window.location.href, {
|
||
method: 'POST',
|
||
body: formData,
|
||
headers: { 'X-CSRFToken': getCookie('csrftoken') }
|
||
})
|
||
.then(r => r.json())
|
||
.then(data => alert(data && data.success ? 'Draft saved successfully' : 'Error saving draft'))
|
||
.catch(() => alert('Error saving draft'));
|
||
}
|
||
|
||
// Real-time validation feedback
|
||
document.querySelectorAll('input, select').forEach(el => {
|
||
el.addEventListener('blur', () => validateField(el));
|
||
});
|
||
|
||
function validateField(field) {
|
||
const value = String(field.value || '').trim();
|
||
field.classList.remove('is-valid', 'is-invalid');
|
||
|
||
if (field.hasAttribute('required') && !value) {
|
||
field.classList.add('is-invalid');
|
||
return false;
|
||
}
|
||
if (field.name === 'room_number' && value && !/^[A-Z0-9\-]+$/i.test(value)) {
|
||
field.classList.add('is-invalid');
|
||
return false;
|
||
}
|
||
if (value) field.classList.add('is-valid');
|
||
return true;
|
||
}
|
||
|
||
// Dynamic form updates by room type
|
||
const roomTypeEl = document.getElementById('id_room_type');
|
||
if (roomTypeEl) {
|
||
roomTypeEl.addEventListener('change', function() {
|
||
const roomType = this.value;
|
||
const caps = {
|
||
'CARDIAC': { 'has_c_arm': true, 'supports_microscopy': true, 'required_nurses': 3, 'required_techs': 2 },
|
||
'NEURO': { 'supports_microscopy': true, 'has_neuromonitoring': true, 'required_nurses': 3, 'required_techs': 2 },
|
||
'ROBOTIC': { 'supports_robotic': true, 'supports_laparoscopic': true, 'required_nurses': 3, 'required_techs': 3 },
|
||
'TRAUMA': { 'has_c_arm': true, 'accepts_emergency': true, 'required_nurses': 4, 'required_techs': 2 }
|
||
};
|
||
if (caps[roomType]) {
|
||
Object.keys(caps[roomType]).forEach(field => {
|
||
const input = document.getElementById(`id_${field}`);
|
||
if (!input) return;
|
||
if (input.type === 'checkbox') input.checked = !!caps[roomType][field];
|
||
else input.value = caps[roomType][field];
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
// Keyboard shortcuts
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.ctrlKey || e.metaKey) {
|
||
if (e.key === 's') { e.preventDefault(); document.getElementById('roomForm').submit(); }
|
||
if (e.key === 'd') { e.preventDefault(); saveDraft(); }
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<style>
|
||
/* Inputs */
|
||
.form-control, .form-select {
|
||
border-radius: 0.375rem;
|
||
border: 1px solid #ced4da;
|
||
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
|
||
}
|
||
.form-control:focus, .form-select:focus {
|
||
border-color: #86b7fe;
|
||
box-shadow: 0 0 0 .25rem rgba(13,110,253,.25);
|
||
}
|
||
.is-valid { border-color: #198754; }
|
||
.is-invalid { border-color: #dc3545; }
|
||
.form-check-input:checked { background-color: #0d6efd; border-color: #0d6efd; }
|
||
|
||
.accordion-button:not(.collapsed) { background-color: #e7f1ff; color: #0c63e4; }
|
||
|
||
/* Panel theme tweaks to mirror detail page */
|
||
.panel-title { color: #495057; }
|
||
.progress { background-color: #e9ecef; }
|
||
.badge { font-size: .75rem; }
|
||
.table-borderless td { padding: .5rem 0; }
|
||
|
||
/* Print: target panels, not cards */
|
||
@media print {
|
||
.btn, .dropdown, nav, .panel-heading .fa-cog { display: none !important; }
|
||
.panel { border: 1px solid #dee2e6 !important; box-shadow: none !important; }
|
||
}
|
||
|
||
/* Responsive */
|
||
@media (max-width: 768px) {
|
||
.col-md-6, .col-md-4 { margin-bottom: 1rem; }
|
||
.btn-group { display: flex; flex-direction: column; gap: .5rem; }
|
||
}
|
||
</style>
|
||
{% endblock %} |