Marwan Alwali b9b8c69129 update
2025-08-31 10:47:23 +03:00

631 lines
23 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}{% if form.instance.pk %}Edit{% else %}Create{% endif %} OR Block{% endblock %}
{% block css %}
<style>
.form-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.form-section {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
margin-bottom: 1.5rem;
}
.section-header {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 1rem 1.5rem;
font-weight: 600;
color: #495057;
}
.section-content {
padding: 1.5rem;
}
.time-picker-group {
display: flex;
align-items: center;
gap: 1rem;
}
.duration-display {
background: #e9ecef;
border-radius: 0.25rem;
padding: 0.5rem 1rem;
font-weight: 600;
color: #495057;
}
.team-selection {
border: 1px solid #dee2e6;
border-radius: 0.25rem;
padding: 1rem;
margin-bottom: 1rem;
}
.selected-team-member {
display: inline-block;
background: #007bff;
color: white;
padding: 0.25rem 0.75rem;
border-radius: 1rem;
margin: 0.25rem;
font-size: 0.875rem;
}
.preview-section {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1.5rem;
margin-top: 2rem;
}
.conflict-warning {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 0.25rem;
padding: 1rem;
margin-bottom: 1rem;
}
.availability-indicator {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 0.5rem;
}
.available { background-color: #28a745; }
.busy { background-color: #dc3545; }
.partial { background-color: #ffc107; }
@media (max-width: 768px) {
.form-header {
padding: 1.5rem;
}
.time-picker-group {
flex-direction: column;
align-items: stretch;
}
.section-content {
padding: 1rem;
}
}
</style>
{% endblock %}
{% block content %}
<div id="content" class="app-content">
<!-- Page Header -->
<div class="d-flex align-items-center mb-3">
<div>
<ol class="breadcrumb">
<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:or_block_list' %}">Block Schedule</a></li>
<li class="breadcrumb-item active">{% if form.instance.pk %}Edit{% else %}Create{% endif %} Block</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-calendar-plus me-2"></i>{% if form.instance.pk %}Edit{% else %}Create{% endif %} OR Block
</h1>
</div>
<div class="ms-auto">
<a href="{% url 'operating_theatre:or_block_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>Back to Schedule
</a>
</div>
</div>
<!-- Form Header -->
<div class="form-header">
<div class="row">
<div class="col-md-8">
<h2 class="mb-3">{% if form.instance.pk %}Edit OR Block{% else %}Create New OR Block{% endif %}</h2>
<p class="mb-0">
{% if form.instance.pk %}
Modify the details of this operating room block. Changes will affect all scheduled cases.
{% else %}
Create a new operating room block to schedule surgical cases and allocate resources.
{% endif %}
</p>
</div>
<div class="col-md-4 text-end">
<div class="mb-2">
<i class="fas fa-info-circle me-2"></i>
<span>Block scheduling helps optimize OR utilization</span>
</div>
</div>
</div>
</div>
<form method="post" id="blockForm">
{% csrf_token %}
<!-- Basic Information -->
<div class="form-section">
<div class="section-header">
<i class="fas fa-info-circle me-2"></i>Basic Information
</div>
<div class="section-content">
<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label required">Operating Room</label>
{{ form.operating_room }}
{% if form.operating_room.errors %}
<div class="text-danger small">{{ form.operating_room.errors.0 }}</div>
{% endif %}
<div class="form-text">Select the operating room for this block</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label required">Block Type</label>
{{ form.block_type }}
{% if form.block_type.errors %}
<div class="text-danger small">{{ form.block_type.errors.0 }}</div>
{% endif %}
<div class="form-text">Type of surgical block</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group mb-3">
<label class="form-label required">Date</label>
{{ form.date }}
{% if form.date.errors %}
<div class="text-danger small">{{ form.date.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-8">
<div class="form-group mb-3">
<label class="form-label required">Time Schedule</label>
<div class="time-picker-group">
<div class="flex-fill">
<label class="form-label small">Start Time</label>
{{ form.start_time }}
{% if form.start_time.errors %}
<div class="text-danger small">{{ form.start_time.errors.0 }}</div>
{% endif %}
</div>
<div class="align-self-end pb-2">
<i class="fas fa-arrow-right text-muted"></i>
</div>
<div class="flex-fill">
<label class="form-label small">End Time</label>
{{ form.end_time }}
{% if form.end_time.errors %}
<div class="text-danger small">{{ form.end_time.errors.0 }}</div>
{% endif %}
</div>
<div class="align-self-end pb-2">
<div class="duration-display" id="durationDisplay">
<i class="fas fa-clock me-1"></i>
<span id="durationText">0 hours</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Team Assignment -->
<div class="form-section">
<div class="section-header">
<i class="fas fa-users me-2"></i>Team Assignment
</div>
<div class="section-content">
<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Primary Surgeon</label>
{{ form.assigned_surgeon }}
{% if form.assigned_surgeon.errors %}
<div class="text-danger small">{{ form.assigned_surgeon.errors.0 }}</div>
{% endif %}
<div class="form-text">Lead surgeon for this block</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Anesthesiologist</label>
{{ form.assigned_anesthesiologist }}
{% if form.assigned_anesthesiologist.errors %}
<div class="text-danger small">{{ form.assigned_anesthesiologist.errors.0 }}</div>
{% endif %}
<div class="form-text">Assigned anesthesiologist</div>
</div>
</div>
</div>
<div class="form-group mb-3">
<label class="form-label">Nursing Team</label>
{{ form.assigned_nurses }}
{% if form.assigned_nurses.errors %}
<div class="text-danger small">{{ form.assigned_nurses.errors.0 }}</div>
{% endif %}
<div class="form-text">Select nursing staff for this block</div>
</div>
</div>
</div>
<!-- Block Configuration -->
<div class="form-section">
<div class="section-header">
<i class="fas fa-cogs me-2"></i>Block Configuration
</div>
<div class="section-content">
<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Priority Level</label>
{{ form.priority }}
{% if form.priority.errors %}
<div class="text-danger small">{{ form.priority.errors.0 }}</div>
{% endif %}
<div class="form-text">Block priority for scheduling conflicts</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Maximum Cases</label>
{{ form.max_cases }}
{% if form.max_cases.errors %}
<div class="text-danger small">{{ form.max_cases.errors.0 }}</div>
{% endif %}
<div class="form-text">Maximum number of cases for this block</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Turnover Time (minutes)</label>
{{ form.turnover_time }}
{% if form.turnover_time.errors %}
<div class="text-danger small">{{ form.turnover_time.errors.0 }}</div>
{% endif %}
<div class="form-text">Time between cases for room preparation</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-3">
<div class="form-check mt-4">
{{ form.allow_emergency_cases }}
<label class="form-check-label" for="{{ form.allow_emergency_cases.id_for_label }}">
Allow Emergency Cases
</label>
</div>
<div class="form-text">Allow emergency cases to be scheduled in this block</div>
</div>
</div>
</div>
</div>
</div>
<!-- Notes and Instructions -->
<div class="form-section">
<div class="section-header">
<i class="fas fa-sticky-note me-2"></i>Notes and Instructions
</div>
<div class="section-content">
<div class="form-group mb-3">
<label class="form-label">Block Notes</label>
{{ form.notes }}
{% if form.notes.errors %}
<div class="text-danger small">{{ form.notes.errors.0 }}</div>
{% endif %}
<div class="form-text">Special instructions or notes for this block</div>
</div>
<div class="form-group mb-3">
<label class="form-label">Setup Requirements</label>
{{ form.setup_requirements }}
{% if form.setup_requirements.errors %}
<div class="text-danger small">{{ form.setup_requirements.errors.0 }}</div>
{% endif %}
<div class="form-text">Special equipment or setup requirements</div>
</div>
</div>
</div>
<!-- Availability Check -->
<div id="availabilityCheck" class="form-section" style="display: none;">
<div class="section-header">
<i class="fas fa-calendar-check me-2"></i>Availability Check
</div>
<div class="section-content">
<div id="availabilityResults">
<!-- Availability results will be loaded here -->
</div>
</div>
</div>
<!-- Conflicts Warning -->
<div id="conflictsWarning" class="conflict-warning" style="display: none;">
<h6 class="mb-2">
<i class="fas fa-exclamation-triangle me-2"></i>Scheduling Conflicts Detected
</h6>
<div id="conflictsList">
<!-- Conflicts will be listed here -->
</div>
</div>
<!-- Block Preview -->
<div class="preview-section">
<h5 class="mb-3">
<i class="fas fa-eye me-2"></i>Block Preview
</h5>
<div class="row">
<div class="col-md-6">
<div class="mb-2">
<strong>Operating Room:</strong> <span id="previewRoom">Not selected</span>
</div>
<div class="mb-2">
<strong>Date & Time:</strong> <span id="previewDateTime">Not set</span>
</div>
<div class="mb-2">
<strong>Duration:</strong> <span id="previewDuration">0 hours</span>
</div>
</div>
<div class="col-md-6">
<div class="mb-2">
<strong>Block Type:</strong> <span id="previewType">Not selected</span>
</div>
<div class="mb-2">
<strong>Primary Surgeon:</strong> <span id="previewSurgeon">Not assigned</span>
</div>
<div class="mb-2">
<strong>Max Cases:</strong> <span id="previewMaxCases">Not set</span>
</div>
</div>
</div>
</div>
<!-- Form Actions -->
<div class="d-flex justify-content-between mt-4">
<div>
<a href="{% url 'operating_theatre:or_block_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-times me-1"></i>Cancel
</a>
</div>
<div>
<button type="button" class="btn btn-outline-primary me-2" onclick="checkAvailability()">
<i class="fas fa-search me-1"></i>Check Availability
</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save me-1"></i>
{% if form.instance.pk %}Update Block{% else %}Create Block{% endif %}
</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block js %}
<script>
$(document).ready(function() {
// Initialize form
updatePreview();
calculateDuration();
// Form change handlers
$('#blockForm input, #blockForm select, #blockForm textarea').on('change', function() {
updatePreview();
if ($(this).attr('name') === 'start_time' || $(this).attr('name') === 'end_time') {
calculateDuration();
}
});
// Real-time availability checking
$('#id_operating_room, #id_date, #id_start_time, #id_end_time').on('change', function() {
if (allFieldsFilled()) {
setTimeout(checkAvailability, 500);
}
});
});
function calculateDuration() {
const startTime = $('#id_start_time').val();
const endTime = $('#id_end_time').val();
if (startTime && endTime) {
const start = new Date('2000-01-01 ' + startTime);
const end = new Date('2000-01-01 ' + endTime);
if (end > start) {
const diffMs = end - start;
const diffHours = diffMs / (1000 * 60 * 60);
const hours = Math.floor(diffHours);
const minutes = Math.round((diffHours - hours) * 60);
let durationText = '';
if (hours > 0) {
durationText += hours + ' hour' + (hours !== 1 ? 's' : '');
}
if (minutes > 0) {
if (hours > 0) durationText += ' ';
durationText += minutes + ' min';
}
$('#durationText').text(durationText || '0 hours');
$('#previewDuration').text(durationText || '0 hours');
} else {
$('#durationText').text('Invalid time range');
$('#previewDuration').text('Invalid time range');
}
} else {
$('#durationText').text('0 hours');
$('#previewDuration').text('0 hours');
}
}
function updatePreview() {
// Update room
const roomSelect = $('#id_operating_room');
const roomText = roomSelect.find('option:selected').text();
$('#previewRoom').text(roomText !== '---------' ? roomText : 'Not selected');
// Update date and time
const date = $('#id_date').val();
const startTime = $('#id_start_time').val();
const endTime = $('#id_end_time').val();
if (date && startTime && endTime) {
const dateObj = new Date(date);
const dateStr = dateObj.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
$('#previewDateTime').text(`${dateStr}, ${startTime} - ${endTime}`);
} else {
$('#previewDateTime').text('Not set');
}
// Update block type
const typeSelect = $('#id_block_type');
const typeText = typeSelect.find('option:selected').text();
$('#previewType').text(typeText !== '---------' ? typeText : 'Not selected');
// Update surgeon
const surgeonSelect = $('#id_assigned_surgeon');
const surgeonText = surgeonSelect.find('option:selected').text();
$('#previewSurgeon').text(surgeonText !== '---------' ? surgeonText : 'Not assigned');
// Update max cases
const maxCases = $('#id_max_cases').val();
$('#previewMaxCases').text(maxCases || 'Not set');
}
function allFieldsFilled() {
return $('#id_operating_room').val() &&
$('#id_date').val() &&
$('#id_start_time').val() &&
$('#id_end_time').val();
}
function checkAvailability() {
if (!allFieldsFilled()) {
alert('Please fill in operating room, date, start time, and end time first.');
return;
}
const formData = {
'operating_room': $('#id_operating_room').val(),
'date': $('#id_date').val(),
'start_time': $('#id_start_time').val(),
'end_time': $('#id_end_time').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}'
};
$.ajax({
url: '{% url "operating_theatre:check_availability" %}',
method: 'POST',
data: formData,
success: function(response) {
displayAvailabilityResults(response);
},
error: function() {
alert('Error checking availability');
}
});
}
function displayAvailabilityResults(data) {
const availabilitySection = $('#availabilityCheck');
const resultsDiv = $('#availabilityResults');
const conflictsWarning = $('#conflictsWarning');
const conflictsList = $('#conflictsList');
// Show availability section
availabilitySection.show();
// Clear previous results
resultsDiv.empty();
conflictsList.empty();
// Room availability
const roomStatus = data.room_available ? 'available' : 'busy';
const roomIndicator = `<span class="availability-indicator ${roomStatus}"></span>`;
resultsDiv.append(`
<div class="mb-3">
<h6>Operating Room Availability</h6>
<div>${roomIndicator} ${data.room_available ? 'Available' : 'Not Available'}</div>
</div>
`);
// Team availability
if (data.team_availability) {
resultsDiv.append('<div class="mb-3"><h6>Team Availability</h6></div>');
Object.keys(data.team_availability).forEach(function(member) {
const availability = data.team_availability[member];
const status = availability.available ? 'available' : 'busy';
const indicator = `<span class="availability-indicator ${status}"></span>`;
resultsDiv.append(`
<div class="mb-1">
${indicator} ${member}: ${availability.available ? 'Available' : availability.reason}
</div>
`);
});
}
// Show conflicts if any
if (data.conflicts && data.conflicts.length > 0) {
conflictsWarning.show();
data.conflicts.forEach(function(conflict) {
conflictsList.append(`
<div class="mb-1">
<i class="fas fa-exclamation-circle me-1"></i>
${conflict.message}
</div>
`);
});
} else {
conflictsWarning.hide();
}
}
// Form validation
$('#blockForm').on('submit', function(e) {
const startTime = $('#id_start_time').val();
const endTime = $('#id_end_time').val();
if (startTime && endTime) {
const start = new Date('2000-01-01 ' + startTime);
const end = new Date('2000-01-01 ' + endTime);
if (end <= start) {
e.preventDefault();
alert('End time must be after start time.');
return false;
}
}
});
</script>
{% endblock %}