This commit is contained in:
Marwan Alwali 2025-08-30 19:32:46 +03:00
parent d1cee9194e
commit 0422966e14
34 changed files with 18964 additions and 278 deletions

BIN
.DS_Store vendored

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,37 @@ class OperatingRoom(models.Model):
"""
Operating room model for OR configuration and management.
"""
ROOM_TYPE_CHOICES = [
('GENERAL', 'General Surgery'),
('CARDIAC', 'Cardiac Surgery'),
('NEURO', 'Neurosurgery'),
('ORTHOPEDIC', 'Orthopedic Surgery'),
('TRAUMA', 'Trauma Surgery'),
('PEDIATRIC', 'Pediatric Surgery'),
('OBSTETRIC', 'Obstetric Surgery'),
('OPHTHALMOLOGY', 'Ophthalmology'),
('ENT', 'ENT Surgery'),
('UROLOGY', 'Urology'),
('PLASTIC', 'Plastic Surgery'),
('VASCULAR', 'Vascular Surgery'),
('THORACIC', 'Thoracic Surgery'),
('TRANSPLANT', 'Transplant Surgery'),
('ROBOTIC', 'Robotic Surgery'),
('HYBRID', 'Hybrid OR'),
('AMBULATORY', 'Ambulatory Surgery'),
('EMERGENCY', 'Emergency Surgery'),
]
STATUS_CHOICES = [
('AVAILABLE', 'Available'),
('OCCUPIED', 'Occupied'),
('CLEANING', 'Cleaning'),
('MAINTENANCE', 'Maintenance'),
('SETUP', 'Setup'),
('TURNOVER', 'Turnover'),
('OUT_OF_ORDER', 'Out of Order'),
('CLOSED', 'Closed'),
]
# Tenant relationship
tenant = models.ForeignKey(
'core.Tenant',
@ -45,42 +75,14 @@ class OperatingRoom(models.Model):
# Room Type and Capabilities
room_type = models.CharField(
max_length=30,
choices=[
('GENERAL', 'General Surgery'),
('CARDIAC', 'Cardiac Surgery'),
('NEURO', 'Neurosurgery'),
('ORTHOPEDIC', 'Orthopedic Surgery'),
('TRAUMA', 'Trauma Surgery'),
('PEDIATRIC', 'Pediatric Surgery'),
('OBSTETRIC', 'Obstetric Surgery'),
('OPHTHALMOLOGY', 'Ophthalmology'),
('ENT', 'ENT Surgery'),
('UROLOGY', 'Urology'),
('PLASTIC', 'Plastic Surgery'),
('VASCULAR', 'Vascular Surgery'),
('THORACIC', 'Thoracic Surgery'),
('TRANSPLANT', 'Transplant Surgery'),
('ROBOTIC', 'Robotic Surgery'),
('HYBRID', 'Hybrid OR'),
('AMBULATORY', 'Ambulatory Surgery'),
('EMERGENCY', 'Emergency Surgery'),
],
choices=ROOM_TYPE_CHOICES,
help_text='Operating room type'
)
# Room Status
status = models.CharField(
max_length=20,
choices=[
('AVAILABLE', 'Available'),
('OCCUPIED', 'Occupied'),
('CLEANING', 'Cleaning'),
('MAINTENANCE', 'Maintenance'),
('SETUP', 'Setup'),
('TURNOVER', 'Turnover'),
('OUT_OF_ORDER', 'Out of Order'),
('CLOSED', 'Closed'),
],
choices=STATUS_CHOICES,
default='AVAILABLE',
help_text='Current room status'
)
@ -275,7 +277,38 @@ class ORBlock(models.Model):
"""
OR block model for surgical scheduling and time management.
"""
BLOCK_TYPE_CHOICES = [
('SCHEDULED', 'Scheduled Block'),
('EMERGENCY', 'Emergency Block'),
('MAINTENANCE', 'Maintenance Block'),
('CLEANING', 'Deep Cleaning'),
('RESERVED', 'Reserved'),
('BLOCKED', 'Blocked'),
]
SERVICE_CHOICES = [
('GENERAL', 'General Surgery'),
('CARDIAC', 'Cardiac Surgery'),
('NEURO', 'Neurosurgery'),
('ORTHOPEDIC', 'Orthopedic Surgery'),
('TRAUMA', 'Trauma Surgery'),
('PEDIATRIC', 'Pediatric Surgery'),
('OBSTETRIC', 'Obstetric Surgery'),
('OPHTHALMOLOGY', 'Ophthalmology'),
('ENT', 'ENT Surgery'),
('UROLOGY', 'Urology'),
('PLASTIC', 'Plastic Surgery'),
('VASCULAR', 'Vascular Surgery'),
('THORACIC', 'Thoracic Surgery'),
('TRANSPLANT', 'Transplant Surgery'),
]
STATUS_CHOICES = [
('SCHEDULED', 'Scheduled'),
('ACTIVE', 'Active'),
('COMPLETED', 'Completed'),
('CANCELLED', 'Cancelled'),
('DELAYED', 'Delayed'),
]
# Operating Room relationship
operating_room = models.ForeignKey(
OperatingRoom,
@ -306,14 +339,7 @@ class ORBlock(models.Model):
# Block Type
block_type = models.CharField(
max_length=20,
choices=[
('SCHEDULED', 'Scheduled Block'),
('EMERGENCY', 'Emergency Block'),
('MAINTENANCE', 'Maintenance Block'),
('CLEANING', 'Deep Cleaning'),
('RESERVED', 'Reserved'),
('BLOCKED', 'Blocked'),
],
choices=BLOCK_TYPE_CHOICES,
default='SCHEDULED',
help_text='Block type'
)
@ -335,35 +361,14 @@ class ORBlock(models.Model):
# Service Assignment
service = models.CharField(
max_length=30,
choices=[
('GENERAL', 'General Surgery'),
('CARDIAC', 'Cardiac Surgery'),
('NEURO', 'Neurosurgery'),
('ORTHOPEDIC', 'Orthopedic Surgery'),
('TRAUMA', 'Trauma Surgery'),
('PEDIATRIC', 'Pediatric Surgery'),
('OBSTETRIC', 'Obstetric Surgery'),
('OPHTHALMOLOGY', 'Ophthalmology'),
('ENT', 'ENT Surgery'),
('UROLOGY', 'Urology'),
('PLASTIC', 'Plastic Surgery'),
('VASCULAR', 'Vascular Surgery'),
('THORACIC', 'Thoracic Surgery'),
('TRANSPLANT', 'Transplant Surgery'),
],
choices=SERVICE_CHOICES,
help_text='Surgical service'
)
# Block Status
status = models.CharField(
max_length=20,
choices=[
('SCHEDULED', 'Scheduled'),
('ACTIVE', 'Active'),
('COMPLETED', 'Completed'),
('CANCELLED', 'Cancelled'),
('DELAYED', 'Delayed'),
],
choices=STATUS_CHOICES,
default='SCHEDULED',
help_text='Block status'
)
@ -457,7 +462,49 @@ class SurgicalCase(models.Model):
"""
Surgical case model for individual surgical procedures.
"""
CASE_TYPE_CHOICES = [
('ELECTIVE', 'Elective'),
('URGENT', 'Urgent'),
('EMERGENCY', 'Emergency'),
('TRAUMA', 'Trauma'),
('TRANSPLANT', 'Transplant'),
]
APPROACH_CHOICES = [
('OPEN', 'Open'),
('LAPAROSCOPIC', 'Laparoscopic'),
('ROBOTIC', 'Robotic'),
('ENDOSCOPIC', 'Endoscopic'),
('PERCUTANEOUS', 'Percutaneous'),
('HYBRID', 'Hybrid'),
]
ANESTHESIA_TYPE_CHOICES = [
('GENERAL', 'General'),
('REGIONAL', 'Regional'),
('LOCAL', 'Local'),
('SEDATION', 'Sedation'),
('SPINAL', 'Spinal'),
('EPIDURAL', 'Epidural'),
('COMBINED', 'Combined'),
]
STATUS_CHOICES = [
('SCHEDULED', 'Scheduled'),
('DELAYED', 'Delayed'),
('IN_PROGRESS', 'In Progress'),
('COMPLETED', 'Completed'),
('CANCELLED', 'Cancelled'),
('POSTPONED', 'Postponed'),
]
PATIENT_POSITION_CHOICES = [
('SUPINE', 'Supine'),
('PRONE', 'Prone'),
('LATERAL', 'Lateral'),
('LITHOTOMY', 'Lithotomy'),
('TRENDELENBURG', 'Trendelenburg'),
('REVERSE_TREND', 'Reverse Trendelenburg'),
('SITTING', 'Sitting'),
('JACKKNIFE', 'Jackknife'),
]
# OR Block relationship
or_block = models.ForeignKey(
ORBlock,
@ -542,13 +589,7 @@ class SurgicalCase(models.Model):
# Case Classification
case_type = models.CharField(
max_length=20,
choices=[
('ELECTIVE', 'Elective'),
('URGENT', 'Urgent'),
('EMERGENCY', 'Emergency'),
('TRAUMA', 'Trauma'),
('TRANSPLANT', 'Transplant'),
],
choices=CASE_TYPE_CHOICES,
default='ELECTIVE',
help_text='Case type'
)
@ -556,29 +597,14 @@ class SurgicalCase(models.Model):
# Surgical Approach
approach = models.CharField(
max_length=20,
choices=[
('OPEN', 'Open'),
('LAPAROSCOPIC', 'Laparoscopic'),
('ROBOTIC', 'Robotic'),
('ENDOSCOPIC', 'Endoscopic'),
('PERCUTANEOUS', 'Percutaneous'),
('HYBRID', 'Hybrid'),
],
choices=APPROACH_CHOICES,
help_text='Surgical approach'
)
# Anesthesia
anesthesia_type = models.CharField(
max_length=20,
choices=[
('GENERAL', 'General'),
('REGIONAL', 'Regional'),
('LOCAL', 'Local'),
('SEDATION', 'Sedation'),
('SPINAL', 'Spinal'),
('EPIDURAL', 'Epidural'),
('COMBINED', 'Combined'),
],
choices=ANESTHESIA_TYPE_CHOICES,
help_text='Anesthesia type'
)
@ -603,14 +629,7 @@ class SurgicalCase(models.Model):
# Case Status
status = models.CharField(
max_length=20,
choices=[
('SCHEDULED', 'Scheduled'),
('DELAYED', 'Delayed'),
('IN_PROGRESS', 'In Progress'),
('COMPLETED', 'Completed'),
('CANCELLED', 'Cancelled'),
('POSTPONED', 'Postponed'),
],
choices=STATUS_CHOICES,
default='SCHEDULED',
help_text='Case status'
)
@ -647,16 +666,7 @@ class SurgicalCase(models.Model):
# Patient Positioning
patient_position = models.CharField(
max_length=20,
choices=[
('SUPINE', 'Supine'),
('PRONE', 'Prone'),
('LATERAL', 'Lateral'),
('LITHOTOMY', 'Lithotomy'),
('TRENDELENBURG', 'Trendelenburg'),
('REVERSE_TREND', 'Reverse Trendelenburg'),
('SITTING', 'Sitting'),
('JACKKNIFE', 'Jackknife'),
],
choices=PATIENT_POSITION_CHOICES,
blank=True,
null=True,
help_text='Patient positioning'
@ -777,7 +787,27 @@ class SurgicalNote(models.Model):
"""
Surgical note model for perioperative documentation.
"""
CONDITION_CHOICES = [
('STABLE', 'Stable'),
('CRITICAL', 'Critical'),
('GUARDED', 'Guarded'),
('FAIR', 'Fair'),
('GOOD', 'Good'),
('EXCELLENT', 'Excellent'),
]
DISPOSITION_CHOICES = [
('RECOVERY', 'Recovery Room'),
('ICU', 'Intensive Care Unit'),
('WARD', 'Ward'),
('DISCHARGE', 'Discharge'),
('MORGUE', 'Morgue'),
]
STATUS_CHOICES = [
('DRAFT', 'Draft'),
('COMPLETED', 'Completed'),
('SIGNED', 'Signed'),
('AMENDED', 'Amended'),
]
# Surgical Case relationship
surgical_case = models.OneToOneField(
SurgicalCase,
@ -833,25 +863,12 @@ class SurgicalNote(models.Model):
)
condition = models.CharField(
max_length=20,
choices=[
('STABLE', 'Stable'),
('CRITICAL', 'Critical'),
('GUARDED', 'Guarded'),
('FAIR', 'Fair'),
('GOOD', 'Good'),
('EXCELLENT', 'Excellent'),
],
choices=CONDITION_CHOICES,
help_text='Patient condition post-surgery'
)
disposition = models.CharField(
max_length=30,
choices=[
('RECOVERY', 'Recovery Room'),
('ICU', 'Intensive Care Unit'),
('WARD', 'Ward'),
('DISCHARGE', 'Discharge'),
('MORGUE', 'Morgue'),
],
choices=DISPOSITION_CHOICES,
help_text='Patient disposition'
)
@ -915,12 +932,7 @@ class SurgicalNote(models.Model):
# Note Status
status = models.CharField(
max_length=20,
choices=[
('DRAFT', 'Draft'),
('COMPLETED', 'Completed'),
('SIGNED', 'Signed'),
('AMENDED', 'Amended'),
],
choices=STATUS_CHOICES,
default='DRAFT',
help_text='Note status'
)
@ -980,7 +992,30 @@ class EquipmentUsage(models.Model):
"""
Equipment usage model for tracking surgical equipment.
"""
EQUIPMENT_TYPE_CHOICES = [
('SURGICAL_INSTRUMENT', 'Surgical Instrument'),
('MONITORING_DEVICE', 'Monitoring Device'),
('ANESTHESIA_MACHINE', 'Anesthesia Machine'),
('VENTILATOR', 'Ventilator'),
('ELECTROCAUTERY', 'Electrocautery'),
('LASER', 'Laser'),
('MICROSCOPE', 'Microscope'),
('C_ARM', 'C-Arm'),
('ULTRASOUND', 'Ultrasound'),
('ROBOT', 'Surgical Robot'),
('IMPLANT', 'Implant'),
('DISPOSABLE', 'Disposable'),
('OTHER', 'Other'),
]
UNIT_OF_MEASURE_CHOICES = [
('EACH', 'Each'),
('SET', 'Set'),
('PACK', 'Pack'),
('BOX', 'Box'),
('UNIT', 'Unit'),
('PIECE', 'Piece'),
]
# Surgical Case relationship
surgical_case = models.ForeignKey(
SurgicalCase,
@ -1002,21 +1037,7 @@ class EquipmentUsage(models.Model):
)
equipment_type = models.CharField(
max_length=50,
choices=[
('SURGICAL_INSTRUMENT', 'Surgical Instrument'),
('MONITORING_DEVICE', 'Monitoring Device'),
('ANESTHESIA_MACHINE', 'Anesthesia Machine'),
('VENTILATOR', 'Ventilator'),
('ELECTROCAUTERY', 'Electrocautery'),
('LASER', 'Laser'),
('MICROSCOPE', 'Microscope'),
('C_ARM', 'C-Arm'),
('ULTRASOUND', 'Ultrasound'),
('ROBOT', 'Surgical Robot'),
('IMPLANT', 'Implant'),
('DISPOSABLE', 'Disposable'),
('OTHER', 'Other'),
],
choices=EQUIPMENT_TYPE_CHOICES,
help_text='Equipment type'
)
@ -1047,14 +1068,7 @@ class EquipmentUsage(models.Model):
)
unit_of_measure = models.CharField(
max_length=20,
choices=[
('EACH', 'Each'),
('SET', 'Set'),
('PACK', 'Pack'),
('BOX', 'Box'),
('UNIT', 'Unit'),
('PIECE', 'Piece'),
],
choices=UNIT_OF_MEASURE_CHOICES,
default='EACH',
help_text='Unit of measure'
)
@ -1172,7 +1186,24 @@ class SurgicalNoteTemplate(models.Model):
"""
Surgical note template model for standardized documentation.
"""
SPECIALTY_CHOICES = [
('ALL', 'All Specialties'),
('GENERAL', 'General Surgery'),
('CARDIAC', 'Cardiac Surgery'),
('NEURO', 'Neurosurgery'),
('ORTHOPEDIC', 'Orthopedic Surgery'),
('TRAUMA', 'Trauma Surgery'),
('PEDIATRIC', 'Pediatric Surgery'),
('OBSTETRIC', 'Obstetric Surgery'),
('OPHTHALMOLOGY', 'Ophthalmology'),
('ENT', 'ENT Surgery'),
('UROLOGY', 'Urology'),
('PLASTIC', 'Plastic Surgery'),
('VASCULAR', 'Vascular Surgery'),
('THORACIC', 'Thoracic Surgery'),
('TRANSPLANT', 'Transplant Surgery'),
]
# Tenant relationship
tenant = models.ForeignKey(
'core.Tenant',
@ -1207,23 +1238,7 @@ class SurgicalNoteTemplate(models.Model):
)
specialty = models.CharField(
max_length=30,
choices=[
('ALL', 'All Specialties'),
('GENERAL', 'General Surgery'),
('CARDIAC', 'Cardiac Surgery'),
('NEURO', 'Neurosurgery'),
('ORTHOPEDIC', 'Orthopedic Surgery'),
('TRAUMA', 'Trauma Surgery'),
('PEDIATRIC', 'Pediatric Surgery'),
('OBSTETRIC', 'Obstetric Surgery'),
('OPHTHALMOLOGY', 'Ophthalmology'),
('ENT', 'ENT Surgery'),
('UROLOGY', 'Urology'),
('PLASTIC', 'Plastic Surgery'),
('VASCULAR', 'Vascular Surgery'),
('THORACIC', 'Thoracic Surgery'),
('TRANSPLANT', 'Transplant Surgery'),
],
choices=SPECIALTY_CHOICES,
default='ALL',
help_text='Applicable specialty'
)

View File

@ -67,62 +67,62 @@ class OperatingTheatreDashboardView(LoginRequiredMixin, TemplateView):
status='MAINTENANCE'
).count(),
'cases_today': SurgicalCase.objects.filter(
tenant=tenant,
scheduled_start_time__date=today
admission__tenant=tenant,
scheduled_start__date=today
).count(),
'cases_in_progress': SurgicalCase.objects.filter(
tenant=tenant,
admission__tenant=tenant,
status='IN_PROGRESS'
).count(),
'cases_completed_today': SurgicalCase.objects.filter(
tenant=tenant,
actual_end_time__date=today,
admission__tenant=tenant,
actual_end__date=today,
status='COMPLETED'
).count(),
'emergency_cases_today': SurgicalCase.objects.filter(
tenant=tenant,
scheduled_start_time__date=today,
priority='EMERGENCY'
admission__tenant=tenant,
scheduled_start__date=today,
case_type='EMERGENCY'
).count(),
'blocks_today': ORBlock.objects.filter(
tenant=tenant,
operating_room__tenant=tenant,
date=today
).count(),
'equipment_in_use': EquipmentUsage.objects.filter(
tenant=tenant,
status='IN_USE'
surgical_case__admission__tenant=tenant,
# status='IN_USE'
).count(),
'notes_pending': SurgicalNote.objects.filter(
tenant=tenant,
surgical_case__admission__tenant=tenant,
status='DRAFT'
).count(),
})
# Recent surgical cases
context['recent_cases'] = SurgicalCase.objects.filter(
tenant=tenant
admission__tenant=tenant
).select_related(
'patient', 'primary_surgeon', 'operating_room'
).order_by('-scheduled_start_time')[:10]
'patient', 'primary_surgeon', 'or_block__operating_room'
).order_by('-scheduled_start')[:10]
# Today's schedule
context['todays_schedule'] = SurgicalCase.objects.filter(
tenant=tenant,
scheduled_start_time__date=today
admission__tenant=tenant,
scheduled_start__date=today
).select_related(
'patient', 'primary_surgeon', 'operating_room'
).order_by('scheduled_start_time')
'patient', 'primary_surgeon', 'or_block__operating_room'
).order_by('scheduled_start')
# Room utilization
context['room_utilization'] = OperatingRoom.objects.filter(
tenant=tenant,
is_active=True
).annotate(
cases_today=Count(
'surgical_cases',
filter=Q(surgical_cases__scheduled_start_time__date=today)
)
).order_by('room_number')
# context['room_utilization'] = OperatingRoom.objects.filter(
# tenant=tenant,
# is_active=True
# ).annotate(
# cases_today=Count(
# 'or_block__surgical_cases',
# filter=Q(surgical_cases__scheduled_start__date=today)
# )
# ).order_by('room_number')
return context
@ -136,7 +136,7 @@ class OperatingRoomListView(LoginRequiredMixin, ListView):
List all operating rooms with filtering and search.
"""
model = OperatingRoom
template_name = 'operating_theatre/operating_room_list.html'
template_name = 'operating_theatre/rooms/operating_room_list.html'
context_object_name = 'operating_rooms'
paginate_by = 25
@ -189,7 +189,7 @@ class OperatingRoomDetailView(LoginRequiredMixin, DetailView):
Display detailed information about an operating room.
"""
model = OperatingRoom
template_name = 'operating_theatre/operating_room_detail.html'
template_name = 'operating_theatre/rooms/operating_room_detail.html'
context_object_name = 'operating_room'
def get_queryset(self):
@ -241,7 +241,7 @@ class OperatingRoomCreateView(LoginRequiredMixin, PermissionRequiredMixin, Creat
"""
model = OperatingRoom
form_class = OperatingRoomForm
template_name = 'operating_theatre/operating_room_form.html'
template_name = 'operating_theatre/rooms/operating_room_form.html'
permission_required = 'operating_theatre.add_operatingroom'
success_url = reverse_lazy('operating_theatre:operating_room_list')
@ -272,7 +272,7 @@ class OperatingRoomUpdateView(LoginRequiredMixin, PermissionRequiredMixin, Updat
"""
model = OperatingRoom
form_class = OperatingRoomForm
template_name = 'operating_theatre/operating_room_form.html'
template_name = 'operating_theatre/rooms/operating_room_form.html'
permission_required = 'operating_theatre.change_operatingroom'
def get_queryset(self):
@ -305,7 +305,7 @@ class OperatingRoomDeleteView(LoginRequiredMixin, PermissionRequiredMixin, Delet
Delete an operating room (soft delete by deactivating).
"""
model = OperatingRoom
template_name = 'operating_theatre/operating_room_confirm_delete.html'
template_name = 'operating_theatre/rooms/operating_room_confirm_delete.html'
permission_required = 'operating_theatre.delete_operatingroom'
success_url = reverse_lazy('operating_theatre:operating_room_list')
@ -333,7 +333,7 @@ class OperatingRoomDeleteView(LoginRequiredMixin, PermissionRequiredMixin, Delet
self.object.save()
# Log the action
AuditLogger.log_action(
AuditLogger.log_event(
user=request.user,
action='OPERATING_ROOM_DEACTIVATED',
model='OperatingRoom',
@ -431,7 +431,7 @@ class SurgicalNoteTemplateCreateView(LoginRequiredMixin, PermissionRequiredMixin
response = super().form_valid(form)
# Log the action
AuditLogger.log_action(
AuditLogger.log_event(
user=self.request.user,
action='SURGICAL_NOTE_TEMPLATE_CREATED',
model='SurgicalNoteTemplate',
@ -682,7 +682,8 @@ class SurgicalCaseListView(LoginRequiredMixin, ListView):
paginate_by = 25
def get_queryset(self):
queryset = SurgicalCase.objects.filter(tenant=self.request.user.tenant)
tenant = self.request.user.tenant
queryset = SurgicalCase.objects.filter(admission__tenant=tenant)
# Search functionality
search = self.request.GET.get('search')
@ -718,19 +719,19 @@ class SurgicalCaseListView(LoginRequiredMixin, ListView):
date_from = self.request.GET.get('date_from')
date_to = self.request.GET.get('date_to')
if date_from:
queryset = queryset.filter(scheduled_start_time__date__gte=date_from)
queryset = queryset.filter(scheduled_start__date__gte=date_from)
if date_to:
queryset = queryset.filter(scheduled_start_time__date__lte=date_to)
queryset = queryset.filter(scheduled_start__date__lte=date_to)
return queryset.select_related(
'patient', 'primary_surgeon', 'operating_room'
).order_by('-scheduled_start_time')
'patient', 'primary_surgeon', 'or_block__operating_room'
).order_by('-scheduled_start')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'statuses': SurgicalCase._meta.get_field('status').choices,
'priorities': SurgicalCase._meta.get_field('priority').choices,
'statuses': SurgicalCase.STATUS_CHOICES,
'priorities': SurgicalCase.CASE_TYPE_CHOICES,
'operating_rooms': OperatingRoom.objects.filter(
tenant=self.request.user.tenant,
is_active=True
@ -744,7 +745,7 @@ class SurgicalCaseDetailView(LoginRequiredMixin, DetailView):
Display detailed information about a surgical case.
"""
model = SurgicalCase
template_name = 'operating_theatre/surgical_case_detail.html'
template_name = 'operating_theatre/cases/surgical_case_detail.html'
context_object_name = 'surgical_case'
def get_queryset(self):
@ -776,7 +777,7 @@ class SurgicalCaseCreateView(LoginRequiredMixin, PermissionRequiredMixin, Create
"""
model = SurgicalCase
form_class = SurgicalCaseForm
template_name = 'operating_theatre/surgical_case_form.html'
template_name = 'operating_theatre/cases/surgical_case_form.html'
permission_required = 'operating_theatre.add_surgicalcase'
success_url = reverse_lazy('operating_theatre:surgical_case_list')
@ -806,7 +807,7 @@ class SurgicalCaseUpdateView(LoginRequiredMixin, PermissionRequiredMixin, Update
Update surgical case (limited to status and notes after surgery starts).
"""
model = SurgicalCase
template_name = 'operating_theatre/surgical_case_update_form.html'
template_name = 'operating_theatre/cases/surgical_case_form.html'
permission_required = 'operating_theatre.change_surgicalcase'
def get_queryset(self):
@ -830,7 +831,7 @@ class SurgicalCaseUpdateView(LoginRequiredMixin, PermissionRequiredMixin, Update
response = super().form_valid(form)
# Log the action
AuditLogger.log_action(
AuditLogger.log_event(
user=self.request.user,
action='SURGICAL_CASE_UPDATED',
model='SurgicalCase',

BIN
templates/.DS_Store vendored

Binary file not shown.

BIN
templates/operating_theatre/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,523 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Delete OR Block - {{ block.operating_room.name }}{% endblock %}
{% block extra_css %}
<style>
.delete-header {
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.warning-section {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.impact-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;
}
.block-info {
background: #f8f9fa;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.info-item {
display: flex;
flex-direction: column;
}
.info-label {
font-size: 0.875rem;
color: #6c757d;
font-weight: 600;
margin-bottom: 0.25rem;
}
.info-value {
color: #495057;
font-weight: 500;
}
.affected-cases {
border: 1px solid #dee2e6;
border-radius: 0.25rem;
max-height: 300px;
overflow-y: auto;
}
.case-item {
padding: 1rem;
border-bottom: 1px solid #dee2e6;
display: flex;
justify-content-between;
align-items: center;
}
.case-item:last-child {
border-bottom: none;
}
.case-info {
flex-grow: 1;
}
.case-title {
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
}
.case-details {
font-size: 0.875rem;
color: #6c757d;
}
.case-status {
margin-left: 1rem;
}
.alternative-actions {
background: #e7f3ff;
border: 1px solid #b3d9ff;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.action-button {
margin-bottom: 0.5rem;
margin-right: 0.5rem;
}
.confirmation-section {
background: #fff5f5;
border: 2px solid #fed7d7;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.type-to-confirm {
background: #f7fafc;
border: 2px solid #e2e8f0;
border-radius: 0.25rem;
padding: 0.75rem;
font-family: monospace;
font-weight: bold;
text-align: center;
margin-bottom: 1rem;
}
@media (max-width: 768px) {
.delete-header {
padding: 1.5rem;
}
.info-grid {
grid-template-columns: 1fr;
}
.case-item {
flex-direction: column;
align-items: flex-start;
}
.case-status {
margin-left: 0;
margin-top: 0.5rem;
}
}
</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:block_schedule_list' %}">Block Schedule</a></li>
<li class="breadcrumb-item"><a href="{% url 'operating_theatre:block_detail' block.pk %}">{{ block.operating_room.name }}</a></li>
<li class="breadcrumb-item active">Delete Block</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-trash-alt me-2"></i>Delete OR Block
</h1>
</div>
<div class="ms-auto">
<a href="{% url 'operating_theatre:block_detail' block.pk %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>Back to Block
</a>
</div>
</div>
<!-- Delete Header -->
<div class="delete-header">
<div class="row">
<div class="col-md-8">
<h2 class="mb-3">
<i class="fas fa-exclamation-triangle me-2"></i>
Delete Operating Room Block
</h2>
<p class="mb-0">
You are about to permanently delete this OR block. This action cannot be undone and will affect all scheduled cases within this block.
</p>
</div>
<div class="col-md-4 text-end">
<div class="mb-2">
<i class="fas fa-calendar-times fa-3x"></i>
</div>
</div>
</div>
</div>
<!-- Warning Section -->
<div class="warning-section">
<h5 class="mb-3">
<i class="fas fa-exclamation-triangle me-2"></i>
Critical Warning
</h5>
<ul class="mb-0">
<li>This OR block will be permanently deleted</li>
<li>All scheduled cases within this block will be affected</li>
<li>Team assignments and resource allocations will be removed</li>
<li>Historical data and logs will be preserved for audit purposes</li>
<li>Patients and staff will need to be notified of schedule changes</li>
</ul>
</div>
<!-- Block Information -->
<div class="block-info">
<h5 class="mb-3">Block Information</h5>
<div class="info-grid">
<div class="info-item">
<div class="info-label">Operating Room</div>
<div class="info-value">{{ block.operating_room.name }}</div>
</div>
<div class="info-item">
<div class="info-label">Date & Time</div>
<div class="info-value">
{{ block.date|date:"l, M d, Y" }}<br>
{{ block.start_time|time:"g:i A" }} - {{ block.end_time|time:"g:i A" }}
</div>
</div>
<div class="info-item">
<div class="info-label">Block Type</div>
<div class="info-value">{{ block.get_block_type_display }}</div>
</div>
<div class="info-item">
<div class="info-label">Primary Surgeon</div>
<div class="info-value">
{% if block.assigned_surgeon %}
{{ block.assigned_surgeon.get_full_name }}
{% else %}
<span class="text-muted">Not assigned</span>
{% endif %}
</div>
</div>
<div class="info-item">
<div class="info-label">Anesthesiologist</div>
<div class="info-value">
{% if block.assigned_anesthesiologist %}
{{ block.assigned_anesthesiologist.get_full_name }}
{% else %}
<span class="text-muted">Not assigned</span>
{% endif %}
</div>
</div>
<div class="info-item">
<div class="info-label">Status</div>
<div class="info-value">
{% if block.status == 'scheduled' %}
<span class="badge bg-primary">Scheduled</span>
{% elif block.status == 'in_progress' %}
<span class="badge bg-success">In Progress</span>
{% elif block.status == 'completed' %}
<span class="badge bg-secondary">Completed</span>
{% elif block.status == 'cancelled' %}
<span class="badge bg-danger">Cancelled</span>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Impact Analysis -->
<div class="impact-section">
<div class="section-header">
<i class="fas fa-exclamation-circle me-2"></i>Impact Analysis
</div>
<div class="section-content">
<div class="row">
<div class="col-md-4">
<div class="text-center mb-3">
<div class="h3 text-danger">{{ affected_cases.count }}</div>
<div class="text-muted">Scheduled Cases</div>
</div>
</div>
<div class="col-md-4">
<div class="text-center mb-3">
<div class="h3 text-warning">{{ affected_staff.count }}</div>
<div class="text-muted">Affected Staff</div>
</div>
</div>
<div class="col-md-4">
<div class="text-center mb-3">
<div class="h3 text-info">{{ affected_patients.count }}</div>
<div class="text-muted">Affected Patients</div>
</div>
</div>
</div>
</div>
</div>
<!-- Affected Cases -->
{% if affected_cases %}
<div class="impact-section">
<div class="section-header">
<i class="fas fa-procedures me-2"></i>Affected Surgical Cases
</div>
<div class="section-content">
<div class="affected-cases">
{% for case in affected_cases %}
<div class="case-item">
<div class="case-info">
<div class="case-title">{{ case.procedure_name }}</div>
<div class="case-details">
Patient: {{ case.patient.get_full_name }} |
Surgeon: {{ case.surgeon.get_full_name }} |
Time: {{ case.scheduled_start_time|time:"g:i A" }}
</div>
</div>
<div class="case-status">
{% if case.status == 'scheduled' %}
<span class="badge bg-primary">Scheduled</span>
{% elif case.status == 'confirmed' %}
<span class="badge bg-success">Confirmed</span>
{% elif case.status == 'in_progress' %}
<span class="badge bg-info">In Progress</span>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<!-- Alternative Actions -->
<div class="alternative-actions">
<h5 class="mb-3">
<i class="fas fa-lightbulb me-2"></i>
Consider These Alternatives
</h5>
<p class="mb-3">Before deleting this block, consider these alternative actions:</p>
<button class="btn btn-outline-primary action-button" onclick="rescheduleBlock()">
<i class="fas fa-calendar-alt me-1"></i>Reschedule Block
</button>
<button class="btn btn-outline-warning action-button" onclick="postponeBlock()">
<i class="fas fa-pause me-1"></i>Postpone Block
</button>
<button class="btn btn-outline-info action-button" onclick="transferCases()">
<i class="fas fa-exchange-alt me-1"></i>Transfer Cases
</button>
<button class="btn btn-outline-secondary action-button" onclick="notifyStaff()">
<i class="fas fa-bell me-1"></i>Notify Staff First
</button>
</div>
<!-- Confirmation Section -->
<div class="confirmation-section">
<h5 class="mb-3">
<i class="fas fa-shield-alt me-2"></i>
Deletion Confirmation Required
</h5>
<form method="post" id="deleteForm">
{% csrf_token %}
<div class="mb-3">
<label class="form-label">
<strong>Type the following text to confirm deletion:</strong>
</label>
<div class="type-to-confirm">
DELETE {{ block.operating_room.name }} {{ block.date|date:"Y-m-d" }}
</div>
<input type="text" class="form-control" id="confirmationText"
placeholder="Type the confirmation text here..." required>
<div class="form-text">This confirmation helps prevent accidental deletions.</div>
</div>
<div class="mb-3">
<label class="form-label">Reason for Deletion</label>
<select class="form-select" name="deletion_reason" required>
<option value="">Select a reason...</option>
<option value="schedule_conflict">Schedule Conflict</option>
<option value="surgeon_unavailable">Surgeon Unavailable</option>
<option value="equipment_failure">Equipment Failure</option>
<option value="emergency_case">Emergency Case Priority</option>
<option value="patient_cancellation">Patient Cancellation</option>
<option value="administrative">Administrative Decision</option>
<option value="other">Other</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Additional Notes</label>
<textarea class="form-control" name="deletion_notes" rows="3"
placeholder="Provide additional context for this deletion..."></textarea>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="notifyStaffCheck" name="notify_staff" checked>
<label class="form-check-label" for="notifyStaffCheck">
Automatically notify affected staff members
</label>
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="notifyPatientsCheck" name="notify_patients" checked>
<label class="form-check-label" for="notifyPatientsCheck">
Automatically notify affected patients
</label>
</div>
</div>
<div class="d-flex justify-content-between">
<div>
<a href="{% url 'operating_theatre:block_detail' block.pk %}" class="btn btn-outline-secondary">
<i class="fas fa-times me-1"></i>Cancel
</a>
</div>
<div>
<button type="submit" class="btn btn-danger" id="deleteButton" disabled>
<i class="fas fa-trash-alt me-1"></i>
Permanently Delete Block
</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
$(document).ready(function() {
const expectedText = 'DELETE {{ block.operating_room.name }} {{ block.date|date:"Y-m-d" }}';
$('#confirmationText').on('input', function() {
const enteredText = $(this).val().trim();
const deleteButton = $('#deleteButton');
if (enteredText === expectedText) {
deleteButton.prop('disabled', false);
$(this).removeClass('is-invalid').addClass('is-valid');
} else {
deleteButton.prop('disabled', true);
$(this).removeClass('is-valid');
if (enteredText.length > 0) {
$(this).addClass('is-invalid');
}
}
});
});
function rescheduleBlock() {
window.location.href = '{% url "operating_theatre:block_reschedule" block.pk %}';
}
function postponeBlock() {
if (confirm('Postpone this block to a later date?')) {
$.ajax({
url: '{% url "operating_theatre:block_postpone" block.pk %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
alert('Block has been postponed successfully.');
window.location.href = '{% url "operating_theatre:block_schedule_list" %}';
} else {
alert('Error postponing block: ' + response.error);
}
},
error: function() {
alert('Error postponing block');
}
});
}
}
function transferCases() {
window.location.href = '{% url "operating_theatre:block_transfer_cases" block.pk %}';
}
function notifyStaff() {
window.location.href = '{% url "operating_theatre:block_notify_staff" block.pk %}';
}
// Form submission with additional confirmation
$('#deleteForm').on('submit', function(e) {
e.preventDefault();
const affectedCases = {{ affected_cases.count }};
const affectedStaff = {{ affected_staff.count }};
let confirmMessage = 'Are you absolutely sure you want to delete this OR block?\n\n';
confirmMessage += `This will affect ${affectedCases} scheduled case(s) and ${affectedStaff} staff member(s).\n\n`;
confirmMessage += 'This action cannot be undone.';
if (confirm(confirmMessage)) {
// Show loading state
$('#deleteButton').html('<i class="fas fa-spinner fa-spin me-1"></i>Deleting...').prop('disabled', true);
// Submit the form
this.submit();
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,534 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}OR Block - {{ block.operating_room.name }}{% endblock %}
{% block extra_css %}
<style>
.block-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.info-card {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
margin-bottom: 1.5rem;
background: white;
}
.info-header {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 1rem 1.5rem;
font-weight: 600;
color: #495057;
}
.info-content {
padding: 1.5rem;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
margin-bottom: 1rem;
}
.info-item {
display: flex;
flex-direction: column;
}
.info-label {
font-weight: 600;
color: #6c757d;
font-size: 0.875rem;
margin-bottom: 0.25rem;
}
.info-value {
color: #495057;
font-size: 1rem;
}
.utilization-chart {
height: 200px;
margin: 1rem 0;
}
.timeline-item {
display: flex;
align-items: start;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid #dee2e6;
}
.timeline-icon {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
font-size: 1.2rem;
}
.timeline-content {
flex-grow: 1;
}
.case-card {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
margin-bottom: 1rem;
background: white;
}
.case-header {
padding: 1rem 1.5rem;
border-bottom: 1px solid #dee2e6;
display: flex;
justify-content: space-between;
align-items: center;
}
.case-content {
padding: 1.5rem;
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
margin-right: 0.5rem;
}
.status-available { background-color: #28a745; }
.status-booked { background-color: #007bff; }
.status-in-progress { background-color: #ffc107; }
.status-completed { background-color: #6c757d; }
.status-cancelled { background-color: #dc3545; }
@media (max-width: 768px) {
.block-header {
padding: 1.5rem;
}
.info-grid {
grid-template-columns: 1fr;
}
.info-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:block_schedule_list' %}">Block Schedule</a></li>
<li class="breadcrumb-item active">{{ block.operating_room.name }}</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-calendar-alt me-2"></i>OR Block Details
</h1>
</div>
<div class="ms-auto">
<div class="btn-group">
<a href="{% url 'operating_theatre:block_schedule_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>Back
</a>
{% if block.status == 'available' %}
<a href="{% url 'operating_theatre:block_edit' block.pk %}" class="btn btn-primary">
<i class="fas fa-edit me-1"></i>Edit
</a>
{% endif %}
<button class="btn btn-outline-secondary" onclick="window.print()">
<i class="fas fa-print me-1"></i>Print
</button>
</div>
</div>
</div>
<!-- Block Header -->
<div class="block-header">
<div class="row">
<div class="col-md-8">
<h2 class="mb-3">{{ block.operating_room.name }}</h2>
<div class="row">
<div class="col-md-6">
<div class="mb-2">
<strong>Date:</strong> {{ block.date|date:"l, F d, Y" }}
</div>
<div class="mb-2">
<strong>Time:</strong> {{ block.start_time|date:"H:i" }} - {{ block.end_time|date:"H:i" }}
</div>
<div class="mb-2">
<strong>Duration:</strong> {{ block.duration_hours }} hours
</div>
</div>
<div class="col-md-6">
<div class="mb-2">
<strong>Block Type:</strong> {{ block.get_block_type_display }}
</div>
<div class="mb-2">
<strong>Assigned Surgeon:</strong>
{% if block.assigned_surgeon %}
{{ block.assigned_surgeon.get_full_name }}
{% else %}
<span class="text-muted">Unassigned</span>
{% endif %}
</div>
<div class="mb-2">
<strong>Created:</strong> {{ block.created_at|date:"M d, Y H:i" }}
</div>
</div>
</div>
</div>
<div class="col-md-4 text-end">
<div class="mb-3">
<span class="status-indicator status-{{ block.status }}"></span>
{% if block.status == 'available' %}
<span class="badge bg-success fs-6">Available</span>
{% elif block.status == 'booked' %}
<span class="badge bg-primary fs-6">Booked</span>
{% elif block.status == 'in_progress' %}
<span class="badge bg-warning fs-6">In Progress</span>
{% elif block.status == 'completed' %}
<span class="badge bg-secondary fs-6">Completed</span>
{% elif block.status == 'cancelled' %}
<span class="badge bg-danger fs-6">Cancelled</span>
{% endif %}
</div>
<div class="text-center">
<div class="fs-1 fw-bold">{{ block.utilization_percentage }}%</div>
<div>Utilization</div>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Block Information -->
<div class="col-lg-8">
<!-- Utilization Chart -->
<div class="info-card">
<div class="info-header">
<i class="fas fa-chart-bar me-2"></i>Block Utilization
</div>
<div class="info-content">
<div class="utilization-chart">
<canvas id="utilizationChart"></canvas>
</div>
<div class="row text-center">
<div class="col-md-3">
<div class="fw-bold text-primary">{{ block.total_minutes }}</div>
<small class="text-muted">Total Minutes</small>
</div>
<div class="col-md-3">
<div class="fw-bold text-success">{{ block.scheduled_minutes }}</div>
<small class="text-muted">Scheduled</small>
</div>
<div class="col-md-3">
<div class="fw-bold text-warning">{{ block.available_minutes }}</div>
<small class="text-muted">Available</small>
</div>
<div class="col-md-3">
<div class="fw-bold text-info">{{ block.turnover_minutes }}</div>
<small class="text-muted">Turnover</small>
</div>
</div>
</div>
</div>
<!-- Scheduled Cases -->
<div class="info-card">
<div class="info-header">
<i class="fas fa-procedures me-2"></i>Scheduled Cases ({{ block.scheduled_cases.count }})
{% if block.status == 'available' %}
<div class="ms-auto">
<button class="btn btn-sm btn-primary" onclick="addCase()">
<i class="fas fa-plus me-1"></i>Add Case
</button>
</div>
{% endif %}
</div>
<div class="info-content">
{% for case in block.scheduled_cases.all %}
<div class="case-card">
<div class="case-header">
<div>
<h6 class="mb-1">{{ case.patient.get_full_name }}</h6>
<small class="text-muted">{{ case.scheduled_start_time|date:"H:i" }} - {{ case.estimated_end_time|date:"H:i" }}</small>
</div>
<div>
{% if case.status == 'scheduled' %}
<span class="badge bg-primary">Scheduled</span>
{% elif case.status == 'in_progress' %}
<span class="badge bg-warning">In Progress</span>
{% elif case.status == 'completed' %}
<span class="badge bg-success">Completed</span>
{% elif case.status == 'cancelled' %}
<span class="badge bg-danger">Cancelled</span>
{% endif %}
</div>
</div>
<div class="case-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Procedure</div>
<div class="info-value">{{ case.procedure_name }}</div>
</div>
<div class="info-item">
<div class="info-label">Surgeon</div>
<div class="info-value">{{ case.primary_surgeon.get_full_name }}</div>
</div>
<div class="info-item">
<div class="info-label">Estimated Duration</div>
<div class="info-value">{{ case.estimated_duration }} minutes</div>
</div>
<div class="info-item">
<div class="info-label">Priority</div>
<div class="info-value">
{% if case.priority == 'emergency' %}
<span class="badge bg-danger">Emergency</span>
{% elif case.priority == 'urgent' %}
<span class="badge bg-warning">Urgent</span>
{% else %}
<span class="badge bg-secondary">Routine</span>
{% endif %}
</div>
</div>
</div>
<div class="d-flex justify-content-between mt-3">
<div class="btn-group">
<a href="{% url 'operating_theatre:case_detail' case.pk %}" class="btn btn-outline-primary btn-sm">
<i class="fas fa-eye me-1"></i>View
</a>
{% if case.status == 'scheduled' %}
<a href="{% url 'operating_theatre:case_edit' case.pk %}" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-edit me-1"></i>Edit
</a>
{% endif %}
</div>
<div class="btn-group">
{% if case.status == 'scheduled' %}
<button class="btn btn-success btn-sm" onclick="startCase('{{ case.pk }}')">
<i class="fas fa-play me-1"></i>Start
</button>
{% elif case.status == 'in_progress' %}
<button class="btn btn-warning btn-sm" onclick="completeCase('{{ case.pk }}')">
<i class="fas fa-check me-1"></i>Complete
</button>
{% endif %}
</div>
</div>
</div>
</div>
{% empty %}
<div class="text-center py-4">
<i class="fas fa-calendar-plus fa-2x text-muted mb-2"></i>
<p class="text-muted mb-0">No cases scheduled for this block</p>
{% if block.status == 'available' %}
<button class="btn btn-primary mt-2" onclick="addCase()">
<i class="fas fa-plus me-1"></i>Add First Case
</button>
{% endif %}
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<!-- Block Details -->
<div class="info-card">
<div class="info-header">
<i class="fas fa-info-circle me-2"></i>Block Details
</div>
<div class="info-content">
<div class="info-item mb-3">
<div class="info-label">Operating Room</div>
<div class="info-value">{{ block.operating_room.name }}</div>
</div>
<div class="info-item mb-3">
<div class="info-label">Room Type</div>
<div class="info-value">{{ block.operating_room.get_room_type_display }}</div>
</div>
<div class="info-item mb-3">
<div class="info-label">Capacity</div>
<div class="info-value">{{ block.operating_room.capacity }} people</div>
</div>
<div class="info-item mb-3">
<div class="info-label">Equipment Level</div>
<div class="info-value">{{ block.operating_room.get_equipment_level_display }}</div>
</div>
{% if block.notes %}
<div class="info-item mb-3">
<div class="info-label">Notes</div>
<div class="info-value">{{ block.notes }}</div>
</div>
{% endif %}
</div>
</div>
<!-- Team Information -->
{% if block.assigned_surgeon or block.assigned_anesthesiologist or block.assigned_nurses.exists %}
<div class="info-card">
<div class="info-header">
<i class="fas fa-users me-2"></i>Assigned Team
</div>
<div class="info-content">
{% if block.assigned_surgeon %}
<div class="info-item mb-3">
<div class="info-label">Primary Surgeon</div>
<div class="info-value">{{ block.assigned_surgeon.get_full_name }}</div>
</div>
{% endif %}
{% if block.assigned_anesthesiologist %}
<div class="info-item mb-3">
<div class="info-label">Anesthesiologist</div>
<div class="info-value">{{ block.assigned_anesthesiologist.get_full_name }}</div>
</div>
{% endif %}
{% if block.assigned_nurses.exists %}
<div class="info-item mb-3">
<div class="info-label">Nursing Team</div>
<div class="info-value">
{% for nurse in block.assigned_nurses.all %}
<div>{{ nurse.get_full_name }}</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
{% endif %}
<!-- Activity Timeline -->
<div class="info-card">
<div class="info-header">
<i class="fas fa-history me-2"></i>Activity Timeline
</div>
<div class="info-content">
{% for activity in block.activities.all %}
<div class="timeline-item">
<div class="timeline-icon bg-primary text-white">
<i class="fas fa-clock"></i>
</div>
<div class="timeline-content">
<div class="fw-bold">{{ activity.action }}</div>
<div class="text-muted small">{{ activity.created_at|date:"M d, Y H:i" }}</div>
{% if activity.notes %}
<div class="mt-1">{{ activity.notes }}</div>
{% endif %}
</div>
</div>
{% empty %}
<div class="text-center py-3">
<i class="fas fa-history fa-2x text-muted mb-2"></i>
<p class="text-muted mb-0">No activity recorded</p>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// Utilization Chart
const ctx = document.getElementById('utilizationChart').getContext('2d');
const utilizationChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Scheduled', 'Available', 'Turnover'],
datasets: [{
data: [{{ block.scheduled_minutes }}, {{ block.available_minutes }}, {{ block.turnover_minutes }}],
backgroundColor: ['#28a745', '#ffc107', '#17a2b8'],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
function addCase() {
window.location.href = '{% url "operating_theatre:case_create" %}?block={{ block.pk }}';
}
function startCase(caseId) {
if (confirm('Start this surgical case?')) {
$.ajax({
url: `/operating-theatre/cases/${caseId}/start/`,
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert('Error starting case: ' + response.error);
}
},
error: function() {
alert('Error starting case');
}
});
}
}
function completeCase(caseId) {
if (confirm('Mark this surgical case as completed?')) {
$.ajax({
url: `/operating-theatre/cases/${caseId}/complete/`,
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert('Error completing case: ' + response.error);
}
},
error: function() {
alert('Error completing case');
}
});
}
}
</script>
{% endblock %}

View File

@ -0,0 +1,630 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}{% if form.instance.pk %}Edit{% else %}Create{% endif %} OR Block{% endblock %}
{% block extra_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:block_schedule_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:block_schedule_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:block_schedule_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 extra_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 %}

View File

@ -0,0 +1,589 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}OR Block Schedule{% endblock %}
{% block extra_css %}
<style>
.schedule-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.block-card {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
margin-bottom: 1rem;
background: white;
transition: all 0.3s ease;
}
.block-card:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.block-header {
padding: 1rem 1.5rem;
border-bottom: 1px solid #dee2e6;
display: flex;
justify-content: space-between;
align-items: center;
}
.block-content {
padding: 1.5rem;
}
.block-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 1rem;
}
.info-item {
display: flex;
flex-direction: column;
}
.info-label {
font-size: 0.875rem;
color: #6c757d;
font-weight: 600;
margin-bottom: 0.25rem;
}
.info-value {
color: #495057;
font-weight: 500;
}
.utilization-bar {
height: 20px;
background: #e9ecef;
border-radius: 10px;
overflow: hidden;
position: relative;
}
.utilization-fill {
height: 100%;
border-radius: 10px;
transition: width 0.3s ease;
}
.utilization-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 0.75rem;
font-weight: bold;
color: #495057;
}
.case-list {
margin-top: 1rem;
}
.case-item {
display: flex;
align-items: center;
padding: 0.75rem;
margin-bottom: 0.5rem;
background: #f8f9fa;
border-radius: 0.25rem;
border-left: 4px solid #007bff;
}
.case-time {
font-weight: bold;
color: #007bff;
margin-right: 1rem;
min-width: 80px;
}
.case-details {
flex-grow: 1;
}
.case-patient {
font-weight: 600;
margin-bottom: 0.25rem;
}
.case-procedure {
font-size: 0.875rem;
color: #6c757d;
}
.case-status {
margin-left: auto;
}
.filter-section {
background: #f8f9fa;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.calendar-nav {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
margin-bottom: 2rem;
}
.date-picker {
display: flex;
align-items: center;
gap: 0.5rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.stat-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1.5rem;
text-align: center;
}
.stat-number {
font-size: 2rem;
font-weight: bold;
color: #007bff;
margin-bottom: 0.5rem;
}
.stat-label {
color: #6c757d;
font-size: 0.875rem;
}
@media (max-width: 768px) {
.schedule-header {
padding: 1.5rem;
}
.block-info {
grid-template-columns: 1fr;
}
.case-item {
flex-direction: column;
align-items: flex-start;
}
.case-time {
margin-bottom: 0.5rem;
}
.calendar-nav {
flex-direction: column;
}
}
</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 active">Block Schedule</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-calendar-alt me-2"></i>OR Block Schedule
</h1>
</div>
<div class="ms-auto">
<div class="btn-group">
<a href="{% url 'operating_theatre:block_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>New Block
</a>
<button class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
<i class="fas fa-download me-1"></i>Export
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="exportSchedule('pdf')">
<i class="fas fa-file-pdf me-2"></i>PDF
</a></li>
<li><a class="dropdown-item" href="#" onclick="exportSchedule('excel')">
<i class="fas fa-file-excel me-2"></i>Excel
</a></li>
</ul>
</div>
</div>
</div>
<!-- Schedule Header -->
<div class="schedule-header">
<div class="row">
<div class="col-md-8">
<h2 class="mb-3">{{ current_date|date:"l, F d, Y" }}</h2>
<div class="row">
<div class="col-md-4">
<div class="mb-2">
<strong>Total Blocks:</strong> {{ stats.total_blocks }}
</div>
<div class="mb-2">
<strong>Active Blocks:</strong> {{ stats.active_blocks }}
</div>
</div>
<div class="col-md-4">
<div class="mb-2">
<strong>Scheduled Cases:</strong> {{ stats.scheduled_cases }}
</div>
<div class="mb-2">
<strong>Available Slots:</strong> {{ stats.available_slots }}
</div>
</div>
<div class="col-md-4">
<div class="mb-2">
<strong>Overall Utilization:</strong> {{ stats.utilization_percentage }}%
</div>
<div class="mb-2">
<strong>Emergency Slots:</strong> {{ stats.emergency_slots }}
</div>
</div>
</div>
</div>
<div class="col-md-4 text-end">
<div class="calendar-nav">
<a href="?date={{ previous_date|date:'Y-m-d' }}" class="btn btn-outline-light">
<i class="fas fa-chevron-left"></i>
</a>
<div class="date-picker">
<input type="date" class="form-control" id="datePicker"
value="{{ current_date|date:'Y-m-d' }}" onchange="changeDate()">
</div>
<a href="?date={{ next_date|date:'Y-m-d' }}" class="btn btn-outline-light">
<i class="fas fa-chevron-right"></i>
</a>
</div>
</div>
</div>
</div>
<!-- Filters -->
<div class="filter-section">
<form method="get" id="filterForm">
<input type="hidden" name="date" value="{{ current_date|date:'Y-m-d' }}">
<div class="row">
<div class="col-md-3">
<div class="form-group">
<label class="form-label">Operating Room</label>
<select class="form-select" name="room">
<option value="">All Rooms</option>
{% for room in operating_rooms %}
<option value="{{ room.id }}" {% if request.GET.room == room.id|stringformat:"s" %}selected{% endif %}>
{{ room.name }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="form-label">Surgeon</label>
<select class="form-select" name="surgeon">
<option value="">All Surgeons</option>
{% for surgeon in surgeons %}
<option value="{{ surgeon.id }}" {% if request.GET.surgeon == surgeon.id|stringformat:"s" %}selected{% endif %}>
{{ surgeon.get_full_name }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="form-label">Block Type</label>
<select class="form-select" name="block_type">
<option value="">All Types</option>
<option value="regular" {% if request.GET.block_type == 'regular' %}selected{% endif %}>Regular</option>
<option value="emergency" {% if request.GET.block_type == 'emergency' %}selected{% endif %}>Emergency</option>
<option value="maintenance" {% if request.GET.block_type == 'maintenance' %}selected{% endif %}>Maintenance</option>
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="form-label">Status</label>
<select class="form-select" name="status">
<option value="">All Status</option>
<option value="available" {% if request.GET.status == 'available' %}selected{% endif %}>Available</option>
<option value="booked" {% if request.GET.status == 'booked' %}selected{% endif %}>Booked</option>
<option value="in_progress" {% if request.GET.status == 'in_progress' %}selected{% endif %}>In Progress</option>
<option value="completed" {% if request.GET.status == 'completed' %}selected{% endif %}>Completed</option>
</select>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<button type="submit" class="btn btn-primary me-2">
<i class="fas fa-filter me-1"></i>Apply Filters
</button>
<a href="?date={{ current_date|date:'Y-m-d' }}" class="btn btn-outline-secondary">
<i class="fas fa-times me-1"></i>Clear
</a>
</div>
</div>
</form>
</div>
<!-- Block Schedule -->
<div class="row">
{% for block in blocks %}
<div class="col-lg-6 col-xl-4">
<div class="block-card">
<div class="block-header">
<div>
<h5 class="mb-1">{{ block.operating_room.name }}</h5>
<small class="text-muted">{{ block.start_time|date:"H:i" }} - {{ block.end_time|date:"H:i" }}</small>
</div>
<div>
{% if block.status == 'available' %}
<span class="badge bg-success">Available</span>
{% elif block.status == 'booked' %}
<span class="badge bg-primary">Booked</span>
{% elif block.status == 'in_progress' %}
<span class="badge bg-warning">In Progress</span>
{% elif block.status == 'completed' %}
<span class="badge bg-secondary">Completed</span>
{% endif %}
</div>
</div>
<div class="block-content">
<div class="block-info">
<div class="info-item">
<div class="info-label">Assigned Surgeon</div>
<div class="info-value">
{% if block.assigned_surgeon %}
{{ block.assigned_surgeon.get_full_name }}
{% else %}
<span class="text-muted">Unassigned</span>
{% endif %}
</div>
</div>
<div class="info-item">
<div class="info-label">Block Type</div>
<div class="info-value">{{ block.get_block_type_display }}</div>
</div>
<div class="info-item">
<div class="info-label">Duration</div>
<div class="info-value">{{ block.duration_hours }} hours</div>
</div>
<div class="info-item">
<div class="info-label">Utilization</div>
<div class="info-value">
<div class="utilization-bar">
<div class="utilization-fill bg-{% if block.utilization_percentage >= 80 %}success{% elif block.utilization_percentage >= 60 %}warning{% else %}danger{% endif %}"
style="width: {{ block.utilization_percentage }}%"></div>
<div class="utilization-text">{{ block.utilization_percentage }}%</div>
</div>
</div>
</div>
</div>
{% if block.scheduled_cases.exists %}
<div class="case-list">
<h6 class="mb-2">
<i class="fas fa-procedures me-1"></i>Scheduled Cases ({{ block.scheduled_cases.count }})
</h6>
{% for case in block.scheduled_cases.all %}
<div class="case-item">
<div class="case-time">{{ case.scheduled_start_time|date:"H:i" }}</div>
<div class="case-details">
<div class="case-patient">{{ case.patient.get_full_name }}</div>
<div class="case-procedure">{{ case.procedure_name }}</div>
</div>
<div class="case-status">
{% if case.status == 'scheduled' %}
<span class="badge bg-primary">Scheduled</span>
{% elif case.status == 'in_progress' %}
<span class="badge bg-warning">In Progress</span>
{% elif case.status == 'completed' %}
<span class="badge bg-success">Completed</span>
{% elif case.status == 'cancelled' %}
<span class="badge bg-danger">Cancelled</span>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-3">
<i class="fas fa-calendar-plus fa-2x text-muted mb-2"></i>
<p class="text-muted mb-0">No cases scheduled</p>
</div>
{% endif %}
<div class="d-flex justify-content-between mt-3">
<div class="btn-group">
<a href="{% url 'operating_theatre:block_detail' block.pk %}"
class="btn btn-outline-primary btn-sm">
<i class="fas fa-eye me-1"></i>View
</a>
{% if block.status == 'available' %}
<a href="{% url 'operating_theatre:block_edit' block.pk %}"
class="btn btn-outline-secondary btn-sm">
<i class="fas fa-edit me-1"></i>Edit
</a>
{% endif %}
</div>
<div class="btn-group">
{% if block.status == 'available' %}
<button class="btn btn-success btn-sm" onclick="addCase('{{ block.pk }}')">
<i class="fas fa-plus me-1"></i>Add Case
</button>
{% endif %}
<div class="dropdown">
<button class="btn btn-outline-secondary btn-sm dropdown-toggle"
data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="#" onclick="duplicateBlock('{{ block.pk }}')">
<i class="fas fa-copy me-2"></i>Duplicate
</a>
</li>
<li>
<a class="dropdown-item" href="#" onclick="printBlock('{{ block.pk }}')">
<i class="fas fa-print me-2"></i>Print Schedule
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item text-danger" href="#" onclick="cancelBlock('{{ block.pk }}')">
<i class="fas fa-times me-2"></i>Cancel Block
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
{% empty %}
<div class="col-12">
<div class="text-center py-5">
<i class="fas fa-calendar-times fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No blocks scheduled</h5>
<p class="text-muted">No OR blocks are scheduled for the selected date and filters.</p>
<a href="{% url 'operating_theatre:block_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Create First Block
</a>
</div>
</div>
{% endfor %}
</div>
<!-- Pagination -->
{% if is_paginated %}
<div class="d-flex justify-content-center mt-4">
<nav>
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Previous</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Last</a>
</li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
<script>
function changeDate() {
const selectedDate = document.getElementById('datePicker').value;
window.location.href = `?date=${selectedDate}`;
}
function addCase(blockId) {
window.location.href = `/operating-theatre/cases/create/?block=${blockId}`;
}
function duplicateBlock(blockId) {
if (confirm('Duplicate this block for another date?')) {
window.location.href = `/operating-theatre/blocks/${blockId}/duplicate/`;
}
}
function printBlock(blockId) {
window.open(`/operating-theatre/blocks/${blockId}/print/`, '_blank');
}
function cancelBlock(blockId) {
if (confirm('Cancel this OR block? All scheduled cases will need to be rescheduled.')) {
$.ajax({
url: `/operating-theatre/blocks/${blockId}/cancel/`,
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert('Error cancelling block: ' + response.error);
}
},
error: function() {
alert('Error cancelling block');
}
});
}
}
function exportSchedule(format) {
const currentDate = '{{ current_date|date:"Y-m-d" }}';
window.location.href = `/operating-theatre/blocks/export/?date=${currentDate}&format=${format}`;
}
// Auto-submit form on filter change
$('#filterForm select').on('change', function() {
$('#filterForm').submit();
});
</script>
{% endblock %}

View File

@ -4,9 +4,9 @@
{% block title %}{% if object %}Edit{% else %}Create{% endif %} Surgical Case - Operating Theatre{% endblock %}
{% block css %}
<link href="{% static 'assets/plugins/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/bootstrap-timepicker/css/bootstrap-timepicker.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/bootstrap-timepicker/css/bootstrap-timepicker.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
@ -404,9 +404,9 @@
{% endblock %}
{% block js %}
<script src="{% static 'assets/plugins/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js' %}"></script>
<script src="{% static 'assets/plugins/bootstrap-timepicker/js/bootstrap-timepicker.min.js' %}"></script>
<script src="{% static 'assets/plugins/select2/dist/js/select2.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-timepicker/js/bootstrap-timepicker.min.js' %}"></script>
<script src="{% static 'plugins/select2/dist/js/select2.min.js' %}"></script>
<script>
$(document).ready(function() {
@ -447,7 +447,7 @@ $(document).ready(function() {
function loadPatientInfo(patientId) {
$.ajax({
url: '{% url "operating_theatre:patient_info" 0 %}'.replace('0', patientId),
url: '{% url "patients:get_patient_info" 0 %}'.replace('0', patientId),
method: 'GET',
success: function(response) {
if (response.success) {
@ -467,62 +467,62 @@ function loadPatientInfo(patientId) {
});
}
function checkRoomAvailability() {
var roomId = $('#id_operating_room').val();
var date = $('#id_scheduled_date').val();
var startTime = $('#id_scheduled_start_time').val();
var duration = $('#id_estimated_duration').val();
if (roomId && date && startTime && duration) {
$.ajax({
url: '{% url "operating_theatre:check_room_availability" %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'room_id': roomId,
'date': date,
'start_time': startTime,
'duration': duration,
'case_id': '{{ object.id|default:"" }}'
},
success: function(response) {
if (response.available) {
$('#availability-message').html('Room is available for the selected time slot');
$('#room-availability').removeClass('alert-warning alert-danger').addClass('alert-success').show();
} else {
$('#availability-message').html('Room conflict: ' + response.conflict_reason);
$('#room-availability').removeClass('alert-success alert-info').addClass('alert-danger').show();
}
},
error: function() {
$('#availability-message').html('Unable to check room availability');
$('#room-availability').removeClass('alert-success alert-info').addClass('alert-warning').show();
}
});
}
}
{#function checkRoomAvailability() {#}
{# var roomId = $('#id_operating_room').val();#}
{# var date = $('#id_scheduled_date').val();#}
{# var startTime = $('#id_scheduled_start_time').val();#}
{# var duration = $('#id_estimated_duration').val();#}
{# #}
{# if (roomId && date && startTime && duration) {#}
{# $.ajax({#}
{# url: '{% url "operating_theatre:check_room_availability" %}',#}
{# method: 'POST',#}
{# data: {#}
{# 'csrfmiddlewaretoken': '{{ csrf_token }}',#}
{# 'room_id': roomId,#}
{# 'date': date,#}
{# 'start_time': startTime,#}
{# 'duration': duration,#}
{# 'case_id': '{{ object.id|default:"" }}'#}
{# },#}
{# success: function(response) {#}
{# if (response.available) {#}
{# $('#availability-message').html('Room is available for the selected time slot');#}
{# $('#room-availability').removeClass('alert-warning alert-danger').addClass('alert-success').show();#}
{# } else {#}
{# $('#availability-message').html('Room conflict: ' + response.conflict_reason);#}
{# $('#room-availability').removeClass('alert-success alert-info').addClass('alert-danger').show();#}
{# }#}
{# },#}
{# error: function() {#}
{# $('#availability-message').html('Unable to check room availability');#}
{# $('#room-availability').removeClass('alert-success alert-info').addClass('alert-warning').show();#}
{# }#}
{# });#}
{# }#}
{# }#}
function loadRoomSchedule() {
var roomId = $('#id_operating_room').val();
var date = $('#id_scheduled_date').val();
if (roomId && date) {
$.ajax({
url: '{% url "operating_theatre:room_schedule" %}',
method: 'GET',
data: {
'room_id': roomId,
'date': date
},
success: function(response) {
$('#room-schedule').html(response.html);
},
error: function() {
$('#room-schedule').html('<p class="text-danger">Unable to load room schedule</p>');
}
});
}
}
{#function loadRoomSchedule() {#}
{# var roomId = $('#id_operating_room').val();#}
{# var date = $('#id_scheduled_date').val();#}
{# #}
{# if (roomId && date) {#}
{# $.ajax({#}
{# url: '{% url "operating_theatre:room_schedule" %}',#}
{# method: 'GET',#}
{# data: {#}
{# 'room_id': roomId,#}
{# 'date': date#}
{# },#}
{# success: function(response) {#}
{# $('#room-schedule').html(response.html);#}
{# },#}
{# error: function() {#}
{# $('#room-schedule').html('<p class="text-danger">Unable to load room schedule</p>');#}
{# }#}
{# });#}
{# }#}
{# }#}
function addTeamMember() {
var html = `

View File

@ -0,0 +1,537 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Delete Equipment - {{ equipment.name }}{% endblock %}
{% block extra_css %}
<style>
.delete-header {
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.warning-section {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.equipment-info {
background: #f8f9fa;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.info-item {
display: flex;
flex-direction: column;
}
.info-label {
font-size: 0.875rem;
color: #6c757d;
font-weight: 600;
margin-bottom: 0.25rem;
}
.info-value {
color: #495057;
font-weight: 500;
}
.impact-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;
}
.alternative-actions {
background: #e7f3ff;
border: 1px solid #b3d9ff;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.action-button {
margin-bottom: 0.5rem;
margin-right: 0.5rem;
}
.confirmation-section {
background: #fff5f5;
border: 2px solid #fed7d7;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.type-to-confirm {
background: #f7fafc;
border: 2px solid #e2e8f0;
border-radius: 0.25rem;
padding: 0.75rem;
font-family: monospace;
font-weight: bold;
text-align: center;
margin-bottom: 1rem;
}
.equipment-image {
max-width: 150px;
max-height: 150px;
border-radius: 0.375rem;
border: 2px solid #dee2e6;
}
@media (max-width: 768px) {
.delete-header {
padding: 1.5rem;
}
.info-grid {
grid-template-columns: 1fr;
}
}
</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:equipment_list' %}">Equipment</a></li>
<li class="breadcrumb-item"><a href="{% url 'operating_theatre:equipment_detail' equipment.pk %}">{{ equipment.name }}</a></li>
<li class="breadcrumb-item active">Delete Equipment</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-trash-alt me-2"></i>Delete Equipment
</h1>
</div>
<div class="ms-auto">
<a href="{% url 'operating_theatre:equipment_detail' equipment.pk %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>Back to Equipment
</a>
</div>
</div>
<!-- Delete Header -->
<div class="delete-header">
<div class="row">
<div class="col-md-8">
<h2 class="mb-3">
<i class="fas fa-exclamation-triangle me-2"></i>
Delete Equipment
</h2>
<p class="mb-0">
You are about to permanently delete this equipment from the system. This action cannot be undone and will affect all related records and maintenance history.
</p>
</div>
<div class="col-md-4 text-end">
<div class="mb-2">
<i class="fas fa-tools fa-3x"></i>
</div>
</div>
</div>
</div>
<!-- Warning Section -->
<div class="warning-section">
<h5 class="mb-3">
<i class="fas fa-exclamation-triangle me-2"></i>
Critical Warning
</h5>
<ul class="mb-0">
<li>This equipment record will be permanently deleted</li>
<li>All maintenance history and logs will be removed</li>
<li>Any active reservations will be cancelled</li>
<li>Financial records and depreciation data will be lost</li>
<li>QR codes and asset tags will become invalid</li>
<li>Related documents and images will be deleted</li>
</ul>
</div>
<!-- Equipment Information -->
<div class="equipment-info">
<h5 class="mb-3">Equipment Information</h5>
<div class="row">
<div class="col-md-9">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Equipment Name</div>
<div class="info-value">{{ equipment.name }}</div>
</div>
<div class="info-item">
<div class="info-label">Equipment ID</div>
<div class="info-value">{{ equipment.equipment_id|default:"Not assigned" }}</div>
</div>
<div class="info-item">
<div class="info-label">Serial Number</div>
<div class="info-value">{{ equipment.serial_number }}</div>
</div>
<div class="info-item">
<div class="info-label">Manufacturer</div>
<div class="info-value">{{ equipment.manufacturer }} {{ equipment.model }}</div>
</div>
<div class="info-item">
<div class="info-label">Category</div>
<div class="info-value">{{ equipment.get_category_display }}</div>
</div>
<div class="info-item">
<div class="info-label">Current Location</div>
<div class="info-value">{{ equipment.current_location|default:"Not specified" }}</div>
</div>
<div class="info-item">
<div class="info-label">Status</div>
<div class="info-value">
{% if equipment.status == 'available' %}
<span class="badge bg-success">Available</span>
{% elif equipment.status == 'in_use' %}
<span class="badge bg-primary">In Use</span>
{% elif equipment.status == 'maintenance' %}
<span class="badge bg-warning">Maintenance</span>
{% elif equipment.status == 'out_of_order' %}
<span class="badge bg-danger">Out of Order</span>
{% endif %}
</div>
</div>
<div class="info-item">
<div class="info-label">Purchase Cost</div>
<div class="info-value">
{% if equipment.purchase_cost %}
${{ equipment.purchase_cost|floatformat:2 }}
{% else %}
Not specified
{% endif %}
</div>
</div>
</div>
</div>
<div class="col-md-3 text-center">
{% if equipment.image %}
<img src="{{ equipment.image.url }}" alt="{{ equipment.name }}" class="equipment-image">
{% else %}
<div class="equipment-image d-flex align-items-center justify-content-center bg-light">
<i class="fas fa-tools fa-3x text-muted"></i>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Impact Analysis -->
<div class="impact-section">
<div class="section-header">
<i class="fas fa-exclamation-circle me-2"></i>Impact Analysis
</div>
<div class="section-content">
<div class="row">
<div class="col-md-3">
<div class="text-center mb-3">
<div class="h3 text-danger">{{ maintenance_records_count }}</div>
<div class="text-muted">Maintenance Records</div>
</div>
</div>
<div class="col-md-3">
<div class="text-center mb-3">
<div class="h3 text-warning">{{ active_reservations_count }}</div>
<div class="text-muted">Active Reservations</div>
</div>
</div>
<div class="col-md-3">
<div class="text-center mb-3">
<div class="h3 text-info">{{ usage_logs_count }}</div>
<div class="text-muted">Usage Logs</div>
</div>
</div>
<div class="col-md-3">
<div class="text-center mb-3">
<div class="h3 text-secondary">{{ documents_count }}</div>
<div class="text-muted">Related Documents</div>
</div>
</div>
</div>
</div>
</div>
<!-- Blocking Conditions -->
{% if blocking_conditions %}
<div class="impact-section">
<div class="section-header">
<i class="fas fa-ban me-2"></i>Deletion Blocked
</div>
<div class="section-content">
<div class="alert alert-danger">
<h6 class="alert-heading">Cannot Delete Equipment</h6>
<p class="mb-0">This equipment cannot be deleted due to the following conditions:</p>
<ul class="mt-2 mb-0">
{% for condition in blocking_conditions %}
<li>{{ condition }}</li>
{% endfor %}
</ul>
</div>
<p class="mb-0">Please resolve these issues before attempting to delete this equipment.</p>
</div>
</div>
{% else %}
<!-- Alternative Actions -->
<div class="alternative-actions">
<h5 class="mb-3">
<i class="fas fa-lightbulb me-2"></i>
Consider These Alternatives
</h5>
<p class="mb-3">Before deleting this equipment, consider these alternative actions:</p>
<button class="btn btn-outline-primary action-button" onclick="deactivateEquipment()">
<i class="fas fa-power-off me-1"></i>Deactivate Equipment
</button>
<button class="btn btn-outline-warning action-button" onclick="markOutOfOrder()">
<i class="fas fa-exclamation-triangle me-1"></i>Mark Out of Order
</button>
<button class="btn btn-outline-info action-button" onclick="transferEquipment()">
<i class="fas fa-exchange-alt me-1"></i>Transfer to Storage
</button>
<button class="btn btn-outline-secondary action-button" onclick="archiveEquipment()">
<i class="fas fa-archive me-1"></i>Archive Equipment
</button>
</div>
<!-- Confirmation Section -->
<div class="confirmation-section">
<h5 class="mb-3">
<i class="fas fa-shield-alt me-2"></i>
Deletion Confirmation Required
</h5>
<form method="post" id="deleteForm">
{% csrf_token %}
<div class="mb-3">
<label class="form-label">
<strong>Type the following text to confirm deletion:</strong>
</label>
<div class="type-to-confirm">
DELETE {{ equipment.name }} {{ equipment.serial_number }}
</div>
<input type="text" class="form-control" id="confirmationText"
placeholder="Type the confirmation text here..." required>
<div class="form-text">This confirmation helps prevent accidental deletions.</div>
</div>
<div class="mb-3">
<label class="form-label">Reason for Deletion</label>
<select class="form-select" name="deletion_reason" required>
<option value="">Select a reason...</option>
<option value="equipment_failure">Equipment Failure</option>
<option value="obsolete">Equipment Obsolete</option>
<option value="replaced">Replaced by New Equipment</option>
<option value="sold">Equipment Sold</option>
<option value="donated">Equipment Donated</option>
<option value="scrapped">Equipment Scrapped</option>
<option value="lost_stolen">Lost or Stolen</option>
<option value="other">Other</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Additional Notes</label>
<textarea class="form-control" name="deletion_notes" rows="3"
placeholder="Provide additional context for this deletion..."></textarea>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="cancelReservationsCheck" name="cancel_reservations" checked>
<label class="form-check-label" for="cancelReservationsCheck">
Automatically cancel all active reservations
</label>
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="notifyUsersCheck" name="notify_users" checked>
<label class="form-check-label" for="notifyUsersCheck">
Notify users with active reservations
</label>
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="archiveDataCheck" name="archive_data">
<label class="form-check-label" for="archiveDataCheck">
Archive maintenance and usage data before deletion
</label>
</div>
</div>
<div class="d-flex justify-content-between">
<div>
<a href="{% url 'operating_theatre:equipment_detail' equipment.pk %}" class="btn btn-outline-secondary">
<i class="fas fa-times me-1"></i>Cancel
</a>
</div>
<div>
<button type="submit" class="btn btn-danger" id="deleteButton" disabled>
<i class="fas fa-trash-alt me-1"></i>
Permanently Delete Equipment
</button>
</div>
</div>
</form>
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
<script>
$(document).ready(function() {
const expectedText = 'DELETE {{ equipment.name }} {{ equipment.serial_number }}';
$('#confirmationText').on('input', function() {
const enteredText = $(this).val().trim();
const deleteButton = $('#deleteButton');
if (enteredText === expectedText) {
deleteButton.prop('disabled', false);
$(this).removeClass('is-invalid').addClass('is-valid');
} else {
deleteButton.prop('disabled', true);
$(this).removeClass('is-valid');
if (enteredText.length > 0) {
$(this).addClass('is-invalid');
}
}
});
});
function deactivateEquipment() {
if (confirm('Deactivate this equipment instead of deleting it?')) {
$.ajax({
url: '{% url "operating_theatre:equipment_deactivate" equipment.pk %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
alert('Equipment has been deactivated successfully.');
window.location.href = '{% url "operating_theatre:equipment_list" %}';
} else {
alert('Error deactivating equipment: ' + response.error);
}
},
error: function() {
alert('Error deactivating equipment');
}
});
}
}
function markOutOfOrder() {
if (confirm('Mark this equipment as out of order?')) {
$.ajax({
url: '{% url "operating_theatre:equipment_out_of_order" equipment.pk %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
alert('Equipment has been marked as out of order.');
window.location.href = '{% url "operating_theatre:equipment_detail" equipment.pk %}';
} else {
alert('Error updating equipment status: ' + response.error);
}
},
error: function() {
alert('Error updating equipment status');
}
});
}
}
function transferEquipment() {
window.location.href = '{% url "operating_theatre:equipment_transfer" equipment.pk %}';
}
function archiveEquipment() {
if (confirm('Archive this equipment instead of deleting it?')) {
$.ajax({
url: '{% url "operating_theatre:equipment_archive" equipment.pk %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
alert('Equipment has been archived successfully.');
window.location.href = '{% url "operating_theatre:equipment_list" %}';
} else {
alert('Error archiving equipment: ' + response.error);
}
},
error: function() {
alert('Error archiving equipment');
}
});
}
}
// Form submission with additional confirmation
$('#deleteForm').on('submit', function(e) {
e.preventDefault();
const maintenanceRecords = {{ maintenance_records_count }};
const activeReservations = {{ active_reservations_count }};
let confirmMessage = 'Are you absolutely sure you want to delete this equipment?\n\n';
confirmMessage += `This will remove ${maintenanceRecords} maintenance record(s) and cancel ${activeReservations} active reservation(s).\n\n`;
confirmMessage += 'This action cannot be undone.';
if (confirm(confirmMessage)) {
// Show loading state
$('#deleteButton').html('<i class="fas fa-spinner fa-spin me-1"></i>Deleting...').prop('disabled', true);
// Submit the form
this.submit();
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,691 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}{{ equipment.name }} - Equipment Details{% endblock %}
{% block extra_css %}
<style>
.equipment-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.equipment-image {
width: 100%;
max-width: 400px;
height: 300px;
object-fit: cover;
border-radius: 0.375rem;
border: 3px solid white;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.info-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;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
}
.info-item {
display: flex;
flex-direction: column;
}
.info-label {
font-size: 0.875rem;
color: #6c757d;
font-weight: 600;
margin-bottom: 0.25rem;
}
.info-value {
color: #495057;
font-weight: 500;
}
.status-indicator {
width: 16px;
height: 16px;
border-radius: 50%;
display: inline-block;
margin-right: 0.5rem;
}
.status-available { background-color: #28a745; }
.status-in-use { background-color: #007bff; }
.status-maintenance { background-color: #ffc107; }
.status-out-of-order { background-color: #dc3545; }
.status-reserved { background-color: #6f42c1; }
.maintenance-timeline {
position: relative;
padding-left: 2rem;
}
.timeline-item {
position: relative;
padding-bottom: 1.5rem;
border-left: 2px solid #dee2e6;
margin-left: 0.5rem;
}
.timeline-item:last-child {
border-left: none;
}
.timeline-marker {
position: absolute;
left: -6px;
top: 0;
width: 12px;
height: 12px;
border-radius: 50%;
background: #007bff;
border: 2px solid white;
box-shadow: 0 0 0 2px #007bff;
}
.timeline-content {
margin-left: 1rem;
}
.timeline-date {
font-size: 0.875rem;
color: #6c757d;
font-weight: 600;
}
.timeline-title {
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
}
.timeline-description {
color: #6c757d;
font-size: 0.875rem;
}
.usage-chart {
height: 300px;
margin-bottom: 1rem;
}
.qr-code-section {
text-align: center;
padding: 2rem;
background: #f8f9fa;
border-radius: 0.375rem;
margin-bottom: 1.5rem;
}
.qr-code {
width: 150px;
height: 150px;
margin: 0 auto 1rem;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
background: white;
display: flex;
align-items: center;
justify-content: center;
}
.alert-maintenance {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 0.25rem;
padding: 1rem;
margin-bottom: 1.5rem;
}
.specifications-table {
width: 100%;
margin-bottom: 0;
}
.specifications-table th {
background: #f8f9fa;
font-weight: 600;
color: #495057;
border-bottom: 2px solid #dee2e6;
padding: 0.75rem;
}
.specifications-table td {
padding: 0.75rem;
border-bottom: 1px solid #dee2e6;
}
@media (max-width: 768px) {
.equipment-header {
padding: 1.5rem;
}
.info-grid {
grid-template-columns: 1fr;
}
.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:equipment_list' %}">Equipment</a></li>
<li class="breadcrumb-item active">{{ equipment.name }}</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-tools me-2"></i>{{ equipment.name }}
</h1>
</div>
<div class="ms-auto">
<div class="btn-group">
<a href="{% url 'operating_theatre:equipment_edit' equipment.pk %}" class="btn btn-primary">
<i class="fas fa-edit me-1"></i>Edit
</a>
<button class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="#" onclick="scheduleMaintenance()">
<i class="fas fa-wrench me-2"></i>Schedule Maintenance
</a>
</li>
<li>
<a class="dropdown-item" href="#" onclick="generateQR()">
<i class="fas fa-qrcode me-2"></i>Generate QR Code
</a>
</li>
<li>
<a class="dropdown-item" href="#" onclick="printDetails()">
<i class="fas fa-print me-2"></i>Print Details
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item text-danger" href="#" onclick="reportIssue()">
<i class="fas fa-exclamation-triangle me-2"></i>Report Issue
</a>
</li>
</ul>
</div>
<a href="{% url 'operating_theatre:equipment_list' %}" class="btn btn-outline-secondary ms-2">
<i class="fas fa-arrow-left me-1"></i>Back to List
</a>
</div>
</div>
<!-- Equipment Header -->
<div class="equipment-header">
<div class="row">
<div class="col-md-8">
<div class="d-flex align-items-center mb-3">
<div>
<span class="status-indicator status-{{ equipment.status }}"></span>
{% if equipment.status == 'available' %}
<span class="badge bg-success me-2">Available</span>
{% elif equipment.status == 'in_use' %}
<span class="badge bg-primary me-2">In Use</span>
{% elif equipment.status == 'maintenance' %}
<span class="badge bg-warning me-2">Maintenance</span>
{% elif equipment.status == 'out_of_order' %}
<span class="badge bg-danger me-2">Out of Order</span>
{% elif equipment.status == 'reserved' %}
<span class="badge bg-info me-2">Reserved</span>
{% endif %}
<span class="badge bg-light text-dark">{{ equipment.get_category_display }}</span>
</div>
</div>
<h2 class="mb-2">{{ equipment.name }}</h2>
<p class="mb-3">{{ equipment.description|default:"No description available" }}</p>
<div class="row">
<div class="col-md-6">
<div class="mb-2">
<strong>Serial Number:</strong> {{ equipment.serial_number }}
</div>
<div class="mb-2">
<strong>Manufacturer:</strong> {{ equipment.manufacturer }}
</div>
<div class="mb-2">
<strong>Model:</strong> {{ equipment.model }}
</div>
</div>
<div class="col-md-6">
<div class="mb-2">
<strong>Location:</strong>
{% if equipment.current_location %}
{{ equipment.current_location.name }}
{% else %}
<span class="text-muted">Not assigned</span>
{% endif %}
</div>
<div class="mb-2">
<strong>Purchase Date:</strong>
{% if equipment.purchase_date %}
{{ equipment.purchase_date|date:"M d, Y" }}
{% else %}
<span class="text-muted">Not recorded</span>
{% endif %}
</div>
<div class="mb-2">
<strong>Warranty Expires:</strong>
{% if equipment.warranty_expiry %}
{{ equipment.warranty_expiry|date:"M d, Y" }}
{% else %}
<span class="text-muted">Not recorded</span>
{% endif %}
</div>
</div>
</div>
</div>
<div class="col-md-4 text-center">
{% if equipment.image %}
<img src="{{ equipment.image.url }}" alt="{{ equipment.name }}" class="equipment-image">
{% else %}
<div class="equipment-image bg-light d-flex align-items-center justify-content-center">
<i class="fas fa-tools fa-4x text-muted"></i>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Maintenance Alert -->
{% if equipment.maintenance_due %}
<div class="alert-maintenance">
<h6 class="mb-2">
<i class="fas fa-exclamation-triangle me-2"></i>Maintenance Due
</h6>
<p class="mb-0">
This equipment is due for maintenance. Last maintenance was on
{% if equipment.last_maintenance_date %}
{{ equipment.last_maintenance_date|date:"M d, Y" }}
{% else %}
never
{% endif %}.
</p>
</div>
{% endif %}
<div class="row">
<!-- Equipment Information -->
<div class="col-lg-8">
<!-- Basic Information -->
<div class="info-section">
<div class="section-header">
<i class="fas fa-info-circle me-2"></i>Equipment Information
</div>
<div class="section-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Equipment ID</div>
<div class="info-value">{{ equipment.equipment_id|default:"Not assigned" }}</div>
</div>
<div class="info-item">
<div class="info-label">Asset Tag</div>
<div class="info-value">{{ equipment.asset_tag|default:"Not assigned" }}</div>
</div>
<div class="info-item">
<div class="info-label">Category</div>
<div class="info-value">{{ equipment.get_category_display }}</div>
</div>
<div class="info-item">
<div class="info-label">Current Status</div>
<div class="info-value">
<span class="status-indicator status-{{ equipment.status }}"></span>
{{ equipment.get_status_display }}
</div>
</div>
<div class="info-item">
<div class="info-label">Purchase Cost</div>
<div class="info-value">
{% if equipment.purchase_cost %}
${{ equipment.purchase_cost|floatformat:2 }}
{% else %}
<span class="text-muted">Not recorded</span>
{% endif %}
</div>
</div>
<div class="info-item">
<div class="info-label">Depreciation</div>
<div class="info-value">
{% if equipment.depreciation_rate %}
{{ equipment.depreciation_rate }}% per year
{% else %}
<span class="text-muted">Not set</span>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- Technical Specifications -->
{% if equipment.specifications %}
<div class="info-section">
<div class="section-header">
<i class="fas fa-cogs me-2"></i>Technical Specifications
</div>
<div class="section-content">
<table class="specifications-table table">
<thead>
<tr>
<th>Specification</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for spec, value in equipment.specifications.items %}
<tr>
<td>{{ spec|title }}</td>
<td>{{ value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<!-- Maintenance History -->
<div class="info-section">
<div class="section-header">
<i class="fas fa-history me-2"></i>Maintenance History
</div>
<div class="section-content">
{% if equipment.maintenance_records.all %}
<div class="maintenance-timeline">
{% for record in equipment.maintenance_records.all %}
<div class="timeline-item">
<div class="timeline-marker"></div>
<div class="timeline-content">
<div class="timeline-date">{{ record.date|date:"M d, Y" }}</div>
<div class="timeline-title">{{ record.maintenance_type|title }}</div>
<div class="timeline-description">
{{ record.description|default:"No description provided" }}
{% if record.performed_by %}
<br><small class="text-muted">Performed by: {{ record.performed_by.get_full_name }}</small>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-4">
<i class="fas fa-wrench fa-2x text-muted mb-3"></i>
<p class="text-muted">No maintenance records found</p>
<button class="btn btn-primary" onclick="scheduleMaintenance()">
<i class="fas fa-plus me-1"></i>Schedule First Maintenance
</button>
</div>
{% endif %}
</div>
</div>
<!-- Usage Statistics -->
<div class="info-section">
<div class="section-header">
<i class="fas fa-chart-line me-2"></i>Usage Statistics
</div>
<div class="section-content">
<div class="usage-chart">
<canvas id="usageChart"></canvas>
</div>
<div class="row text-center">
<div class="col-md-3">
<div class="mb-2">
<strong class="text-primary">{{ equipment.total_usage_hours|default:0 }}</strong>
</div>
<div class="text-muted small">Total Hours</div>
</div>
<div class="col-md-3">
<div class="mb-2">
<strong class="text-success">{{ equipment.monthly_usage_hours|default:0 }}</strong>
</div>
<div class="text-muted small">This Month</div>
</div>
<div class="col-md-3">
<div class="mb-2">
<strong class="text-warning">{{ equipment.utilization_rate|default:0 }}%</strong>
</div>
<div class="text-muted small">Utilization</div>
</div>
<div class="col-md-3">
<div class="mb-2">
<strong class="text-info">{{ equipment.efficiency_score|default:0 }}%</strong>
</div>
<div class="text-muted small">Efficiency</div>
</div>
</div>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<!-- QR Code -->
<div class="qr-code-section">
<h6 class="mb-3">Equipment QR Code</h6>
<div class="qr-code">
{% if equipment.qr_code %}
<img src="{{ equipment.qr_code.url }}" alt="QR Code" style="width: 100%; height: 100%;">
{% else %}
<i class="fas fa-qrcode fa-3x text-muted"></i>
{% endif %}
</div>
<button class="btn btn-outline-primary btn-sm" onclick="generateQR()">
<i class="fas fa-download me-1"></i>Download QR Code
</button>
</div>
<!-- Quick Actions -->
<div class="info-section">
<div class="section-header">
<i class="fas fa-bolt me-2"></i>Quick Actions
</div>
<div class="section-content">
<div class="d-grid gap-2">
{% if equipment.status == 'available' %}
<button class="btn btn-success" onclick="reserveEquipment()">
<i class="fas fa-lock me-1"></i>Reserve Equipment
</button>
{% elif equipment.status == 'reserved' %}
<button class="btn btn-warning" onclick="releaseEquipment()">
<i class="fas fa-unlock me-1"></i>Release Reservation
</button>
{% endif %}
<button class="btn btn-primary" onclick="scheduleMaintenance()">
<i class="fas fa-wrench me-1"></i>Schedule Maintenance
</button>
<button class="btn btn-outline-secondary" onclick="viewHistory()">
<i class="fas fa-history me-1"></i>View Full History
</button>
<button class="btn btn-outline-danger" onclick="reportIssue()">
<i class="fas fa-exclamation-triangle me-1"></i>Report Issue
</button>
</div>
</div>
</div>
<!-- Current Assignment -->
{% if equipment.current_assignment %}
<div class="info-section">
<div class="section-header">
<i class="fas fa-user-md me-2"></i>Current Assignment
</div>
<div class="section-content">
<div class="mb-2">
<strong>Assigned to:</strong> {{ equipment.current_assignment.assigned_to.get_full_name }}
</div>
<div class="mb-2">
<strong>Since:</strong> {{ equipment.current_assignment.assigned_date|date:"M d, Y" }}
</div>
<div class="mb-2">
<strong>Purpose:</strong> {{ equipment.current_assignment.purpose|default:"General use" }}
</div>
{% if equipment.current_assignment.expected_return %}
<div class="mb-2">
<strong>Expected Return:</strong> {{ equipment.current_assignment.expected_return|date:"M d, Y" }}
</div>
{% endif %}
</div>
</div>
{% endif %}
<!-- Related Documents -->
{% if equipment.documents.all %}
<div class="info-section">
<div class="section-header">
<i class="fas fa-file-alt me-2"></i>Related Documents
</div>
<div class="section-content">
{% for document in equipment.documents.all %}
<div class="d-flex justify-content-between align-items-center mb-2">
<div>
<div class="fw-bold">{{ document.name }}</div>
<small class="text-muted">{{ document.document_type|title }}</small>
</div>
<a href="{{ document.file.url }}" class="btn btn-outline-primary btn-sm" target="_blank">
<i class="fas fa-download"></i>
</a>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// Usage Chart
const ctx = document.getElementById('usageChart').getContext('2d');
const usageChart = new Chart(ctx, {
type: 'line',
data: {
labels: {{ equipment.usage_chart_labels|safe }},
datasets: [{
label: 'Usage Hours',
data: {{ equipment.usage_chart_data|safe }},
borderColor: '#007bff',
backgroundColor: 'rgba(0, 123, 255, 0.1)',
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
function reserveEquipment() {
if (confirm('Reserve this equipment?')) {
$.ajax({
url: '{% url "operating_theatre:equipment_reserve" equipment.pk %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert('Error reserving equipment: ' + response.error);
}
},
error: function() {
alert('Error reserving equipment');
}
});
}
}
function releaseEquipment() {
if (confirm('Release this equipment reservation?')) {
$.ajax({
url: '{% url "operating_theatre:equipment_release" equipment.pk %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert('Error releasing equipment: ' + response.error);
}
},
error: function() {
alert('Error releasing equipment');
}
});
}
}
function scheduleMaintenance() {
window.location.href = '{% url "operating_theatre:equipment_maintenance_schedule" equipment.pk %}';
}
function generateQR() {
window.open('{% url "operating_theatre:equipment_qr_code" equipment.pk %}', '_blank');
}
function viewHistory() {
window.location.href = '{% url "operating_theatre:equipment_history" equipment.pk %}';
}
function reportIssue() {
window.location.href = '{% url "operating_theatre:equipment_report_issue" equipment.pk %}';
}
function printDetails() {
window.print();
}
</script>
{% endblock %}

View File

@ -0,0 +1,743 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}{% if form.instance.pk %}Edit{% else %}Add{% endif %} Equipment{% endblock %}
{% block extra_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;
}
.image-upload-area {
border: 2px dashed #dee2e6;
border-radius: 0.375rem;
padding: 2rem;
text-align: center;
background: #f8f9fa;
transition: all 0.3s ease;
cursor: pointer;
}
.image-upload-area:hover {
border-color: #007bff;
background: #e7f3ff;
}
.image-upload-area.dragover {
border-color: #007bff;
background: #e7f3ff;
}
.image-preview {
max-width: 200px;
max-height: 200px;
border-radius: 0.375rem;
border: 2px solid #dee2e6;
margin: 1rem auto;
}
.specifications-builder {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1rem;
background: #f8f9fa;
}
.spec-item {
display: flex;
gap: 0.5rem;
margin-bottom: 0.5rem;
align-items: center;
}
.spec-item input {
flex: 1;
}
.maintenance-schedule {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 0.375rem;
padding: 1rem;
margin-top: 1rem;
}
.cost-calculator {
background: #e7f3ff;
border: 1px solid #b3d9ff;
border-radius: 0.375rem;
padding: 1rem;
margin-top: 1rem;
}
.validation-feedback {
display: block;
width: 100%;
margin-top: 0.25rem;
font-size: 0.875rem;
}
.validation-feedback.valid {
color: #28a745;
}
.validation-feedback.invalid {
color: #dc3545;
}
.auto-generate-btn {
position: absolute;
right: 0.5rem;
top: 50%;
transform: translateY(-50%);
border: none;
background: #007bff;
color: white;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
}
.input-group-auto {
position: relative;
}
@media (max-width: 768px) {
.form-header {
padding: 1.5rem;
}
.section-content {
padding: 1rem;
}
.spec-item {
flex-direction: column;
}
.spec-item input {
margin-bottom: 0.5rem;
}
}
</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:equipment_list' %}">Equipment</a></li>
<li class="breadcrumb-item active">{% if form.instance.pk %}Edit{% else %}Add{% endif %} Equipment</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-tools me-2"></i>{% if form.instance.pk %}Edit{% else %}Add{% endif %} Equipment
</h1>
</div>
<div class="ms-auto">
<a href="{% url 'operating_theatre:equipment_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>Back to List
</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 Equipment{% else %}Add New Equipment{% endif %}</h2>
<p class="mb-0">
{% if form.instance.pk %}
Update the equipment information. Changes will be logged for audit purposes.
{% else %}
Add new equipment to the operating theatre inventory. All fields marked with * are required.
{% endif %}
</p>
</div>
<div class="col-md-4 text-end">
<div class="mb-2">
<i class="fas fa-info-circle me-2"></i>
<span>Equipment tracking helps optimize OR efficiency</span>
</div>
</div>
</div>
</div>
<form method="post" enctype="multipart/form-data" id="equipmentForm">
{% 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-8">
<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label required">Equipment Name</label>
{{ form.name }}
{% if form.name.errors %}
<div class="validation-feedback invalid">{{ form.name.errors.0 }}</div>
{% endif %}
<div class="form-text">Descriptive name for the equipment</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label required">Category</label>
{{ form.category }}
{% if form.category.errors %}
<div class="validation-feedback invalid">{{ form.category.errors.0 }}</div>
{% endif %}
<div class="form-text">Equipment category for organization</div>
</div>
</div>
</div>
<div class="form-group mb-3">
<label class="form-label">Description</label>
{{ form.description }}
{% if form.description.errors %}
<div class="validation-feedback invalid">{{ form.description.errors.0 }}</div>
{% endif %}
<div class="form-text">Detailed description of the equipment</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Equipment ID</label>
<div class="input-group-auto">
{{ form.equipment_id }}
<button type="button" class="auto-generate-btn" onclick="generateEquipmentId()">
<i class="fas fa-magic"></i>
</button>
</div>
{% if form.equipment_id.errors %}
<div class="validation-feedback invalid">{{ form.equipment_id.errors.0 }}</div>
{% endif %}
<div class="form-text">Unique identifier for the equipment</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label required">Serial Number</label>
{{ form.serial_number }}
{% if form.serial_number.errors %}
<div class="validation-feedback invalid">{{ form.serial_number.errors.0 }}</div>
{% endif %}
<div class="form-text">Manufacturer's serial number</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group mb-3">
<label class="form-label">Equipment Image</label>
<div class="image-upload-area" onclick="$('#id_image').click()">
{% if form.instance.image %}
<img src="{{ form.instance.image.url }}" alt="Equipment Image" class="image-preview" id="imagePreview">
{% else %}
<div id="uploadPlaceholder">
<i class="fas fa-camera fa-3x text-muted mb-2"></i>
<div class="text-muted">Click to upload image</div>
<small class="text-muted">JPG, PNG up to 5MB</small>
</div>
<img id="imagePreview" class="image-preview" style="display: none;">
{% endif %}
</div>
{{ form.image }}
{% if form.image.errors %}
<div class="validation-feedback invalid">{{ form.image.errors.0 }}</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- Manufacturer Information -->
<div class="form-section">
<div class="section-header">
<i class="fas fa-industry me-2"></i>Manufacturer Information
</div>
<div class="section-content">
<div class="row">
<div class="col-md-4">
<div class="form-group mb-3">
<label class="form-label required">Manufacturer</label>
{{ form.manufacturer }}
{% if form.manufacturer.errors %}
<div class="validation-feedback invalid">{{ form.manufacturer.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="form-group mb-3">
<label class="form-label required">Model</label>
{{ form.model }}
{% if form.model.errors %}
<div class="validation-feedback invalid">{{ form.model.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="form-group mb-3">
<label class="form-label">Model Year</label>
{{ form.model_year }}
{% if form.model_year.errors %}
<div class="validation-feedback invalid">{{ form.model_year.errors.0 }}</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- Location and Status -->
<div class="form-section">
<div class="section-header">
<i class="fas fa-map-marker-alt me-2"></i>Location and Status
</div>
<div class="section-content">
<div class="row">
<div class="col-md-4">
<div class="form-group mb-3">
<label class="form-label">Current Location</label>
{{ form.current_location }}
{% if form.current_location.errors %}
<div class="validation-feedback invalid">{{ form.current_location.errors.0 }}</div>
{% endif %}
<div class="form-text">Operating room or storage location</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group mb-3">
<label class="form-label required">Status</label>
{{ form.status }}
{% if form.status.errors %}
<div class="validation-feedback invalid">{{ form.status.errors.0 }}</div>
{% endif %}
<div class="form-text">Current operational status</div>
</div>
</div>
<div class="col-md-4">
<div class="form-group mb-3">
<label class="form-label">Asset Tag</label>
<div class="input-group-auto">
{{ form.asset_tag }}
<button type="button" class="auto-generate-btn" onclick="generateAssetTag()">
<i class="fas fa-magic"></i>
</button>
</div>
{% if form.asset_tag.errors %}
<div class="validation-feedback invalid">{{ form.asset_tag.errors.0 }}</div>
{% endif %}
<div class="form-text">Hospital asset tracking number</div>
</div>
</div>
</div>
</div>
</div>
<!-- Financial Information -->
<div class="form-section">
<div class="section-header">
<i class="fas fa-dollar-sign me-2"></i>Financial Information
</div>
<div class="section-content">
<div class="row">
<div class="col-md-3">
<div class="form-group mb-3">
<label class="form-label">Purchase Date</label>
{{ form.purchase_date }}
{% if form.purchase_date.errors %}
<div class="validation-feedback invalid">{{ form.purchase_date.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-3">
<div class="form-group mb-3">
<label class="form-label">Purchase Cost</label>
{{ form.purchase_cost }}
{% if form.purchase_cost.errors %}
<div class="validation-feedback invalid">{{ form.purchase_cost.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-3">
<div class="form-group mb-3">
<label class="form-label">Warranty Expiry</label>
{{ form.warranty_expiry }}
{% if form.warranty_expiry.errors %}
<div class="validation-feedback invalid">{{ form.warranty_expiry.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-3">
<div class="form-group mb-3">
<label class="form-label">Depreciation Rate (%)</label>
{{ form.depreciation_rate }}
{% if form.depreciation_rate.errors %}
<div class="validation-feedback invalid">{{ form.depreciation_rate.errors.0 }}</div>
{% endif %}
</div>
</div>
</div>
<div class="cost-calculator">
<h6 class="mb-2">
<i class="fas fa-calculator me-2"></i>Cost Analysis
</h6>
<div class="row">
<div class="col-md-4">
<div class="text-center">
<div class="h5 text-primary" id="currentValue">$0.00</div>
<small class="text-muted">Current Value</small>
</div>
</div>
<div class="col-md-4">
<div class="text-center">
<div class="h5 text-warning" id="annualDepreciation">$0.00</div>
<small class="text-muted">Annual Depreciation</small>
</div>
</div>
<div class="col-md-4">
<div class="text-center">
<div class="h5 text-info" id="equipmentAge">0 years</div>
<small class="text-muted">Equipment Age</small>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Technical Specifications -->
<div class="form-section">
<div class="section-header">
<i class="fas fa-cogs me-2"></i>Technical Specifications
</div>
<div class="section-content">
<div class="specifications-builder">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">Equipment Specifications</h6>
<button type="button" class="btn btn-outline-primary btn-sm" onclick="addSpecification()">
<i class="fas fa-plus me-1"></i>Add Specification
</button>
</div>
<div id="specificationsContainer">
{% if form.instance.specifications %}
{% for spec, value in form.instance.specifications.items %}
<div class="spec-item">
<input type="text" class="form-control" placeholder="Specification name" value="{{ spec }}" name="spec_name[]">
<input type="text" class="form-control" placeholder="Value" value="{{ value }}" name="spec_value[]">
<button type="button" class="btn btn-outline-danger btn-sm" onclick="removeSpecification(this)">
<i class="fas fa-trash"></i>
</button>
</div>
{% endfor %}
{% endif %}
</div>
<div class="form-text mt-2">
Add technical specifications like power requirements, dimensions, weight, etc.
</div>
</div>
</div>
</div>
<!-- Maintenance Information -->
<div class="form-section">
<div class="section-header">
<i class="fas fa-wrench me-2"></i>Maintenance Information
</div>
<div class="section-content">
<div class="row">
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Last Maintenance Date</label>
{{ form.last_maintenance_date }}
{% if form.last_maintenance_date.errors %}
<div class="validation-feedback invalid">{{ form.last_maintenance_date.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="form-group mb-3">
<label class="form-label">Maintenance Interval (days)</label>
{{ form.maintenance_interval }}
{% if form.maintenance_interval.errors %}
<div class="validation-feedback invalid">{{ form.maintenance_interval.errors.0 }}</div>
{% endif %}
</div>
</div>
</div>
<div class="maintenance-schedule">
<h6 class="mb-2">
<i class="fas fa-calendar-alt me-2"></i>Maintenance Schedule
</h6>
<div class="row">
<div class="col-md-6">
<div class="mb-2">
<strong>Next Maintenance Due:</strong> <span id="nextMaintenanceDate">Not calculated</span>
</div>
</div>
<div class="col-md-6">
<div class="mb-2">
<strong>Days Until Due:</strong> <span id="daysUntilMaintenance">-</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Notes -->
<div class="form-section">
<div class="section-header">
<i class="fas fa-sticky-note me-2"></i>Additional Information
</div>
<div class="section-content">
<div class="form-group mb-3">
<label class="form-label">Notes</label>
{{ form.notes }}
{% if form.notes.errors %}
<div class="validation-feedback invalid">{{ form.notes.errors.0 }}</div>
{% endif %}
<div class="form-text">Additional notes, special instructions, or important information</div>
</div>
</div>
</div>
<!-- Form Actions -->
<div class="d-flex justify-content-between">
<div>
<a href="{% url 'operating_theatre:equipment_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-info me-2" onclick="previewEquipment()">
<i class="fas fa-eye me-1"></i>Preview
</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save me-1"></i>
{% if form.instance.pk %}Update Equipment{% else %}Add Equipment{% endif %}
</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block extra_js %}
<script>
$(document).ready(function() {
// Initialize calculations
calculateCosts();
calculateMaintenanceSchedule();
// Form change handlers
$('#id_purchase_cost, #id_purchase_date, #id_depreciation_rate').on('change', calculateCosts);
$('#id_last_maintenance_date, #id_maintenance_interval').on('change', calculateMaintenanceSchedule);
// Image upload handler
$('#id_image').on('change', function(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
$('#imagePreview').attr('src', e.target.result).show();
$('#uploadPlaceholder').hide();
};
reader.readAsDataURL(file);
}
});
// Drag and drop for image upload
$('.image-upload-area').on('dragover', function(e) {
e.preventDefault();
$(this).addClass('dragover');
});
$('.image-upload-area').on('dragleave', function(e) {
e.preventDefault();
$(this).removeClass('dragover');
});
$('.image-upload-area').on('drop', function(e) {
e.preventDefault();
$(this).removeClass('dragover');
const files = e.originalEvent.dataTransfer.files;
if (files.length > 0) {
$('#id_image')[0].files = files;
$('#id_image').trigger('change');
}
});
});
function generateEquipmentId() {
const category = $('#id_category').val();
const timestamp = Date.now().toString().slice(-6);
const equipmentId = `EQ-${category}-${timestamp}`;
$('#id_equipment_id').val(equipmentId);
}
function generateAssetTag() {
const timestamp = Date.now().toString().slice(-8);
const assetTag = `AT-${timestamp}`;
$('#id_asset_tag').val(assetTag);
}
function addSpecification() {
const container = $('#specificationsContainer');
const specItem = $(`
<div class="spec-item">
<input type="text" class="form-control" placeholder="Specification name" name="spec_name[]">
<input type="text" class="form-control" placeholder="Value" name="spec_value[]">
<button type="button" class="btn btn-outline-danger btn-sm" onclick="removeSpecification(this)">
<i class="fas fa-trash"></i>
</button>
</div>
`);
container.append(specItem);
}
function removeSpecification(button) {
$(button).closest('.spec-item').remove();
}
function calculateCosts() {
const purchaseCost = parseFloat($('#id_purchase_cost').val()) || 0;
const purchaseDate = $('#id_purchase_date').val();
const depreciationRate = parseFloat($('#id_depreciation_rate').val()) || 0;
if (purchaseCost > 0 && purchaseDate && depreciationRate > 0) {
const purchaseDateObj = new Date(purchaseDate);
const currentDate = new Date();
const yearsOld = (currentDate - purchaseDateObj) / (1000 * 60 * 60 * 24 * 365.25);
const annualDepreciation = purchaseCost * (depreciationRate / 100);
const totalDepreciation = annualDepreciation * yearsOld;
const currentValue = Math.max(0, purchaseCost - totalDepreciation);
$('#currentValue').text('$' + currentValue.toFixed(2));
$('#annualDepreciation').text('$' + annualDepreciation.toFixed(2));
$('#equipmentAge').text(yearsOld.toFixed(1) + ' years');
} else {
$('#currentValue').text('$0.00');
$('#annualDepreciation').text('$0.00');
$('#equipmentAge').text('0 years');
}
}
function calculateMaintenanceSchedule() {
const lastMaintenanceDate = $('#id_last_maintenance_date').val();
const maintenanceInterval = parseInt($('#id_maintenance_interval').val()) || 0;
if (lastMaintenanceDate && maintenanceInterval > 0) {
const lastDate = new Date(lastMaintenanceDate);
const nextDate = new Date(lastDate.getTime() + (maintenanceInterval * 24 * 60 * 60 * 1000));
const currentDate = new Date();
const daysUntil = Math.ceil((nextDate - currentDate) / (1000 * 60 * 60 * 24));
$('#nextMaintenanceDate').text(nextDate.toLocaleDateString());
$('#daysUntilMaintenance').text(daysUntil + ' days');
if (daysUntil < 0) {
$('#daysUntilMaintenance').addClass('text-danger').text(Math.abs(daysUntil) + ' days overdue');
} else if (daysUntil < 30) {
$('#daysUntilMaintenance').addClass('text-warning');
} else {
$('#daysUntilMaintenance').addClass('text-success');
}
} else {
$('#nextMaintenanceDate').text('Not calculated');
$('#daysUntilMaintenance').text('-');
}
}
function previewEquipment() {
// Collect form data for preview
const formData = new FormData($('#equipmentForm')[0]);
// Open preview in new window/modal
alert('Preview functionality would show equipment details preview');
}
// Form validation
$('#equipmentForm').on('submit', function(e) {
let isValid = true;
// Check required fields
$(this).find('input[required], select[required]').each(function() {
if (!$(this).val()) {
isValid = false;
$(this).addClass('is-invalid');
} else {
$(this).removeClass('is-invalid');
}
});
if (!isValid) {
e.preventDefault();
alert('Please fill in all required fields.');
return false;
}
// Collect specifications
const specNames = $('input[name="spec_name[]"]').map(function() { return $(this).val(); }).get();
const specValues = $('input[name="spec_value[]"]').map(function() { return $(this).val(); }).get();
const specifications = {};
for (let i = 0; i < specNames.length; i++) {
if (specNames[i] && specValues[i]) {
specifications[specNames[i]] = specValues[i];
}
}
// Add specifications as hidden field
$('<input>').attr({
type: 'hidden',
name: 'specifications',
value: JSON.stringify(specifications)
}).appendTo(this);
});
</script>
{% endblock %}

View File

@ -0,0 +1,583 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}OR Equipment Management{% endblock %}
{% block extra_css %}
<style>
.equipment-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.equipment-card {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
margin-bottom: 1rem;
background: white;
transition: all 0.3s ease;
}
.equipment-card:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.equipment-image {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 0.375rem 0.375rem 0 0;
}
.equipment-content {
padding: 1.5rem;
}
.equipment-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
margin-bottom: 1rem;
}
.info-item {
display: flex;
flex-direction: column;
}
.info-label {
font-size: 0.875rem;
color: #6c757d;
font-weight: 600;
margin-bottom: 0.25rem;
}
.info-value {
color: #495057;
font-weight: 500;
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
margin-right: 0.5rem;
}
.status-available { background-color: #28a745; }
.status-in-use { background-color: #007bff; }
.status-maintenance { background-color: #ffc107; }
.status-out-of-order { background-color: #dc3545; }
.status-reserved { background-color: #6f42c1; }
.maintenance-alert {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 0.25rem;
padding: 0.75rem;
margin-bottom: 1rem;
}
.filter-section {
background: #f8f9fa;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.stat-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1.5rem;
text-align: center;
}
.stat-number {
font-size: 2rem;
font-weight: bold;
color: #007bff;
margin-bottom: 0.5rem;
}
.stat-label {
color: #6c757d;
font-size: 0.875rem;
}
.equipment-actions {
display: flex;
justify-content: between;
gap: 0.5rem;
margin-top: 1rem;
}
@media (max-width: 768px) {
.equipment-header {
padding: 1.5rem;
}
.equipment-info {
grid-template-columns: 1fr;
}
.equipment-actions {
flex-direction: column;
}
}
</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 active">Equipment</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-tools me-2"></i>OR Equipment Management
</h1>
</div>
<div class="ms-auto">
<div class="btn-group">
<a href="{% url 'operating_theatre:equipment_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Add Equipment
</a>
<button class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
<i class="fas fa-download me-1"></i>Export
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="exportEquipment('pdf')">
<i class="fas fa-file-pdf me-2"></i>PDF
</a></li>
<li><a class="dropdown-item" href="#" onclick="exportEquipment('excel')">
<i class="fas fa-file-excel me-2"></i>Excel
</a></li>
</ul>
</div>
</div>
</div>
<!-- Equipment Header -->
<div class="equipment-header">
<div class="row">
<div class="col-md-8">
<h2 class="mb-3">Equipment Inventory</h2>
<div class="row">
<div class="col-md-4">
<div class="mb-2">
<strong>Total Equipment:</strong> {{ stats.total_equipment }}
</div>
<div class="mb-2">
<strong>Available:</strong> {{ stats.available_equipment }}
</div>
</div>
<div class="col-md-4">
<div class="mb-2">
<strong>In Use:</strong> {{ stats.in_use_equipment }}
</div>
<div class="mb-2">
<strong>Maintenance:</strong> {{ stats.maintenance_equipment }}
</div>
</div>
<div class="col-md-4">
<div class="mb-2">
<strong>Out of Order:</strong> {{ stats.out_of_order_equipment }}
</div>
<div class="mb-2">
<strong>Utilization:</strong> {{ stats.utilization_percentage }}%
</div>
</div>
</div>
</div>
<div class="col-md-4 text-end">
<div class="mb-3">
<button class="btn btn-outline-light" onclick="scheduleMaintenanceCheck()">
<i class="fas fa-wrench me-1"></i>Schedule Maintenance
</button>
</div>
<div class="mb-2">
<button class="btn btn-outline-light" onclick="generateQRCodes()">
<i class="fas fa-qrcode me-1"></i>Generate QR Codes
</button>
</div>
</div>
</div>
</div>
<!-- Maintenance Alerts -->
{% if maintenance_alerts %}
<div class="maintenance-alert">
<h6 class="mb-2">
<i class="fas fa-exclamation-triangle me-2"></i>Maintenance Alerts
</h6>
<ul class="mb-0">
{% for alert in maintenance_alerts %}
<li>{{ alert.equipment_name }} - {{ alert.message }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
<!-- Statistics -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-number text-success">{{ stats.available_equipment }}</div>
<div class="stat-label">Available</div>
</div>
<div class="stat-card">
<div class="stat-number text-primary">{{ stats.in_use_equipment }}</div>
<div class="stat-label">In Use</div>
</div>
<div class="stat-card">
<div class="stat-number text-warning">{{ stats.maintenance_equipment }}</div>
<div class="stat-label">Maintenance</div>
</div>
<div class="stat-card">
<div class="stat-number text-danger">{{ stats.out_of_order_equipment }}</div>
<div class="stat-label">Out of Order</div>
</div>
</div>
<!-- Filters -->
<div class="filter-section">
<form method="get" id="filterForm">
<div class="row">
<div class="col-md-3">
<div class="form-group">
<label class="form-label">Category</label>
<select class="form-select" name="category">
<option value="">All Categories</option>
{% for category in equipment_categories %}
<option value="{{ category.0 }}" {% if request.GET.category == category.0 %}selected{% endif %}>
{{ category.1 }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="form-label">Status</label>
<select class="form-select" name="status">
<option value="">All Status</option>
<option value="available" {% if request.GET.status == 'available' %}selected{% endif %}>Available</option>
<option value="in_use" {% if request.GET.status == 'in_use' %}selected{% endif %}>In Use</option>
<option value="maintenance" {% if request.GET.status == 'maintenance' %}selected{% endif %}>Maintenance</option>
<option value="out_of_order" {% if request.GET.status == 'out_of_order' %}selected{% endif %}>Out of Order</option>
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="form-label">Location</label>
<select class="form-select" name="location">
<option value="">All Locations</option>
{% for room in operating_rooms %}
<option value="{{ room.id }}" {% if request.GET.location == room.id|stringformat:"s" %}selected{% endif %}>
{{ room.name }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="form-label">Search</label>
<input type="text" class="form-control" name="search"
value="{{ request.GET.search }}" placeholder="Search equipment...">
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<button type="submit" class="btn btn-primary me-2">
<i class="fas fa-filter me-1"></i>Apply Filters
</button>
<a href="{% url 'operating_theatre:equipment_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-times me-1"></i>Clear
</a>
</div>
</div>
</form>
</div>
<!-- Equipment Grid -->
<div class="row">
{% for equipment in equipment_list %}
<div class="col-lg-4 col-md-6">
<div class="equipment-card">
{% if equipment.image %}
<img src="{{ equipment.image.url }}" alt="{{ equipment.name }}" class="equipment-image">
{% else %}
<div class="equipment-image bg-light d-flex align-items-center justify-content-center">
<i class="fas fa-tools fa-3x text-muted"></i>
</div>
{% endif %}
<div class="equipment-content">
<div class="d-flex justify-content-between align-items-start mb-2">
<h5 class="mb-1">{{ equipment.name }}</h5>
<div>
<span class="status-indicator status-{{ equipment.status }}"></span>
{% if equipment.status == 'available' %}
<span class="badge bg-success">Available</span>
{% elif equipment.status == 'in_use' %}
<span class="badge bg-primary">In Use</span>
{% elif equipment.status == 'maintenance' %}
<span class="badge bg-warning">Maintenance</span>
{% elif equipment.status == 'out_of_order' %}
<span class="badge bg-danger">Out of Order</span>
{% elif equipment.status == 'reserved' %}
<span class="badge bg-info">Reserved</span>
{% endif %}
</div>
</div>
<div class="equipment-info">
<div class="info-item">
<div class="info-label">Serial Number</div>
<div class="info-value">{{ equipment.serial_number }}</div>
</div>
<div class="info-item">
<div class="info-label">Category</div>
<div class="info-value">{{ equipment.get_category_display }}</div>
</div>
<div class="info-item">
<div class="info-label">Manufacturer</div>
<div class="info-value">{{ equipment.manufacturer }}</div>
</div>
<div class="info-item">
<div class="info-label">Model</div>
<div class="info-value">{{ equipment.model }}</div>
</div>
<div class="info-item">
<div class="info-label">Location</div>
<div class="info-value">
{% if equipment.current_location %}
{{ equipment.current_location.name }}
{% else %}
<span class="text-muted">Not assigned</span>
{% endif %}
</div>
</div>
<div class="info-item">
<div class="info-label">Last Maintenance</div>
<div class="info-value">
{% if equipment.last_maintenance_date %}
{{ equipment.last_maintenance_date|date:"M d, Y" }}
{% else %}
<span class="text-muted">Never</span>
{% endif %}
</div>
</div>
</div>
{% if equipment.next_maintenance_due %}
<div class="mb-2">
<small class="text-muted">
<i class="fas fa-calendar-alt me-1"></i>
Next maintenance: {{ equipment.next_maintenance_due|date:"M d, Y" }}
</small>
</div>
{% endif %}
<div class="equipment-actions">
<div class="btn-group flex-fill">
<a href="{% url 'operating_theatre:equipment_detail' equipment.pk %}"
class="btn btn-outline-primary btn-sm">
<i class="fas fa-eye me-1"></i>View
</a>
<a href="{% url 'operating_theatre:equipment_edit' equipment.pk %}"
class="btn btn-outline-secondary btn-sm">
<i class="fas fa-edit me-1"></i>Edit
</a>
</div>
<div class="btn-group">
{% if equipment.status == 'available' %}
<button class="btn btn-success btn-sm" onclick="reserveEquipment('{{ equipment.pk }}')">
<i class="fas fa-lock me-1"></i>Reserve
</button>
{% elif equipment.status == 'reserved' %}
<button class="btn btn-warning btn-sm" onclick="releaseEquipment('{{ equipment.pk }}')">
<i class="fas fa-unlock me-1"></i>Release
</button>
{% endif %}
<div class="dropdown">
<button class="btn btn-outline-secondary btn-sm dropdown-toggle"
data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="#" onclick="scheduleMaintenance('{{ equipment.pk }}')">
<i class="fas fa-wrench me-2"></i>Schedule Maintenance
</a>
</li>
<li>
<a class="dropdown-item" href="#" onclick="generateQR('{{ equipment.pk }}')">
<i class="fas fa-qrcode me-2"></i>Generate QR Code
</a>
</li>
<li>
<a class="dropdown-item" href="#" onclick="viewHistory('{{ equipment.pk }}')">
<i class="fas fa-history me-2"></i>View History
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item text-danger" href="#" onclick="reportIssue('{{ equipment.pk }}')">
<i class="fas fa-exclamation-triangle me-2"></i>Report Issue
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
{% empty %}
<div class="col-12">
<div class="text-center py-5">
<i class="fas fa-tools fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No equipment found</h5>
<p class="text-muted">No equipment matches the selected filters.</p>
<a href="{% url 'operating_theatre:equipment_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Add First Equipment
</a>
</div>
</div>
{% endfor %}
</div>
<!-- Pagination -->
{% if is_paginated %}
<div class="d-flex justify-content-center mt-4">
<nav>
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Previous</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Last</a>
</li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
<script>
function reserveEquipment(equipmentId) {
if (confirm('Reserve this equipment?')) {
$.ajax({
url: `/operating-theatre/equipment/${equipmentId}/reserve/`,
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert('Error reserving equipment: ' + response.error);
}
},
error: function() {
alert('Error reserving equipment');
}
});
}
}
function releaseEquipment(equipmentId) {
if (confirm('Release this equipment reservation?')) {
$.ajax({
url: `/operating-theatre/equipment/${equipmentId}/release/`,
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert('Error releasing equipment: ' + response.error);
}
},
error: function() {
alert('Error releasing equipment');
}
});
}
}
function scheduleMaintenance(equipmentId) {
window.location.href = `/operating-theatre/equipment/${equipmentId}/maintenance/schedule/`;
}
function generateQR(equipmentId) {
window.open(`/operating-theatre/equipment/${equipmentId}/qr-code/`, '_blank');
}
function viewHistory(equipmentId) {
window.location.href = `/operating-theatre/equipment/${equipmentId}/history/`;
}
function reportIssue(equipmentId) {
window.location.href = `/operating-theatre/equipment/${equipmentId}/report-issue/`;
}
function scheduleMaintenanceCheck() {
window.location.href = '/operating-theatre/maintenance/schedule/';
}
function generateQRCodes() {
window.open('/operating-theatre/equipment/qr-codes/bulk/', '_blank');
}
function exportEquipment(format) {
window.location.href = `/operating-theatre/equipment/export/?format=${format}`;
}
// Auto-submit form on filter change
$('#filterForm select').on('change', function() {
$('#filterForm').submit();
});
</script>
{% endblock %}

View File

@ -0,0 +1,608 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Operative Note - {{ note.patient.get_full_name }}{% endblock %}
{% block extra_css %}
<style>
.note-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.section-card {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
margin-bottom: 1.5rem;
background: white;
}
.section-header {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 1rem 1.5rem;
font-weight: 600;
color: #495057;
}
.section-content {
padding: 1.5rem;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
margin-bottom: 1rem;
}
.info-item {
display: flex;
flex-direction: column;
}
.info-label {
font-weight: 600;
color: #6c757d;
font-size: 0.875rem;
margin-bottom: 0.25rem;
}
.info-value {
color: #495057;
font-size: 1rem;
}
.signature-section {
background: #e8f5e8;
border: 2px solid #28a745;
border-radius: 0.375rem;
padding: 1.5rem;
margin-top: 2rem;
}
.signature-pending {
background: #fff3cd;
border: 2px solid #ffc107;
}
.signature-draft {
background: #f8f9fa;
border: 2px solid #6c757d;
}
.timeline-item {
display: flex;
align-items: start;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid #dee2e6;
}
.timeline-icon {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
font-size: 1.2rem;
}
.timeline-content {
flex-grow: 1;
}
.timeline-time {
font-size: 0.875rem;
color: #6c757d;
}
.procedure-details {
background: #f8f9fa;
border-radius: 0.375rem;
padding: 1rem;
margin: 1rem 0;
}
.complication-alert {
background: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 0.375rem;
padding: 1rem;
margin: 1rem 0;
}
.print-section {
display: none;
}
@media print {
.no-print {
display: none !important;
}
.print-section {
display: block !important;
}
.note-header {
background: #f8f9fa !important;
color: #000 !important;
border: 2px solid #000;
}
.section-card {
break-inside: avoid;
margin-bottom: 1rem;
}
}
@media (max-width: 768px) {
.note-header {
padding: 1.5rem;
}
.info-grid {
grid-template-columns: 1fr;
}
.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 no-print">
<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:operative_note_list' %}">Operative Notes</a></li>
<li class="breadcrumb-item active">{{ note.patient.get_full_name }}</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-file-medical me-2"></i>Operative Note
</h1>
</div>
<div class="ms-auto">
<div class="btn-group">
<button class="btn btn-outline-secondary" onclick="window.print()">
<i class="fas fa-print me-1"></i>Print
</button>
<a href="{% url 'operating_theatre:operative_note_pdf' note.pk %}" class="btn btn-outline-primary">
<i class="fas fa-file-pdf me-1"></i>PDF
</a>
{% if note.status == 'draft' or note.status == 'pending' %}
<a href="{% url 'operating_theatre:operative_note_edit' note.pk %}" class="btn btn-primary">
<i class="fas fa-edit me-1"></i>Edit
</a>
{% endif %}
</div>
</div>
</div>
<!-- Print Header -->
<div class="print-section">
<div class="text-center mb-4">
<h2>{{ hospital.name }}</h2>
<p>{{ hospital.address }}</p>
<h3>OPERATIVE NOTE</h3>
</div>
</div>
<!-- Note Header -->
<div class="note-header">
<div class="row">
<div class="col-md-8">
<h2 class="mb-3">{{ note.patient.get_full_name }}</h2>
<div class="row">
<div class="col-md-6">
<div class="mb-2">
<strong>Patient ID:</strong> {{ note.patient.patient_id }}
</div>
<div class="mb-2">
<strong>Date of Birth:</strong> {{ note.patient.date_of_birth|date:"M d, Y" }}
</div>
<div class="mb-2">
<strong>Gender:</strong> {{ note.patient.get_gender_display }}
</div>
</div>
<div class="col-md-6">
<div class="mb-2">
<strong>Surgery Date:</strong> {{ note.surgery_date|date:"M d, Y H:i" }}
</div>
<div class="mb-2">
<strong>Operating Room:</strong> {{ note.operating_room.name }}
</div>
<div class="mb-2">
<strong>Case Number:</strong> {{ note.case_number }}
</div>
</div>
</div>
</div>
<div class="col-md-4 text-end">
<div class="mb-3">
{% if note.status == 'draft' %}
<span class="badge bg-secondary fs-6">Draft</span>
{% elif note.status == 'pending' %}
<span class="badge bg-warning fs-6">Pending Signature</span>
{% elif note.status == 'signed' %}
<span class="badge bg-success fs-6">Signed</span>
{% elif note.status == 'amended' %}
<span class="badge bg-info fs-6">Amended</span>
{% endif %}
</div>
{% if note.urgency == 'emergency' %}
<div class="mb-2">
<span class="badge bg-danger fs-6">Emergency</span>
</div>
{% elif note.urgency == 'urgent' %}
<div class="mb-2">
<span class="badge bg-warning fs-6">Urgent</span>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Procedure Information -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-procedures me-2"></i>Procedure Information
</div>
<div class="section-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Primary Procedure</div>
<div class="info-value">{{ note.procedure_name }}</div>
</div>
<div class="info-item">
<div class="info-label">Procedure Code</div>
<div class="info-value">{{ note.procedure_code }}</div>
</div>
<div class="info-item">
<div class="info-label">Laterality</div>
<div class="info-value">{{ note.get_laterality_display|default:"Not specified" }}</div>
</div>
<div class="info-item">
<div class="info-label">Approach</div>
<div class="info-value">{{ note.get_approach_display|default:"Not specified" }}</div>
</div>
</div>
{% if note.secondary_procedures %}
<div class="procedure-details">
<h6>Secondary Procedures:</h6>
<p>{{ note.secondary_procedures }}</p>
</div>
{% endif %}
</div>
</div>
<!-- Surgical Team -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-users me-2"></i>Surgical Team
</div>
<div class="section-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Primary Surgeon</div>
<div class="info-value">{{ note.primary_surgeon.get_full_name }}</div>
</div>
{% if note.assistant_surgeon %}
<div class="info-item">
<div class="info-label">Assistant Surgeon</div>
<div class="info-value">{{ note.assistant_surgeon.get_full_name }}</div>
</div>
{% endif %}
{% if note.anesthesiologist %}
<div class="info-item">
<div class="info-label">Anesthesiologist</div>
<div class="info-value">{{ note.anesthesiologist.get_full_name }}</div>
</div>
{% endif %}
{% if note.scrub_nurse %}
<div class="info-item">
<div class="info-label">Scrub Nurse</div>
<div class="info-value">{{ note.scrub_nurse.get_full_name }}</div>
</div>
{% endif %}
</div>
{% if note.other_team_members %}
<div class="mt-3">
<h6>Other Team Members:</h6>
<p>{{ note.other_team_members }}</p>
</div>
{% endif %}
</div>
</div>
<!-- Timing Information -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-clock me-2"></i>Timing Information
</div>
<div class="section-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Surgery Start Time</div>
<div class="info-value">{{ note.surgery_start_time|date:"H:i" }}</div>
</div>
{% if note.surgery_end_time %}
<div class="info-item">
<div class="info-label">Surgery End Time</div>
<div class="info-value">{{ note.surgery_end_time|date:"H:i" }}</div>
</div>
<div class="info-item">
<div class="info-label">Total Duration</div>
<div class="info-value">{{ note.surgery_duration }} minutes</div>
</div>
{% endif %}
{% if note.anesthesia_start_time %}
<div class="info-item">
<div class="info-label">Anesthesia Start</div>
<div class="info-value">{{ note.anesthesia_start_time|date:"H:i" }}</div>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Clinical Information -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-stethoscope me-2"></i>Clinical Information
</div>
<div class="section-content">
{% if note.preoperative_diagnosis %}
<div class="mb-3">
<h6>Preoperative Diagnosis:</h6>
<p>{{ note.preoperative_diagnosis }}</p>
</div>
{% endif %}
{% if note.postoperative_diagnosis %}
<div class="mb-3">
<h6>Postoperative Diagnosis:</h6>
<p>{{ note.postoperative_diagnosis }}</p>
</div>
{% endif %}
{% if note.indications %}
<div class="mb-3">
<h6>Indications for Surgery:</h6>
<p>{{ note.indications }}</p>
</div>
{% endif %}
</div>
</div>
<!-- Anesthesia Information -->
{% if note.anesthesia_type %}
<div class="section-card">
<div class="section-header">
<i class="fas fa-syringe me-2"></i>Anesthesia Information
</div>
<div class="section-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Anesthesia Type</div>
<div class="info-value">{{ note.get_anesthesia_type_display }}</div>
</div>
{% if note.asa_classification %}
<div class="info-item">
<div class="info-label">ASA Classification</div>
<div class="info-value">{{ note.asa_classification }}</div>
</div>
{% endif %}
</div>
{% if note.anesthesia_notes %}
<div class="mt-3">
<h6>Anesthesia Notes:</h6>
<p>{{ note.anesthesia_notes }}</p>
</div>
{% endif %}
</div>
</div>
{% endif %}
<!-- Operative Technique -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-cut me-2"></i>Operative Technique
</div>
<div class="section-content">
{% if note.operative_technique %}
<div class="mb-3">
<h6>Technique Description:</h6>
<div style="white-space: pre-wrap;">{{ note.operative_technique }}</div>
</div>
{% endif %}
{% if note.findings %}
<div class="mb-3">
<h6>Operative Findings:</h6>
<div style="white-space: pre-wrap;">{{ note.findings }}</div>
</div>
{% endif %}
{% if note.specimens %}
<div class="mb-3">
<h6>Specimens Sent:</h6>
<p>{{ note.specimens }}</p>
</div>
{% endif %}
</div>
</div>
<!-- Complications -->
{% if note.complications %}
<div class="section-card">
<div class="section-header">
<i class="fas fa-exclamation-triangle me-2"></i>Complications
</div>
<div class="section-content">
<div class="complication-alert">
<h6 class="text-danger">Complications Noted:</h6>
<p class="mb-0">{{ note.complications }}</p>
</div>
</div>
</div>
{% endif %}
<!-- Postoperative Care -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-bed me-2"></i>Postoperative Care
</div>
<div class="section-content">
{% if note.postoperative_instructions %}
<div class="mb-3">
<h6>Postoperative Instructions:</h6>
<div style="white-space: pre-wrap;">{{ note.postoperative_instructions }}</div>
</div>
{% endif %}
{% if note.estimated_blood_loss %}
<div class="info-grid">
<div class="info-item">
<div class="info-label">Estimated Blood Loss</div>
<div class="info-value">{{ note.estimated_blood_loss }} mL</div>
</div>
{% if note.fluid_replacement %}
<div class="info-item">
<div class="info-label">Fluid Replacement</div>
<div class="info-value">{{ note.fluid_replacement }}</div>
</div>
{% endif %}
</div>
{% endif %}
{% if note.disposition %}
<div class="mt-3">
<h6>Patient Disposition:</h6>
<p>{{ note.disposition }}</p>
</div>
{% endif %}
</div>
</div>
<!-- Signature Section -->
<div class="signature-section {% if note.status == 'pending' %}signature-pending{% elif note.status == 'draft' %}signature-draft{% endif %}">
<div class="row">
<div class="col-md-8">
<h5 class="mb-3">
<i class="fas fa-signature me-2"></i>Electronic Signature
</h5>
{% if note.status == 'signed' %}
<div class="mb-2">
<strong>Signed by:</strong> {{ note.primary_surgeon.get_full_name }}
</div>
<div class="mb-2">
<strong>Date & Time:</strong> {{ note.signed_at|date:"M d, Y H:i" }}
</div>
<div class="mb-2">
<strong>Digital Signature:</strong> {{ note.signature_hash }}
</div>
{% elif note.status == 'pending' %}
<p class="mb-2">This operative note is pending electronic signature by the primary surgeon.</p>
{% if note.primary_surgeon == request.user %}
<button class="btn btn-success" onclick="signNote()">
<i class="fas fa-signature me-1"></i>Sign Note
</button>
{% endif %}
{% else %}
<p class="mb-2">This operative note is in draft status and has not been submitted for signature.</p>
{% endif %}
</div>
<div class="col-md-4 text-end">
{% if note.status == 'signed' %}
<div class="text-success">
<i class="fas fa-check-circle fa-3x"></i>
<div class="mt-2">Electronically Signed</div>
</div>
{% elif note.status == 'pending' %}
<div class="text-warning">
<i class="fas fa-clock fa-3x"></i>
<div class="mt-2">Pending Signature</div>
</div>
{% else %}
<div class="text-secondary">
<i class="fas fa-edit fa-3x"></i>
<div class="mt-2">Draft</div>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Amendment History -->
{% if note.amendments.exists %}
<div class="section-card">
<div class="section-header">
<i class="fas fa-history me-2"></i>Amendment History
</div>
<div class="section-content">
{% for amendment in note.amendments.all %}
<div class="timeline-item">
<div class="timeline-icon bg-info text-white">
<i class="fas fa-edit"></i>
</div>
<div class="timeline-content">
<div class="fw-bold">Amendment {{ forloop.counter }}</div>
<div class="timeline-time">{{ amendment.created_at|date:"M d, Y H:i" }} by {{ amendment.created_by.get_full_name }}</div>
<div class="mt-2">
<strong>Reason:</strong> {{ amendment.reason }}
</div>
{% if amendment.changes %}
<div class="mt-1">
<strong>Changes:</strong> {{ amendment.changes }}
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
<script>
function signNote() {
if (confirm('Sign this operative note? Once signed, the note cannot be edited without creating an amendment.')) {
$.ajax({
url: '{% url "operating_theatre:operative_note_sign" note.pk %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert('Error signing note: ' + response.error);
}
},
error: function() {
alert('Error signing note');
}
});
}
}
</script>
{% endblock %}

View File

@ -0,0 +1,465 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Operative Notes{% endblock %}
{% block extra_css %}
<style>
.stats-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 1rem;
border: none;
}
.stats-card .stats-icon {
font-size: 2.5rem;
opacity: 0.8;
}
.stats-card .stats-number {
font-size: 2rem;
font-weight: bold;
margin: 0.5rem 0;
}
.filter-section {
background: #f8f9fa;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.note-card {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1rem;
background: white;
transition: all 0.3s ease;
}
.note-card:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.note-header {
display: flex;
justify-content: between;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid #dee2e6;
}
.procedure-badge {
font-size: 0.875rem;
padding: 0.25rem 0.75rem;
border-radius: 1rem;
}
.urgency-badge {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
}
.note-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1rem;
}
.note-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
margin-right: 0.5rem;
}
.status-draft { background-color: #6c757d; }
.status-pending { background-color: #ffc107; }
.status-signed { background-color: #28a745; }
.status-amended { background-color: #17a2b8; }
@media (max-width: 768px) {
.note-content {
grid-template-columns: 1fr;
gap: 0.5rem;
}
.note-actions {
flex-direction: column;
}
.filter-section {
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 active">Operative Notes</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-file-medical me-2"></i>Operative Notes
</h1>
</div>
<div class="ms-auto">
<a href="{% url 'operating_theatre:operative_note_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>New Note
</a>
</div>
</div>
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-lg-3 col-md-6">
<div class="stats-card">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="stats-number">{{ stats.total_notes }}</div>
<div>Total Notes</div>
</div>
<div class="stats-icon">
<i class="fas fa-file-medical"></i>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="stats-card">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="stats-number">{{ stats.pending_signature }}</div>
<div>Pending Signature</div>
</div>
<div class="stats-icon">
<i class="fas fa-signature"></i>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="stats-card">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="stats-number">{{ stats.today_notes }}</div>
<div>Today's Notes</div>
</div>
<div class="stats-icon">
<i class="fas fa-calendar-day"></i>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="stats-card">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="stats-number">{{ stats.overdue_notes }}</div>
<div>Overdue</div>
</div>
<div class="stats-icon">
<i class="fas fa-exclamation-triangle"></i>
</div>
</div>
</div>
</div>
</div>
<!-- Filters -->
<div class="filter-section">
<form method="get" id="filterForm">
<div class="row">
<div class="col-md-3">
<div class="form-group">
<label class="form-label">Date Range</label>
<select class="form-select" name="date_range">
<option value="">All Dates</option>
<option value="today" {% if request.GET.date_range == 'today' %}selected{% endif %}>Today</option>
<option value="week" {% if request.GET.date_range == 'week' %}selected{% endif %}>This Week</option>
<option value="month" {% if request.GET.date_range == 'month' %}selected{% endif %}>This Month</option>
<option value="custom" {% if request.GET.date_range == 'custom' %}selected{% endif %}>Custom Range</option>
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="form-label">Status</label>
<select class="form-select" name="status">
<option value="">All Status</option>
<option value="draft" {% if request.GET.status == 'draft' %}selected{% endif %}>Draft</option>
<option value="pending" {% if request.GET.status == 'pending' %}selected{% endif %}>Pending Signature</option>
<option value="signed" {% if request.GET.status == 'signed' %}selected{% endif %}>Signed</option>
<option value="amended" {% if request.GET.status == 'amended' %}selected{% endif %}>Amended</option>
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="form-label">Surgeon</label>
<select class="form-select" name="surgeon">
<option value="">All Surgeons</option>
{% for surgeon in surgeons %}
<option value="{{ surgeon.id }}" {% if request.GET.surgeon == surgeon.id|stringformat:"s" %}selected{% endif %}>
{{ surgeon.get_full_name }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="form-label">Search</label>
<div class="input-group">
<input type="text" class="form-control" name="search"
value="{{ request.GET.search }}" placeholder="Patient name, procedure...">
<button class="btn btn-outline-secondary" type="submit">
<i class="fas fa-search"></i>
</button>
</div>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<button type="submit" class="btn btn-primary me-2">
<i class="fas fa-filter me-1"></i>Apply Filters
</button>
<a href="{% url 'operating_theatre:operative_note_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-times me-1"></i>Clear
</a>
</div>
</div>
</form>
</div>
<!-- Notes List -->
<div class="row">
{% for note in notes %}
<div class="col-12">
<div class="note-card">
<div class="note-header">
<div class="d-flex align-items-center">
<span class="status-indicator status-{{ note.status }}"></span>
<h5 class="mb-0 me-3">{{ note.patient.get_full_name }}</h5>
<span class="procedure-badge badge bg-primary">{{ note.procedure_name }}</span>
{% if note.urgency == 'emergency' %}
<span class="urgency-badge badge bg-danger ms-2">Emergency</span>
{% elif note.urgency == 'urgent' %}
<span class="urgency-badge badge bg-warning ms-2">Urgent</span>
{% endif %}
</div>
<div class="text-muted">
<small>{{ note.surgery_date|date:"M d, Y H:i" }}</small>
</div>
</div>
<div class="note-content">
<div>
<div class="mb-2">
<strong>Patient ID:</strong> {{ note.patient.patient_id }}
</div>
<div class="mb-2">
<strong>Surgeon:</strong> {{ note.primary_surgeon.get_full_name }}
</div>
<div class="mb-2">
<strong>Operating Room:</strong> {{ note.operating_room.name }}
</div>
<div class="mb-2">
<strong>Duration:</strong>
{% if note.surgery_end_time %}
{{ note.surgery_duration }} minutes
{% else %}
In Progress
{% endif %}
</div>
</div>
<div>
<div class="mb-2">
<strong>Status:</strong>
{% if note.status == 'draft' %}
<span class="badge bg-secondary">Draft</span>
{% elif note.status == 'pending' %}
<span class="badge bg-warning">Pending Signature</span>
{% elif note.status == 'signed' %}
<span class="badge bg-success">Signed</span>
{% elif note.status == 'amended' %}
<span class="badge bg-info">Amended</span>
{% endif %}
</div>
<div class="mb-2">
<strong>Created:</strong> {{ note.created_at|date:"M d, Y H:i" }}
</div>
<div class="mb-2">
<strong>Last Updated:</strong> {{ note.updated_at|date:"M d, Y H:i" }}
</div>
{% if note.signed_at %}
<div class="mb-2">
<strong>Signed:</strong> {{ note.signed_at|date:"M d, Y H:i" }}
</div>
{% endif %}
</div>
</div>
<div class="note-actions">
<a href="{% url 'operating_theatre:operative_note_detail' note.pk %}"
class="btn btn-outline-primary btn-sm">
<i class="fas fa-eye me-1"></i>View
</a>
{% if note.status == 'draft' or note.status == 'pending' %}
<a href="{% url 'operating_theatre:operative_note_edit' note.pk %}"
class="btn btn-outline-secondary btn-sm">
<i class="fas fa-edit me-1"></i>Edit
</a>
{% endif %}
{% if note.status == 'pending' and note.primary_surgeon == request.user %}
<button class="btn btn-success btn-sm" onclick="signNote('{{ note.pk }}')">
<i class="fas fa-signature me-1"></i>Sign
</button>
{% endif %}
<div class="dropdown">
<button class="btn btn-outline-secondary btn-sm dropdown-toggle"
type="button" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="{% url 'operating_theatre:operative_note_print' note.pk %}">
<i class="fas fa-print me-2"></i>Print
</a>
</li>
<li>
<a class="dropdown-item" href="{% url 'operating_theatre:operative_note_pdf' note.pk %}">
<i class="fas fa-file-pdf me-2"></i>Download PDF
</a>
</li>
{% if note.status == 'signed' %}
<li>
<a class="dropdown-item" href="#" onclick="amendNote('{{ note.pk }}')">
<i class="fas fa-edit me-2"></i>Amend Note
</a>
</li>
{% endif %}
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item text-danger" href="#" onclick="deleteNote('{{ note.pk }}')">
<i class="fas fa-trash me-2"></i>Delete
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
{% empty %}
<div class="col-12">
<div class="text-center py-5">
<i class="fas fa-file-medical fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No operative notes found</h5>
<p class="text-muted">No notes match your current filters.</p>
<a href="{% url 'operating_theatre:operative_note_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Create First Note
</a>
</div>
</div>
{% endfor %}
</div>
<!-- Pagination -->
{% if is_paginated %}
<div class="d-flex justify-content-center mt-4">
<nav>
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Previous</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Last</a>
</li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
<script>
function signNote(noteId) {
if (confirm('Sign this operative note? Once signed, the note cannot be edited without creating an amendment.')) {
$.ajax({
url: `/operating-theatre/notes/${noteId}/sign/`,
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
location.reload();
} else {
alert('Error signing note: ' + response.error);
}
},
error: function() {
alert('Error signing note');
}
});
}
}
function amendNote(noteId) {
const reason = prompt('Enter reason for amendment:');
if (reason) {
window.location.href = `/operating-theatre/notes/${noteId}/amend/?reason=${encodeURIComponent(reason)}`;
}
}
function deleteNote(noteId) {
if (confirm('Delete this operative note? This action cannot be undone.')) {
window.location.href = `/operating-theatre/notes/${noteId}/delete/`;
}
}
// Auto-submit form on filter change
$('#filterForm select').on('change', function() {
$('#filterForm').submit();
});
</script>
{% endblock %}

View File

@ -0,0 +1,596 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Delete Surgical Note - {{ note.patient.get_full_name }}{% endblock %}
{% block extra_css %}
<style>
.delete-header {
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.warning-section {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 2rem;
}
.note-details {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
margin-bottom: 2rem;
}
.detail-header {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 1rem 1.5rem;
font-weight: 600;
color: #495057;
}
.detail-content {
padding: 1.5rem;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 1.5rem;
}
.info-item {
display: flex;
flex-direction: column;
}
.info-label {
font-size: 0.875rem;
color: #6c757d;
font-weight: 600;
margin-bottom: 0.25rem;
}
.info-value {
color: #495057;
font-weight: 500;
}
.note-status {
padding: 0.5rem 1rem;
border-radius: 0.25rem;
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
display: inline-block;
}
.status-draft { background: #f8f9fa; color: #6c757d; }
.status-in-progress { background: #fff3cd; color: #856404; }
.status-completed { background: #d4edda; color: #155724; }
.status-signed { background: #d1ecf1; color: #0c5460; }
.status-amended { background: #f8d7da; color: #721c24; }
.priority-badge {
padding: 0.25rem 0.75rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
display: inline-block;
}
.priority-low { background: #d4edda; color: #155724; }
.priority-medium { background: #fff3cd; color: #856404; }
.priority-high { background: #f8d7da; color: #721c24; }
.priority-critical { background: #f5c6cb; color: #721c24; }
.consequences-list {
background: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 2rem;
}
.consequences-list h6 {
color: #721c24;
margin-bottom: 1rem;
}
.consequences-list ul {
color: #721c24;
margin-bottom: 0;
}
.confirmation-section {
background: #fff;
border: 2px solid #dc3545;
border-radius: 0.375rem;
padding: 2rem;
margin-bottom: 2rem;
}
.confirmation-checkbox {
margin-bottom: 1rem;
}
.confirmation-checkbox .form-check-input:checked {
background-color: #dc3545;
border-color: #dc3545;
}
.delete-reason {
margin-bottom: 1.5rem;
}
.action-buttons {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 2rem;
border-top: 1px solid #dee2e6;
}
.btn-delete {
background: #dc3545;
border-color: #dc3545;
color: white;
}
.btn-delete:hover {
background: #c82333;
border-color: #bd2130;
color: white;
}
.btn-delete:disabled {
background: #6c757d;
border-color: #6c757d;
cursor: not-allowed;
}
@media (max-width: 768px) {
.delete-header {
padding: 1.5rem;
}
.detail-content {
padding: 1rem;
}
.info-grid {
grid-template-columns: 1fr;
}
.action-buttons {
flex-direction: column;
gap: 1rem;
}
.action-buttons .btn {
width: 100%;
}
}
@media print {
.action-buttons, .confirmation-section {
display: none !important;
}
}
</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:surgical_note_list' %}">Surgical Notes</a></li>
<li class="breadcrumb-item"><a href="{% url 'operating_theatre:surgical_note_detail' note.pk %}">{{ note.patient.get_full_name }}</a></li>
<li class="breadcrumb-item active">Delete</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-trash-alt me-2"></i>Delete Surgical Note
</h1>
</div>
<div class="ms-auto">
<a href="{% url 'operating_theatre:surgical_note_detail' note.pk %}" class="btn btn-outline-primary">
<i class="fas fa-arrow-left me-1"></i>Back to Note
</a>
</div>
</div>
<!-- Delete Warning Header -->
<div class="delete-header text-center">
<div class="mb-3">
<i class="fas fa-exclamation-triangle fa-4x"></i>
</div>
<h2 class="mb-2">Confirm Deletion</h2>
<p class="mb-0 fs-5">You are about to permanently delete this surgical note</p>
</div>
<!-- Warning Section -->
<div class="warning-section">
<div class="d-flex align-items-center mb-3">
<i class="fas fa-exclamation-triangle fa-2x text-warning me-3"></i>
<div>
<h5 class="mb-1 text-warning">Warning: This action cannot be undone</h5>
<p class="mb-0">Deleting this surgical note will permanently remove all associated data from the system.</p>
</div>
</div>
</div>
<!-- Note Details -->
<div class="note-details">
<div class="detail-header">
<i class="fas fa-file-medical me-2"></i>Surgical Note Details
</div>
<div class="detail-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Patient</div>
<div class="info-value">
<strong>{{ note.patient.get_full_name }}</strong><br>
<small class="text-muted">ID: {{ note.patient.patient_id }}</small>
</div>
</div>
<div class="info-item">
<div class="info-label">Procedure</div>
<div class="info-value">
<strong>{{ note.procedure_name }}</strong><br>
{% if note.procedure_code %}
<small class="text-muted">Code: {{ note.procedure_code }}</small>
{% endif %}
</div>
</div>
<div class="info-item">
<div class="info-label">Surgeon</div>
<div class="info-value">
<strong>{{ note.surgeon.get_full_name }}</strong><br>
{% if note.surgeon.specialization %}
<small class="text-muted">{{ note.surgeon.specialization }}</small>
{% endif %}
</div>
</div>
<div class="info-item">
<div class="info-label">Surgery Date</div>
<div class="info-value">
<strong>{{ note.surgery_date|date:"F d, Y" }}</strong><br>
<small class="text-muted">{{ note.surgery_date|time:"g:i A" }}</small>
</div>
</div>
<div class="info-item">
<div class="info-label">Status</div>
<div class="info-value">
<span class="note-status status-{{ note.status }}">
{{ note.get_status_display }}
</span>
</div>
</div>
<div class="info-item">
<div class="info-label">Priority</div>
<div class="info-value">
<span class="priority-badge priority-{{ note.priority }}">
{{ note.get_priority_display }}
</span>
</div>
</div>
<div class="info-item">
<div class="info-label">Created</div>
<div class="info-value">
<strong>{{ note.created_at|date:"M d, Y" }}</strong><br>
<small class="text-muted">{{ note.created_at|time:"g:i A" }}</small>
</div>
</div>
<div class="info-item">
<div class="info-label">Last Modified</div>
<div class="info-value">
<strong>{{ note.updated_at|date:"M d, Y" }}</strong><br>
<small class="text-muted">{{ note.updated_at|time:"g:i A" }}</small>
</div>
</div>
</div>
{% if note.preoperative_diagnosis %}
<div class="row">
<div class="col-md-6">
<div class="info-item">
<div class="info-label">Pre-operative Diagnosis</div>
<div class="info-value">{{ note.preoperative_diagnosis|truncatewords:10 }}</div>
</div>
</div>
{% if note.postoperative_diagnosis %}
<div class="col-md-6">
<div class="info-item">
<div class="info-label">Post-operative Diagnosis</div>
<div class="info-value">{{ note.postoperative_diagnosis|truncatewords:10 }}</div>
</div>
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>
<!-- Consequences -->
<div class="consequences-list">
<h6><i class="fas fa-exclamation-circle me-2"></i>Consequences of Deletion</h6>
<ul>
<li><strong>Permanent Data Loss:</strong> All surgical note content will be permanently deleted</li>
<li><strong>Audit Trail:</strong> The deletion will be logged for compliance purposes</li>
<li><strong>Patient Records:</strong> This note will no longer appear in the patient's medical history</li>
<li><strong>Reporting:</strong> This note will be excluded from all future reports and statistics</li>
{% if note.status == 'signed' %}
<li><strong>Legal Implications:</strong> Deleting a signed surgical note may have legal consequences</li>
{% endif %}
{% if note.is_emergency %}
<li><strong>Emergency Record:</strong> This emergency surgical record will be permanently lost</li>
{% endif %}
</ul>
</div>
<!-- Special Warnings -->
{% if note.status == 'signed' %}
<div class="alert alert-danger">
<h6><i class="fas fa-signature me-2"></i>Signed Document Warning</h6>
<p class="mb-0">This surgical note has been electronically signed. Deleting signed medical documents may violate regulatory requirements and institutional policies. Please consult with your compliance officer before proceeding.</p>
</div>
{% endif %}
{% if note.is_emergency %}
<div class="alert alert-warning">
<h6><i class="fas fa-ambulance me-2"></i>Emergency Surgery Record</h6>
<p class="mb-0">This is an emergency surgical note. Emergency records are often subject to additional regulatory scrutiny and may be required for quality assurance reviews.</p>
</div>
{% endif %}
<!-- Confirmation Form -->
<form method="post" id="delete-form">
{% csrf_token %}
<div class="confirmation-section">
<h5 class="mb-3"><i class="fas fa-check-square me-2"></i>Confirmation Required</h5>
<div class="confirmation-checkbox">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="confirm-understanding" required>
<label class="form-check-label" for="confirm-understanding">
I understand that this action will permanently delete the surgical note and cannot be undone
</label>
</div>
</div>
<div class="confirmation-checkbox">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="confirm-authority" required>
<label class="form-check-label" for="confirm-authority">
I have the authority to delete this surgical note
</label>
</div>
</div>
{% if note.status == 'signed' %}
<div class="confirmation-checkbox">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="confirm-signed" required>
<label class="form-check-label" for="confirm-signed">
I acknowledge the legal implications of deleting a signed medical document
</label>
</div>
</div>
{% endif %}
<div class="delete-reason">
<label for="deletion-reason" class="form-label">
<i class="fas fa-comment me-1"></i>Reason for Deletion <span class="text-danger">*</span>
</label>
<textarea class="form-control" id="deletion-reason" name="deletion_reason" rows="3"
placeholder="Please provide a detailed reason for deleting this surgical note..." required></textarea>
<div class="form-text">This reason will be logged for audit purposes</div>
</div>
<div class="mb-3">
<label for="confirmation-text" class="form-label">
Type "DELETE" to confirm <span class="text-danger">*</span>
</label>
<input type="text" class="form-control" id="confirmation-text"
placeholder="Type DELETE in capital letters" required>
<div class="form-text">This additional confirmation helps prevent accidental deletions</div>
</div>
</div>
<!-- Action Buttons -->
<div class="action-buttons">
<div>
<a href="{% url 'operating_theatre:surgical_note_detail' note.pk %}" class="btn btn-outline-secondary btn-lg">
<i class="fas fa-times me-2"></i>Cancel
</a>
<a href="{% url 'operating_theatre:surgical_note_list' %}" class="btn btn-outline-primary btn-lg ms-2">
<i class="fas fa-list me-2"></i>Back to List
</a>
</div>
<div>
<button type="button" class="btn btn-outline-info btn-lg me-2" onclick="printRecord()">
<i class="fas fa-print me-2"></i>Print Record First
</button>
<button type="submit" class="btn btn-delete btn-lg" id="delete-button" disabled>
<i class="fas fa-trash-alt me-2"></i>Delete Surgical Note
</button>
</div>
</div>
</form>
</div>
<!-- Confirmation Modal -->
<div class="modal fade" id="finalConfirmationModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title">
<i class="fas fa-exclamation-triangle me-2"></i>Final Confirmation
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body text-center">
<div class="mb-3">
<i class="fas fa-trash-alt fa-4x text-danger"></i>
</div>
<h5>Are you absolutely sure?</h5>
<p class="text-muted">This surgical note will be permanently deleted. This action cannot be undone.</p>
<div class="alert alert-danger">
<strong>Patient:</strong> {{ note.patient.get_full_name }}<br>
<strong>Procedure:</strong> {{ note.procedure_name }}<br>
<strong>Date:</strong> {{ note.surgery_date|date:"F d, Y" }}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i>Cancel
</button>
<button type="button" class="btn btn-danger" onclick="confirmDelete()">
<i class="fas fa-trash-alt me-1"></i>Yes, Delete Permanently
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
$(document).ready(function() {
// Enable/disable delete button based on confirmations
function checkConfirmations() {
const checkboxes = document.querySelectorAll('.confirmation-checkbox input[type="checkbox"]');
const confirmationText = document.getElementById('confirmation-text').value.trim();
const deletionReason = document.getElementById('deletion-reason').value.trim();
const deleteButton = document.getElementById('delete-button');
let allChecked = true;
checkboxes.forEach(checkbox => {
if (!checkbox.checked) {
allChecked = false;
}
});
const isValid = allChecked &&
confirmationText === 'DELETE' &&
deletionReason.length >= 10;
deleteButton.disabled = !isValid;
if (isValid) {
deleteButton.classList.remove('btn-secondary');
deleteButton.classList.add('btn-delete');
} else {
deleteButton.classList.remove('btn-delete');
deleteButton.classList.add('btn-secondary');
}
}
// Attach event listeners
document.querySelectorAll('.confirmation-checkbox input[type="checkbox"]').forEach(checkbox => {
checkbox.addEventListener('change', checkConfirmations);
});
document.getElementById('confirmation-text').addEventListener('input', checkConfirmations);
document.getElementById('deletion-reason').addEventListener('input', checkConfirmations);
// Form submission
document.getElementById('delete-form').addEventListener('submit', function(e) {
e.preventDefault();
// Show final confirmation modal
const modal = new bootstrap.Modal(document.getElementById('finalConfirmationModal'));
modal.show();
});
// Initial check
checkConfirmations();
});
function confirmDelete() {
// Close modal
const modal = bootstrap.Modal.getInstance(document.getElementById('finalConfirmationModal'));
modal.hide();
// Show loading state
const deleteButton = document.getElementById('delete-button');
const originalText = deleteButton.innerHTML;
deleteButton.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Deleting...';
deleteButton.disabled = true;
// Submit form
setTimeout(() => {
document.getElementById('delete-form').submit();
}, 1000);
}
function printRecord() {
// Open the surgical note detail page in a new window for printing
const printUrl = "{% url 'operating_theatre:surgical_note_detail' note.pk %}?print=1";
const printWindow = window.open(printUrl, '_blank');
printWindow.onload = function() {
setTimeout(() => {
printWindow.print();
}, 1000);
};
}
// Prevent accidental page navigation
window.addEventListener('beforeunload', function(e) {
const form = document.getElementById('delete-form');
const formData = new FormData(form);
// Check if user has started filling the form
if (formData.get('deletion_reason') ||
document.getElementById('confirmation-text').value ||
document.querySelector('.confirmation-checkbox input:checked')) {
e.preventDefault();
e.returnValue = 'You have unsaved changes. Are you sure you want to leave?';
return e.returnValue;
}
});
// Auto-save draft reason (for audit purposes)
let autoSaveTimer;
document.getElementById('deletion-reason').addEventListener('input', function() {
clearTimeout(autoSaveTimer);
autoSaveTimer = setTimeout(() => {
// Save draft reason to session storage
sessionStorage.setItem('deletion_reason_draft', this.value);
}, 2000);
});
// Restore draft reason on page load
window.addEventListener('load', function() {
const draftReason = sessionStorage.getItem('deletion_reason_draft');
if (draftReason) {
document.getElementById('deletion-reason').value = draftReason;
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,649 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Surgical Note - {{ note.patient.get_full_name }}{% endblock %}
{% block extra_css %}
<style>
.note-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.note-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;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 1.5rem;
}
.info-item {
display: flex;
flex-direction: column;
}
.info-label {
font-size: 0.875rem;
color: #6c757d;
font-weight: 600;
margin-bottom: 0.25rem;
}
.info-value {
color: #495057;
font-weight: 500;
}
.note-status {
padding: 0.5rem 1rem;
border-radius: 0.25rem;
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
display: inline-block;
}
.status-draft { background: #f8f9fa; color: #6c757d; }
.status-in-progress { background: #fff3cd; color: #856404; }
.status-completed { background: #d4edda; color: #155724; }
.status-signed { background: #d1ecf1; color: #0c5460; }
.status-amended { background: #f8d7da; color: #721c24; }
.priority-badge {
padding: 0.25rem 0.75rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
display: inline-block;
}
.priority-low { background: #d4edda; color: #155724; }
.priority-medium { background: #fff3cd; color: #856404; }
.priority-high { background: #f8d7da; color: #721c24; }
.priority-critical { background: #f5c6cb; color: #721c24; }
.signature-section {
background: #f8f9fa;
border: 2px dashed #dee2e6;
border-radius: 0.375rem;
padding: 2rem;
text-align: center;
margin-top: 2rem;
}
.signature-box {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
padding: 1rem;
margin: 1rem 0;
min-height: 100px;
display: flex;
align-items: center;
justify-content: center;
}
.timeline {
position: relative;
padding-left: 2rem;
}
.timeline-item {
position: relative;
padding-bottom: 1.5rem;
}
.timeline-item:before {
content: '';
position: absolute;
left: -2rem;
top: 0;
width: 2px;
height: 100%;
background: #dee2e6;
}
.timeline-item:last-child:before {
display: none;
}
.timeline-marker {
position: absolute;
left: -2.5rem;
top: 0.25rem;
width: 1rem;
height: 1rem;
border-radius: 50%;
background: #007bff;
border: 2px solid white;
box-shadow: 0 0 0 2px #dee2e6;
}
.timeline-content {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
padding: 1rem;
}
.print-section {
background: #e7f3ff;
border: 1px solid #b3d9ff;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1.5rem;
}
@media print {
.no-print {
display: none !important;
}
.note-header {
background: #f8f9fa !important;
color: #495057 !important;
border: 1px solid #dee2e6 !important;
}
.section-header {
background: #f8f9fa !important;
}
}
@media (max-width: 768px) {
.note-header {
padding: 1.5rem;
}
.info-grid {
grid-template-columns: 1fr;
}
.timeline {
padding-left: 1rem;
}
.timeline-item:before {
left: -1rem;
}
.timeline-marker {
left: -1.5rem;
}
}
</style>
{% endblock %}
{% block content %}
<div id="content" class="app-content">
<!-- Page Header -->
<div class="d-flex align-items-center mb-3 no-print">
<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:surgical_note_list' %}">Surgical Notes</a></li>
<li class="breadcrumb-item active">{{ note.patient.get_full_name }}</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-file-medical me-2"></i>Surgical Note Details
</h1>
</div>
<div class="ms-auto">
<div class="btn-group">
<a href="{% url 'operating_theatre:surgical_note_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>Back to List
</a>
{% if note.status != 'signed' %}
<a href="{% url 'operating_theatre:surgical_note_edit' note.pk %}" class="btn btn-warning">
<i class="fas fa-edit me-1"></i>Edit
</a>
{% endif %}
<button class="btn btn-success" onclick="printNote()">
<i class="fas fa-print me-1"></i>Print
</button>
<div class="btn-group">
<button class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown">
<i class="fas fa-cog me-1"></i>Actions
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="exportNote('pdf')">
<i class="fas fa-file-pdf me-2"></i>Export as PDF
</a></li>
<li><a class="dropdown-item" href="#" onclick="exportNote('word')">
<i class="fas fa-file-word me-2"></i>Export as Word
</a></li>
<li><hr class="dropdown-divider"></li>
{% if note.status == 'completed' and not note.is_signed %}
<li><a class="dropdown-item" href="#" onclick="signNote()">
<i class="fas fa-signature me-2"></i>Sign Note
</a></li>
{% endif %}
{% if note.status == 'signed' %}
<li><a class="dropdown-item" href="#" onclick="amendNote()">
<i class="fas fa-edit me-2"></i>Create Amendment
</a></li>
{% endif %}
<li><a class="dropdown-item" href="#" onclick="duplicateNote()">
<i class="fas fa-copy me-2"></i>Duplicate Note
</a></li>
</ul>
</div>
</div>
</div>
</div>
<!-- Note Header -->
<div class="note-header">
<div class="row">
<div class="col-md-8">
<h2 class="mb-3">{{ note.procedure_name }}</h2>
<div class="row">
<div class="col-md-6">
<p class="mb-1"><strong>Patient:</strong> {{ note.patient.get_full_name }}</p>
<p class="mb-1"><strong>Patient ID:</strong> {{ note.patient.patient_id }}</p>
<p class="mb-1"><strong>Date of Birth:</strong> {{ note.patient.date_of_birth|date:"M d, Y" }}</p>
</div>
<div class="col-md-6">
<p class="mb-1"><strong>Surgery Date:</strong> {{ note.surgery_date|date:"M d, Y" }}</p>
<p class="mb-1"><strong>Surgeon:</strong> {{ note.surgeon.get_full_name }}</p>
<p class="mb-1"><strong>Operating Room:</strong> {{ note.operating_room.name }}</p>
</div>
</div>
</div>
<div class="col-md-4 text-end">
<div class="mb-3">
<span class="note-status status-{{ note.status }}">
{{ note.get_status_display }}
</span>
</div>
<div class="mb-2">
<span class="priority-badge priority-{{ note.priority }}">
{{ note.get_priority_display }} Priority
</span>
</div>
<p class="mb-0"><small>Note ID: {{ note.id }}</small></p>
</div>
</div>
</div>
<!-- Pre-operative Information -->
<div class="note-section">
<div class="section-header">
<i class="fas fa-clipboard-list me-2"></i>Pre-operative Information
</div>
<div class="section-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Pre-operative Diagnosis</div>
<div class="info-value">{{ note.preoperative_diagnosis|default:"Not specified" }}</div>
</div>
<div class="info-item">
<div class="info-label">Planned Procedure</div>
<div class="info-value">{{ note.planned_procedure|default:"Not specified" }}</div>
</div>
<div class="info-item">
<div class="info-label">Anesthesia Type</div>
<div class="info-value">{{ note.get_anesthesia_type_display|default:"Not specified" }}</div>
</div>
<div class="info-item">
<div class="info-label">ASA Classification</div>
<div class="info-value">{{ note.asa_classification|default:"Not specified" }}</div>
</div>
</div>
{% if note.preoperative_notes %}
<div class="mt-3">
<div class="info-label">Pre-operative Notes</div>
<div class="info-value">{{ note.preoperative_notes|linebreaks }}</div>
</div>
{% endif %}
</div>
</div>
<!-- Operative Procedure -->
<div class="note-section">
<div class="section-header">
<i class="fas fa-procedures me-2"></i>Operative Procedure
</div>
<div class="section-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Actual Procedure</div>
<div class="info-value">{{ note.actual_procedure|default:"Not specified" }}</div>
</div>
<div class="info-item">
<div class="info-label">Procedure Duration</div>
<div class="info-value">{{ note.procedure_duration|default:"Not specified" }}</div>
</div>
<div class="info-item">
<div class="info-label">Incision Type</div>
<div class="info-value">{{ note.incision_type|default:"Not specified" }}</div>
</div>
<div class="info-item">
<div class="info-label">Closure Method</div>
<div class="info-value">{{ note.closure_method|default:"Not specified" }}</div>
</div>
</div>
{% if note.operative_findings %}
<div class="mt-3">
<div class="info-label">Operative Findings</div>
<div class="info-value">{{ note.operative_findings|linebreaks }}</div>
</div>
{% endif %}
{% if note.procedure_description %}
<div class="mt-3">
<div class="info-label">Detailed Procedure Description</div>
<div class="info-value">{{ note.procedure_description|linebreaks }}</div>
</div>
{% endif %}
</div>
</div>
<!-- Post-operative Information -->
<div class="note-section">
<div class="section-header">
<i class="fas fa-heartbeat me-2"></i>Post-operative Information
</div>
<div class="section-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Post-operative Diagnosis</div>
<div class="info-value">{{ note.postoperative_diagnosis|default:"Not specified" }}</div>
</div>
<div class="info-item">
<div class="info-label">Estimated Blood Loss</div>
<div class="info-value">{{ note.estimated_blood_loss|default:"Not specified" }}</div>
</div>
<div class="info-item">
<div class="info-label">Complications</div>
<div class="info-value">{{ note.complications|default:"None reported" }}</div>
</div>
<div class="info-item">
<div class="info-label">Condition at End</div>
<div class="info-value">{{ note.condition_at_end|default:"Not specified" }}</div>
</div>
</div>
{% if note.postoperative_instructions %}
<div class="mt-3">
<div class="info-label">Post-operative Instructions</div>
<div class="info-value">{{ note.postoperative_instructions|linebreaks }}</div>
</div>
{% endif %}
</div>
</div>
<!-- Surgical Team -->
<div class="note-section">
<div class="section-header">
<i class="fas fa-users me-2"></i>Surgical Team
</div>
<div class="section-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Primary Surgeon</div>
<div class="info-value">{{ note.surgeon.get_full_name }}</div>
</div>
{% if note.assistant_surgeon %}
<div class="info-item">
<div class="info-label">Assistant Surgeon</div>
<div class="info-value">{{ note.assistant_surgeon.get_full_name }}</div>
</div>
{% endif %}
{% if note.anesthesiologist %}
<div class="info-item">
<div class="info-label">Anesthesiologist</div>
<div class="info-value">{{ note.anesthesiologist.get_full_name }}</div>
</div>
{% endif %}
{% if note.scrub_nurse %}
<div class="info-item">
<div class="info-label">Scrub Nurse</div>
<div class="info-value">{{ note.scrub_nurse.get_full_name }}</div>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Revision History -->
{% if note.revisions.exists %}
<div class="note-section">
<div class="section-header">
<i class="fas fa-history me-2"></i>Revision History
</div>
<div class="section-content">
<div class="timeline">
{% for revision in note.revisions.all %}
<div class="timeline-item">
<div class="timeline-marker"></div>
<div class="timeline-content">
<div class="d-flex justify-content-between align-items-start mb-2">
<h6 class="mb-0">{{ revision.get_action_display }}</h6>
<small class="text-muted">{{ revision.created_at|date:"M d, Y g:i A" }}</small>
</div>
<p class="mb-1"><strong>By:</strong> {{ revision.created_by.get_full_name }}</p>
{% if revision.reason %}
<p class="mb-0"><strong>Reason:</strong> {{ revision.reason }}</p>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<!-- Electronic Signature -->
{% if note.status == 'signed' %}
<div class="note-section">
<div class="section-header">
<i class="fas fa-signature me-2"></i>Electronic Signature
</div>
<div class="section-content">
<div class="signature-box">
<div class="text-center">
<i class="fas fa-certificate fa-3x text-success mb-3"></i>
<h5 class="text-success">Electronically Signed</h5>
<p class="mb-1"><strong>Signed by:</strong> {{ note.signed_by.get_full_name }}</p>
<p class="mb-1"><strong>Date:</strong> {{ note.signed_at|date:"M d, Y g:i A" }}</p>
<p class="mb-0"><strong>IP Address:</strong> {{ note.signature_ip|default:"Not recorded" }}</p>
</div>
</div>
</div>
</div>
{% elif note.status == 'completed' %}
<div class="signature-section no-print">
<h5 class="mb-3">
<i class="fas fa-signature me-2"></i>Electronic Signature Required
</h5>
<p class="text-muted mb-3">This note is complete and ready for electronic signature.</p>
<button class="btn btn-primary btn-lg" onclick="signNote()">
<i class="fas fa-signature me-2"></i>Sign Note
</button>
</div>
{% endif %}
</div>
<!-- Sign Note Modal -->
<div class="modal fade" id="signNoteModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-signature me-2"></i>Electronic Signature
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
By signing this note, you confirm that all information is accurate and complete.
</div>
<form id="signatureForm">
{% csrf_token %}
<div class="mb-3">
<label class="form-label">Password Confirmation</label>
<input type="password" class="form-control" name="password" required
placeholder="Enter your password to confirm signature">
</div>
<div class="mb-3">
<label class="form-label">Signature Comments (Optional)</label>
<textarea class="form-control" name="signature_comments" rows="3"
placeholder="Any additional comments about this signature..."></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="submitSignature()">
<i class="fas fa-signature me-1"></i>Sign Note
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
function printNote() {
window.print();
}
function exportNote(format) {
const url = `{% url "operating_theatre:surgical_note_export" note.pk %}?format=${format}`;
window.open(url, '_blank');
}
function signNote() {
$('#signNoteModal').modal('show');
}
function submitSignature() {
const form = document.getElementById('signatureForm');
const formData = new FormData(form);
$.ajax({
url: '{% url "operating_theatre:surgical_note_sign" note.pk %}',
method: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
if (response.success) {
$('#signNoteModal').modal('hide');
alert('Note signed successfully!');
location.reload();
} else {
alert('Error signing note: ' + response.error);
}
},
error: function() {
alert('Error signing note');
}
});
}
function amendNote() {
const reason = prompt('Please provide a reason for the amendment:');
if (reason) {
$.ajax({
url: '{% url "operating_theatre:surgical_note_amend" note.pk %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'reason': reason
},
success: function(response) {
if (response.success) {
alert('Amendment created successfully!');
window.location.href = response.amendment_url;
} else {
alert('Error creating amendment: ' + response.error);
}
},
error: function() {
alert('Error creating amendment');
}
});
}
}
function duplicateNote() {
if (confirm('Create a duplicate of this note?')) {
$.ajax({
url: '{% url "operating_theatre:surgical_note_duplicate" note.pk %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
alert('Note duplicated successfully!');
window.location.href = response.duplicate_url;
} else {
alert('Error duplicating note: ' + response.error);
}
},
error: function() {
alert('Error duplicating note');
}
});
}
}
// Keyboard shortcuts
$(document).keydown(function(e) {
// Ctrl+P for print
if (e.ctrlKey && e.keyCode === 80) {
e.preventDefault();
printNote();
}
// Ctrl+E for edit (if not signed)
{% if note.status != 'signed' %}
if (e.ctrlKey && e.keyCode === 69) {
e.preventDefault();
window.location.href = '{% url "operating_theatre:surgical_note_edit" note.pk %}';
}
{% endif %}
// Ctrl+S for sign (if completed)
{% if note.status == 'completed' %}
if (e.ctrlKey && e.keyCode === 83) {
e.preventDefault();
signNote();
}
{% endif %}
});
</script>
{% endblock %}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,516 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Surgical Notes{% endblock %}
{% block extra_css %}
<link href="{% static 'assets/plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
<style>
.stats-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.stat-item {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1.5rem;
text-align: center;
}
.stat-icon {
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1rem;
color: white;
}
.stat-icon.primary { background: #007bff; }
.stat-icon.success { background: #28a745; }
.stat-icon.warning { background: #ffc107; }
.stat-icon.info { background: #17a2b8; }
.stat-number {
font-size: 2rem;
font-weight: bold;
color: #495057;
margin-bottom: 0.5rem;
}
.stat-label {
color: #6c757d;
font-size: 0.875rem;
font-weight: 600;
}
.filter-section {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.note-status {
padding: 0.25rem 0.75rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.status-draft { background: #f8f9fa; color: #6c757d; }
.status-in-progress { background: #fff3cd; color: #856404; }
.status-completed { background: #d4edda; color: #155724; }
.status-signed { background: #d1ecf1; color: #0c5460; }
.status-amended { background: #f8d7da; color: #721c24; }
.priority-badge {
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
}
.priority-low { background: #d4edda; color: #155724; }
.priority-medium { background: #fff3cd; color: #856404; }
.priority-high { background: #f8d7da; color: #721c24; }
.priority-critical { background: #f5c6cb; color: #721c24; }
@media (max-width: 768px) {
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.stat-item {
padding: 1rem;
}
.stat-number {
font-size: 1.5rem;
}
}
</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 active">Surgical Notes</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-file-medical me-2"></i>Surgical Notes Management
</h1>
</div>
<div class="ms-auto">
<a href="{% url 'operating_theatre:surgical_note_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Create Note
</a>
</div>
</div>
<!-- Statistics Cards -->
<div class="stats-grid">
<div class="stat-item">
<div class="stat-icon primary">
<i class="fas fa-file-medical"></i>
</div>
<div class="stat-number" id="total-notes">{{ stats.total_notes|default:0 }}</div>
<div class="stat-label">Total Notes</div>
</div>
<div class="stat-item">
<div class="stat-icon warning">
<i class="fas fa-edit"></i>
</div>
<div class="stat-number" id="draft-notes">{{ stats.draft_notes|default:0 }}</div>
<div class="stat-label">Draft Notes</div>
</div>
<div class="stat-item">
<div class="stat-icon success">
<i class="fas fa-check-circle"></i>
</div>
<div class="stat-number" id="completed-notes">{{ stats.completed_notes|default:0 }}</div>
<div class="stat-label">Completed</div>
</div>
<div class="stat-item">
<div class="stat-icon info">
<i class="fas fa-signature"></i>
</div>
<div class="stat-number" id="signed-notes">{{ stats.signed_notes|default:0 }}</div>
<div class="stat-label">Signed</div>
</div>
</div>
<!-- Filters -->
<div class="filter-section">
<div class="row">
<div class="col-md-3">
<label class="form-label">Status</label>
<select class="form-select" id="status-filter">
<option value="">All Statuses</option>
<option value="draft">Draft</option>
<option value="in_progress">In Progress</option>
<option value="completed">Completed</option>
<option value="signed">Signed</option>
<option value="amended">Amended</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Surgeon</label>
<select class="form-select" id="surgeon-filter">
<option value="">All Surgeons</option>
{% for surgeon in surgeons %}
<option value="{{ surgeon.id }}">{{ surgeon.get_full_name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<label class="form-label">Date Range</label>
<select class="form-select" id="date-filter">
<option value="">All Dates</option>
<option value="today">Today</option>
<option value="week">This Week</option>
<option value="month">This Month</option>
<option value="custom">Custom Range</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Priority</label>
<select class="form-select" id="priority-filter">
<option value="">All Priorities</option>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
<option value="critical">Critical</option>
</select>
</div>
</div>
<div class="row mt-3" id="custom-date-range" style="display: none;">
<div class="col-md-6">
<label class="form-label">From Date</label>
<input type="date" class="form-control" id="from-date">
</div>
<div class="col-md-6">
<label class="form-label">To Date</label>
<input type="date" class="form-control" id="to-date">
</div>
</div>
<div class="row mt-3">
<div class="col-md-8">
<label class="form-label">Search</label>
<div class="input-group">
<input type="text" class="form-control" id="search-input"
placeholder="Search by patient name, procedure, or note content...">
<button class="btn btn-outline-secondary" type="button" id="search-btn">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<div class="col-md-4 d-flex align-items-end">
<button class="btn btn-outline-primary me-2" onclick="applyFilters()">
<i class="fas fa-filter me-1"></i>Apply
</button>
<button class="btn btn-outline-secondary" onclick="clearFilters()">
<i class="fas fa-times me-1"></i>Clear
</button>
</div>
</div>
</div>
<!-- Notes Table -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-list me-2"></i>Surgical Notes
</h5>
<div class="btn-group">
<button class="btn btn-outline-success btn-sm" onclick="exportNotes('excel')">
<i class="fas fa-file-excel me-1"></i>Excel
</button>
<button class="btn btn-outline-danger btn-sm" onclick="exportNotes('pdf')">
<i class="fas fa-file-pdf me-1"></i>PDF
</button>
<button class="btn btn-outline-secondary btn-sm" onclick="printNotes()">
<i class="fas fa-print me-1"></i>Print
</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="notesTable" class="table table-striped table-bordered align-middle">
<thead>
<tr>
<th width="5%">
<input type="checkbox" id="select-all">
</th>
<th>Patient</th>
<th>Procedure</th>
<th>Surgeon</th>
<th>Date</th>
<th>Status</th>
<th>Priority</th>
<th>Last Modified</th>
<th width="15%">Actions</th>
</tr>
</thead>
<tbody>
{% for note in notes %}
<tr>
<td>
<input type="checkbox" class="note-checkbox" value="{{ note.id }}">
</td>
<td>
<div class="d-flex align-items-center">
<div class="avatar avatar-sm me-2">
<i class="fas fa-user-circle fa-2x text-muted"></i>
</div>
<div>
<div class="fw-bold">{{ note.patient.get_full_name }}</div>
<small class="text-muted">ID: {{ note.patient.patient_id }}</small>
</div>
</div>
</td>
<td>
<div class="fw-bold">{{ note.procedure_name }}</div>
<small class="text-muted">{{ note.procedure_code|default:"" }}</small>
</td>
<td>
<div>{{ note.surgeon.get_full_name }}</div>
<small class="text-muted">{{ note.surgeon.specialization|default:"" }}</small>
</td>
<td>
<div>{{ note.surgery_date|date:"M d, Y" }}</div>
<small class="text-muted">{{ note.surgery_date|time:"g:i A" }}</small>
</td>
<td>
<span class="note-status status-{{ note.status }}">
{{ note.get_status_display }}
</span>
</td>
<td>
<span class="priority-badge priority-{{ note.priority }}">
{{ note.get_priority_display }}
</span>
</td>
<td>
<div>{{ note.updated_at|date:"M d, Y" }}</div>
<small class="text-muted">{{ note.updated_at|time:"g:i A" }}</small>
</td>
<td>
<div class="btn-group btn-group-sm">
<a href="{% url 'operating_theatre:surgical_note_detail' note.pk %}"
class="btn btn-outline-primary" title="View">
<i class="fas fa-eye"></i>
</a>
{% if note.status != 'signed' %}
<a href="{% url 'operating_theatre:surgical_note_edit' note.pk %}"
class="btn btn-outline-warning" title="Edit">
<i class="fas fa-edit"></i>
</a>
{% endif %}
<button class="btn btn-outline-success"
onclick="printNote('{{ note.pk }}')" title="Print">
<i class="fas fa-print"></i>
</button>
{% if note.status != 'signed' %}
<a href="{% url 'operating_theatre:surgical_note_delete' note.pk %}"
class="btn btn-outline-danger" title="Delete">
<i class="fas fa-trash"></i>
</a>
{% endif %}
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="9" class="text-center py-4">
<i class="fas fa-file-medical fa-3x text-muted mb-3"></i>
<p class="text-muted">No surgical notes found</p>
<a href="{% url 'operating_theatre:surgical_note_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Create First Note
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{% static 'assets/plugins/datatables.net/js/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
<script>
$(document).ready(function() {
// Initialize DataTable
const table = $('#notesTable').DataTable({
responsive: true,
pageLength: 25,
order: [[4, 'desc']], // Sort by date descending
columnDefs: [
{ orderable: false, targets: [0, 8] } // Disable sorting for checkbox and actions
],
language: {
search: "",
searchPlaceholder: "Search notes...",
lengthMenu: "Show _MENU_ notes per page",
info: "Showing _START_ to _END_ of _TOTAL_ notes",
infoEmpty: "No notes available",
infoFiltered: "(filtered from _MAX_ total notes)"
}
});
// Custom search
$('#search-input').on('keyup', function() {
table.search(this.value).draw();
});
// Select all checkbox
$('#select-all').on('change', function() {
$('.note-checkbox').prop('checked', this.checked);
});
// Date filter change
$('#date-filter').on('change', function() {
if ($(this).val() === 'custom') {
$('#custom-date-range').show();
} else {
$('#custom-date-range').hide();
}
});
// Load statistics
loadStats();
});
function loadStats() {
$.ajax({
url: '{% url "operating_theatre:surgical_note_stats" %}',
success: function(data) {
$('#total-notes').text(data.total_notes);
$('#draft-notes').text(data.draft_notes);
$('#completed-notes').text(data.completed_notes);
$('#signed-notes').text(data.signed_notes);
}
});
}
function applyFilters() {
const filters = {
status: $('#status-filter').val(),
surgeon: $('#surgeon-filter').val(),
date_range: $('#date-filter').val(),
priority: $('#priority-filter').val(),
from_date: $('#from-date').val(),
to_date: $('#to-date').val(),
search: $('#search-input').val()
};
// Apply filters via AJAX
$.ajax({
url: '{% url "operating_theatre:surgical_note_list" %}',
data: filters,
success: function(response) {
// Reload table data
location.reload();
}
});
}
function clearFilters() {
$('#status-filter').val('');
$('#surgeon-filter').val('');
$('#date-filter').val('');
$('#priority-filter').val('');
$('#from-date').val('');
$('#to-date').val('');
$('#search-input').val('');
$('#custom-date-range').hide();
// Clear DataTable search
$('#notesTable').DataTable().search('').draw();
}
function exportNotes(format) {
const selectedNotes = $('.note-checkbox:checked').map(function() {
return this.value;
}).get();
const params = new URLSearchParams({
format: format,
notes: selectedNotes.join(',')
});
window.open(`{% url "operating_theatre:surgical_note_export" %}?${params}`, '_blank');
}
function printNotes() {
const selectedNotes = $('.note-checkbox:checked').map(function() {
return this.value;
}).get();
if (selectedNotes.length === 0) {
alert('Please select notes to print');
return;
}
const params = new URLSearchParams({
notes: selectedNotes.join(',')
});
window.open(`{% url "operating_theatre:surgical_note_print" %}?${params}`, '_blank');
}
function printNote(noteId) {
window.open(`{% url "operating_theatre:surgical_note_print" %}?notes=${noteId}`, '_blank');
}
// Keyboard shortcuts
$(document).keydown(function(e) {
// Ctrl+N for new note
if (e.ctrlKey && e.keyCode === 78) {
e.preventDefault();
window.location.href = '{% url "operating_theatre:surgical_note_create" %}';
}
// Ctrl+F for search
if (e.ctrlKey && e.keyCode === 70) {
e.preventDefault();
$('#search-input').focus();
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,838 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Delete Template - {{ template.name }}{% endblock %}
{% block extra_css %}
<style>
.delete-header {
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.warning-section {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 2rem;
}
.template-details {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
margin-bottom: 2rem;
}
.detail-header {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 1rem 1.5rem;
font-weight: 600;
color: #495057;
}
.detail-content {
padding: 1.5rem;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 1.5rem;
}
.info-item {
display: flex;
flex-direction: column;
}
.info-label {
font-size: 0.875rem;
color: #6c757d;
font-weight: 600;
margin-bottom: 0.25rem;
}
.info-value {
color: #495057;
font-weight: 500;
}
.template-status {
padding: 0.5rem 1rem;
border-radius: 0.25rem;
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
display: inline-block;
}
.status-active { background: #d4edda; color: #155724; }
.status-inactive { background: #f8d7da; color: #721c24; }
.status-draft { background: #fff3cd; color: #856404; }
.template-category {
padding: 0.5rem 1rem;
border-radius: 0.25rem;
font-size: 0.875rem;
font-weight: 600;
background: #e3f2fd;
color: #1976d2;
display: inline-block;
}
.usage-impact {
background: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 2rem;
}
.usage-impact h6 {
color: #721c24;
margin-bottom: 1rem;
}
.impact-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
margin-bottom: 1rem;
}
.impact-stat {
text-align: center;
padding: 1rem;
background: white;
border: 1px solid #f5c6cb;
border-radius: 0.375rem;
}
.impact-number {
font-size: 1.5rem;
font-weight: bold;
color: #721c24;
margin-bottom: 0.5rem;
}
.impact-label {
color: #721c24;
font-size: 0.875rem;
font-weight: 600;
}
.consequences-list {
color: #721c24;
margin-bottom: 0;
}
.confirmation-section {
background: #fff;
border: 2px solid #dc3545;
border-radius: 0.375rem;
padding: 2rem;
margin-bottom: 2rem;
}
.confirmation-checkbox {
margin-bottom: 1rem;
}
.confirmation-checkbox .form-check-input:checked {
background-color: #dc3545;
border-color: #dc3545;
}
.delete-reason {
margin-bottom: 1.5rem;
}
.action-buttons {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 2rem;
border-top: 1px solid #dee2e6;
}
.btn-delete {
background: #dc3545;
border-color: #dc3545;
color: white;
}
.btn-delete:hover {
background: #c82333;
border-color: #bd2130;
color: white;
}
.btn-delete:disabled {
background: #6c757d;
border-color: #6c757d;
cursor: not-allowed;
}
.dependent-notes {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1.5rem;
}
.note-item {
display: flex;
justify-content: between;
align-items: center;
padding: 0.5rem;
background: white;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
margin-bottom: 0.5rem;
}
.note-item:last-child {
margin-bottom: 0;
}
.note-info {
flex: 1;
}
.note-patient {
font-weight: 600;
color: #495057;
}
.note-details {
font-size: 0.875rem;
color: #6c757d;
}
@media (max-width: 768px) {
.delete-header {
padding: 1.5rem;
}
.detail-content {
padding: 1rem;
}
.info-grid {
grid-template-columns: 1fr;
}
.impact-stats {
grid-template-columns: repeat(2, 1fr);
}
.action-buttons {
flex-direction: column;
gap: 1rem;
}
.action-buttons .btn {
width: 100%;
}
}
@media print {
.action-buttons, .confirmation-section {
display: none !important;
}
}
</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:surgical_note_template_list' %}">Templates</a></li>
<li class="breadcrumb-item"><a href="{% url 'operating_theatre:surgical_note_template_detail' template.pk %}">{{ template.name }}</a></li>
<li class="breadcrumb-item active">Delete</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-trash-alt me-2"></i>Delete Template
</h1>
</div>
<div class="ms-auto">
<a href="{% url 'operating_theatre:surgical_note_template_detail' template.pk %}" class="btn btn-outline-primary">
<i class="fas fa-arrow-left me-1"></i>Back to Template
</a>
</div>
</div>
<!-- Delete Warning Header -->
<div class="delete-header text-center">
<div class="mb-3">
<i class="fas fa-exclamation-triangle fa-4x"></i>
</div>
<h2 class="mb-2">Confirm Template Deletion</h2>
<p class="mb-0 fs-5">You are about to permanently delete this surgical note template</p>
</div>
<!-- Warning Section -->
<div class="warning-section">
<div class="d-flex align-items-center mb-3">
<i class="fas fa-exclamation-triangle fa-2x text-warning me-3"></i>
<div>
<h5 class="mb-1 text-warning">Warning: This action cannot be undone</h5>
<p class="mb-0">Deleting this template will permanently remove it from the system and affect all dependent surgical notes.</p>
</div>
</div>
</div>
<!-- Template Details -->
<div class="template-details">
<div class="detail-header">
<i class="fas fa-clipboard-list me-2"></i>Template Details
</div>
<div class="detail-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Template Name</div>
<div class="info-value">
<strong>{{ template.name }}</strong>
</div>
</div>
<div class="info-item">
<div class="info-label">Category</div>
<div class="info-value">
<span class="template-category">
{{ template.get_category_display }}
</span>
</div>
</div>
<div class="info-item">
<div class="info-label">Status</div>
<div class="info-value">
<span class="template-status status-{{ template.status }}">
{{ template.get_status_display }}
</span>
</div>
</div>
<div class="info-item">
<div class="info-label">Created By</div>
<div class="info-value">
<strong>{{ template.created_by.get_full_name }}</strong><br>
<small class="text-muted">{{ template.created_by.specialization|default:"" }}</small>
</div>
</div>
<div class="info-item">
<div class="info-label">Created Date</div>
<div class="info-value">
<strong>{{ template.created_at|date:"F d, Y" }}</strong><br>
<small class="text-muted">{{ template.created_at|time:"g:i A" }}</small>
</div>
</div>
<div class="info-item">
<div class="info-label">Last Modified</div>
<div class="info-value">
<strong>{{ template.updated_at|date:"M d, Y" }}</strong><br>
<small class="text-muted">{{ template.updated_at|time:"g:i A" }}</small>
</div>
</div>
<div class="info-item">
<div class="info-label">Version</div>
<div class="info-value">
<strong>v{{ template.version|default:1 }}</strong><br>
{% if template.is_latest_version %}
<small class="text-success">Latest Version</small>
{% else %}
<small class="text-warning">Outdated Version</small>
{% endif %}
</div>
</div>
<div class="info-item">
<div class="info-label">Content Size</div>
<div class="info-value">
<strong>{{ template.content|length|filesizeformat }}</strong><br>
<small class="text-muted">{{ template.content|length }} characters</small>
</div>
</div>
</div>
{% if template.description %}
<div class="mb-3">
<div class="info-label mb-2">Description</div>
<div class="info-value">{{ template.description }}</div>
</div>
{% endif %}
{% if template.tags.all %}
<div class="mb-3">
<div class="info-label mb-2">Tags</div>
<div>
{% for tag in template.tags.all %}
<span class="badge bg-light text-dark me-1">{{ tag.name }}</span>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
<!-- Usage Impact -->
<div class="usage-impact">
<h6><i class="fas fa-chart-bar me-2"></i>Usage Impact Analysis</h6>
<div class="impact-stats">
<div class="impact-stat">
<div class="impact-number">{{ template.usage_count|default:0 }}</div>
<div class="impact-label">Total Uses</div>
</div>
<div class="impact-stat">
<div class="impact-number">{{ template.active_notes_count|default:0 }}</div>
<div class="impact-label">Active Notes</div>
</div>
<div class="impact-stat">
<div class="impact-number">{{ template.unique_users|default:0 }}</div>
<div class="impact-label">Users Affected</div>
</div>
<div class="impact-stat">
<div class="impact-number">{{ template.usage_this_month|default:0 }}</div>
<div class="impact-label">Uses This Month</div>
</div>
</div>
<ul class="consequences-list">
<li><strong>Template Removal:</strong> This template will no longer be available for creating new surgical notes</li>
<li><strong>Existing Notes:</strong> {{ template.active_notes_count|default:0 }} surgical notes currently use this template</li>
<li><strong>User Impact:</strong> {{ template.unique_users|default:0 }} users have used this template</li>
<li><strong>Version History:</strong> All version history for this template will be permanently lost</li>
{% if template.is_default %}
<li><strong>Default Template:</strong> This is the default template for {{ template.get_category_display }} - a new default will need to be set</li>
{% endif %}
{% if template.is_public %}
<li><strong>Public Access:</strong> Other users will lose access to this shared template</li>
{% endif %}
</ul>
</div>
<!-- Dependent Notes -->
{% if dependent_notes %}
<div class="dependent-notes">
<h6 class="mb-3">
<i class="fas fa-link me-2"></i>Surgical Notes Using This Template ({{ dependent_notes|length }})
</h6>
<div style="max-height: 300px; overflow-y: auto;">
{% for note in dependent_notes %}
<div class="note-item">
<div class="note-info">
<div class="note-patient">{{ note.patient.get_full_name }}</div>
<div class="note-details">
{{ note.procedure_name }} - {{ note.surgery_date|date:"M d, Y" }}
<span class="badge bg-secondary ms-2">{{ note.get_status_display }}</span>
</div>
</div>
<a href="{% url 'operating_theatre:surgical_note_detail' note.pk %}"
class="btn btn-outline-primary btn-sm">
<i class="fas fa-eye"></i>
</a>
</div>
{% endfor %}
</div>
<div class="alert alert-warning mt-3 mb-0">
<i class="fas fa-info-circle me-2"></i>
<strong>Note:</strong> Existing surgical notes will retain their content, but will no longer be linked to this template.
</div>
</div>
{% endif %}
<!-- Special Warnings -->
{% if template.status == 'active' %}
<div class="alert alert-danger">
<h6><i class="fas fa-exclamation-triangle me-2"></i>Active Template Warning</h6>
<p class="mb-0">This template is currently active and being used by medical staff. Deleting it may disrupt workflow and force users to find alternative templates.</p>
</div>
{% endif %}
{% if template.is_default %}
<div class="alert alert-warning">
<h6><i class="fas fa-star me-2"></i>Default Template Warning</h6>
<p class="mb-0">This is the default template for {{ template.get_category_display }}. After deletion, you will need to set a new default template for this category.</p>
</div>
{% endif %}
{% if template.usage_count > 50 %}
<div class="alert alert-info">
<h6><i class="fas fa-chart-line me-2"></i>High Usage Template</h6>
<p class="mb-0">This template has been used {{ template.usage_count }} times. Consider archiving instead of deleting to preserve historical data.</p>
</div>
{% endif %}
<!-- Confirmation Form -->
<form method="post" id="delete-form">
{% csrf_token %}
<div class="confirmation-section">
<h5 class="mb-3"><i class="fas fa-check-square me-2"></i>Confirmation Required</h5>
<div class="confirmation-checkbox">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="confirm-understanding" required>
<label class="form-check-label" for="confirm-understanding">
I understand that this action will permanently delete the template and cannot be undone
</label>
</div>
</div>
<div class="confirmation-checkbox">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="confirm-authority" required>
<label class="form-check-label" for="confirm-authority">
I have the authority to delete this template
</label>
</div>
</div>
{% if template.status == 'active' %}
<div class="confirmation-checkbox">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="confirm-active" required>
<label class="form-check-label" for="confirm-active">
I acknowledge that deleting an active template may disrupt medical workflows
</label>
</div>
</div>
{% endif %}
{% if template.is_default %}
<div class="confirmation-checkbox">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="confirm-default" required>
<label class="form-check-label" for="confirm-default">
I will set a new default template for {{ template.get_category_display }} after deletion
</label>
</div>
</div>
{% endif %}
{% if dependent_notes %}
<div class="confirmation-checkbox">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="confirm-dependent" required>
<label class="form-check-label" for="confirm-dependent">
I understand that {{ dependent_notes|length }} surgical notes will be affected by this deletion
</label>
</div>
</div>
{% endif %}
<div class="delete-reason">
<label for="deletion-reason" class="form-label">
<i class="fas fa-comment me-1"></i>Reason for Deletion <span class="text-danger">*</span>
</label>
<textarea class="form-control" id="deletion-reason" name="deletion_reason" rows="3"
placeholder="Please provide a detailed reason for deleting this template..." required></textarea>
<div class="form-text">This reason will be logged for audit purposes and compliance</div>
</div>
<div class="mb-3">
<label for="replacement-template" class="form-label">
Replacement Template (Optional)
</label>
<select class="form-select" id="replacement-template" name="replacement_template">
<option value="">No replacement</option>
{% for alt_template in alternative_templates %}
<option value="{{ alt_template.id }}">{{ alt_template.name }}</option>
{% endfor %}
</select>
<div class="form-text">Suggest an alternative template for users</div>
</div>
<div class="mb-3">
<label for="confirmation-text" class="form-label">
Type "DELETE TEMPLATE" to confirm <span class="text-danger">*</span>
</label>
<input type="text" class="form-control" id="confirmation-text"
placeholder="Type DELETE TEMPLATE in capital letters" required>
<div class="form-text">This additional confirmation helps prevent accidental deletions</div>
</div>
</div>
<!-- Action Buttons -->
<div class="action-buttons">
<div>
<a href="{% url 'operating_theatre:surgical_note_template_detail' template.pk %}" class="btn btn-outline-secondary btn-lg">
<i class="fas fa-times me-2"></i>Cancel
</a>
<a href="{% url 'operating_theatre:surgical_note_template_list' %}" class="btn btn-outline-primary btn-lg ms-2">
<i class="fas fa-list me-2"></i>Back to List
</a>
</div>
<div>
<button type="button" class="btn btn-outline-warning btn-lg me-2" onclick="archiveTemplate()">
<i class="fas fa-archive me-2"></i>Archive Instead
</button>
<button type="button" class="btn btn-outline-info btn-lg me-2" onclick="exportTemplate()">
<i class="fas fa-download me-2"></i>Export First
</button>
<button type="submit" class="btn btn-delete btn-lg" id="delete-button" disabled>
<i class="fas fa-trash-alt me-2"></i>Delete Template
</button>
</div>
</div>
</form>
</div>
<!-- Final Confirmation Modal -->
<div class="modal fade" id="finalConfirmationModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title">
<i class="fas fa-exclamation-triangle me-2"></i>Final Confirmation
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body text-center">
<div class="mb-3">
<i class="fas fa-trash-alt fa-4x text-danger"></i>
</div>
<h5>Are you absolutely sure?</h5>
<p class="text-muted">This template will be permanently deleted. This action cannot be undone.</p>
<div class="alert alert-danger">
<strong>Template:</strong> {{ template.name }}<br>
<strong>Category:</strong> {{ template.get_category_display }}<br>
<strong>Usage Count:</strong> {{ template.usage_count|default:0 }} times<br>
{% if dependent_notes %}
<strong>Affected Notes:</strong> {{ dependent_notes|length }} surgical notes
{% endif %}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i>Cancel
</button>
<button type="button" class="btn btn-danger" onclick="confirmDelete()">
<i class="fas fa-trash-alt me-1"></i>Yes, Delete Permanently
</button>
</div>
</div>
</div>
</div>
<!-- Archive Modal -->
<div class="modal fade" id="archiveModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-archive me-2"></i>Archive Template
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
Archiving will make the template inactive but preserve all data and history.
</div>
<div class="mb-3">
<label for="archive-reason" class="form-label">Archive Reason</label>
<textarea class="form-control" id="archive-reason" rows="3"
placeholder="Reason for archiving this template..."></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i>Cancel
</button>
<button type="button" class="btn btn-warning" onclick="confirmArchive()">
<i class="fas fa-archive me-1"></i>Archive Template
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
$(document).ready(function() {
// Enable/disable delete button based on confirmations
function checkConfirmations() {
const checkboxes = document.querySelectorAll('.confirmation-checkbox input[type="checkbox"]');
const confirmationText = document.getElementById('confirmation-text').value.trim();
const deletionReason = document.getElementById('deletion-reason').value.trim();
const deleteButton = document.getElementById('delete-button');
let allChecked = true;
checkboxes.forEach(checkbox => {
if (!checkbox.checked) {
allChecked = false;
}
});
const isValid = allChecked &&
confirmationText === 'DELETE TEMPLATE' &&
deletionReason.length >= 10;
deleteButton.disabled = !isValid;
if (isValid) {
deleteButton.classList.remove('btn-secondary');
deleteButton.classList.add('btn-delete');
} else {
deleteButton.classList.remove('btn-delete');
deleteButton.classList.add('btn-secondary');
}
}
// Attach event listeners
document.querySelectorAll('.confirmation-checkbox input[type="checkbox"]').forEach(checkbox => {
checkbox.addEventListener('change', checkConfirmations);
});
document.getElementById('confirmation-text').addEventListener('input', checkConfirmations);
document.getElementById('deletion-reason').addEventListener('input', checkConfirmations);
// Form submission
document.getElementById('delete-form').addEventListener('submit', function(e) {
e.preventDefault();
// Show final confirmation modal
const modal = new bootstrap.Modal(document.getElementById('finalConfirmationModal'));
modal.show();
});
// Initial check
checkConfirmations();
});
function confirmDelete() {
// Close modal
const modal = bootstrap.Modal.getInstance(document.getElementById('finalConfirmationModal'));
modal.hide();
// Show loading state
const deleteButton = document.getElementById('delete-button');
const originalText = deleteButton.innerHTML;
deleteButton.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Deleting...';
deleteButton.disabled = true;
// Submit form
setTimeout(() => {
document.getElementById('delete-form').submit();
}, 1000);
}
function archiveTemplate() {
new bootstrap.Modal(document.getElementById('archiveModal')).show();
}
function confirmArchive() {
const reason = document.getElementById('archive-reason').value;
fetch(`/operating-theatre/templates/{{ template.pk }}/archive/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json'
},
body: JSON.stringify({
reason: reason
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert('Template archived successfully', 'success');
setTimeout(() => {
window.location.href = '{% url "operating_theatre:surgical_note_template_list" %}';
}, 1500);
} else {
showAlert('Error archiving template', 'danger');
}
})
.catch(error => {
showAlert('Error archiving template', 'danger');
});
bootstrap.Modal.getInstance(document.getElementById('archiveModal')).hide();
}
function exportTemplate() {
window.open(`/operating-theatre/templates/{{ template.pk }}/export/`, '_blank');
}
// Prevent accidental page navigation
window.addEventListener('beforeunload', function(e) {
const form = document.getElementById('delete-form');
const formData = new FormData(form);
// Check if user has started filling the form
if (formData.get('deletion_reason') ||
document.getElementById('confirmation-text').value ||
document.querySelector('.confirmation-checkbox input:checked')) {
e.preventDefault();
e.returnValue = 'You have unsaved changes. Are you sure you want to leave?';
return e.returnValue;
}
});
// Auto-save draft reason (for audit purposes)
let autoSaveTimer;
document.getElementById('deletion-reason').addEventListener('input', function() {
clearTimeout(autoSaveTimer);
autoSaveTimer = setTimeout(() => {
// Save draft reason to session storage
sessionStorage.setItem('template_deletion_reason_draft', this.value);
}, 2000);
});
// Restore draft reason on page load
window.addEventListener('load', function() {
const draftReason = sessionStorage.getItem('template_deletion_reason_draft');
if (draftReason) {
document.getElementById('deletion-reason').value = draftReason;
}
});
function showAlert(message, type) {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 1060; min-width: 300px;';
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(alertDiv);
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 5000);
}
</script>
{% endblock %}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,941 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Surgical Note Templates{% endblock %}
{% block extra_css %}
<link href="{% static 'assets/plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
<style>
.templates-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.stat-item {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1.5rem;
text-align: center;
transition: transform 0.2s, box-shadow 0.2s;
}
.stat-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.stat-icon {
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1rem;
color: white;
}
.stat-icon.primary { background: #007bff; }
.stat-icon.success { background: #28a745; }
.stat-icon.warning { background: #ffc107; }
.stat-icon.info { background: #17a2b8; }
.stat-number {
font-size: 2rem;
font-weight: bold;
color: #495057;
margin-bottom: 0.5rem;
}
.stat-label {
color: #6c757d;
font-size: 0.875rem;
font-weight: 600;
}
.filter-section {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.template-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
margin-bottom: 1rem;
transition: transform 0.2s, box-shadow 0.2s;
}
.template-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.template-header {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 1rem 1.5rem;
display: flex;
justify-content: between;
align-items: center;
}
.template-content {
padding: 1.5rem;
}
.template-meta {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-bottom: 1rem;
}
.meta-item {
display: flex;
align-items: center;
font-size: 0.875rem;
color: #6c757d;
}
.meta-item i {
margin-right: 0.5rem;
}
.template-status {
padding: 0.25rem 0.75rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.status-active { background: #d4edda; color: #155724; }
.status-inactive { background: #f8d7da; color: #721c24; }
.status-draft { background: #fff3cd; color: #856404; }
.template-category {
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
background: #e3f2fd;
color: #1976d2;
}
.usage-stats {
background: #f8f9fa;
border-radius: 0.25rem;
padding: 0.75rem;
margin-top: 1rem;
}
.usage-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
}
.usage-item:last-child {
margin-bottom: 0;
}
.usage-bar {
height: 4px;
background: #e9ecef;
border-radius: 2px;
overflow: hidden;
margin-top: 0.25rem;
}
.usage-fill {
height: 100%;
background: #007bff;
transition: width 0.3s ease;
}
.quick-actions {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
}
.btn-template-action {
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
border-radius: 0.25rem;
}
@media (max-width: 768px) {
.templates-header {
padding: 1.5rem;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.stat-item {
padding: 1rem;
}
.stat-number {
font-size: 1.5rem;
}
.template-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.template-meta {
flex-direction: column;
gap: 0.5rem;
}
.quick-actions {
flex-wrap: wrap;
}
}
@media print {
.filter-section, .quick-actions, .btn {
display: none !important;
}
.template-card {
break-inside: avoid;
margin-bottom: 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 active">Note Templates</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-clipboard-list me-2"></i>Surgical Note Templates
</h1>
</div>
<div class="ms-auto">
<div class="btn-group">
<button class="btn btn-outline-info" onclick="importTemplate()">
<i class="fas fa-file-import me-1"></i>Import
</button>
<button class="btn btn-outline-success" onclick="exportTemplates()">
<i class="fas fa-file-export me-1"></i>Export
</button>
<a href="{% url 'operating_theatre:surgical_note_template_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Create Template
</a>
</div>
</div>
</div>
<!-- Statistics Cards -->
<div class="stats-grid">
<div class="stat-item">
<div class="stat-icon primary">
<i class="fas fa-clipboard-list"></i>
</div>
<div class="stat-number" id="total-templates">{{ stats.total_templates|default:0 }}</div>
<div class="stat-label">Total Templates</div>
</div>
<div class="stat-item">
<div class="stat-icon success">
<i class="fas fa-check-circle"></i>
</div>
<div class="stat-number" id="active-templates">{{ stats.active_templates|default:0 }}</div>
<div class="stat-label">Active Templates</div>
</div>
<div class="stat-item">
<div class="stat-icon warning">
<i class="fas fa-star"></i>
</div>
<div class="stat-number" id="popular-templates">{{ stats.popular_templates|default:0 }}</div>
<div class="stat-label">Popular Templates</div>
</div>
<div class="stat-item">
<div class="stat-icon info">
<i class="fas fa-chart-line"></i>
</div>
<div class="stat-number" id="usage-this-month">{{ stats.usage_this_month|default:0 }}</div>
<div class="stat-label">Used This Month</div>
</div>
</div>
<!-- Filters -->
<div class="filter-section">
<div class="row">
<div class="col-md-3">
<label class="form-label">Category</label>
<select class="form-select" id="category-filter">
<option value="">All Categories</option>
<option value="general">General Surgery</option>
<option value="cardiac">Cardiac Surgery</option>
<option value="orthopedic">Orthopedic Surgery</option>
<option value="neurosurgery">Neurosurgery</option>
<option value="plastic">Plastic Surgery</option>
<option value="emergency">Emergency Surgery</option>
<option value="pediatric">Pediatric Surgery</option>
<option value="other">Other</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Status</label>
<select class="form-select" id="status-filter">
<option value="">All Statuses</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
<option value="draft">Draft</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Created By</label>
<select class="form-select" id="author-filter">
<option value="">All Authors</option>
{% for author in authors %}
<option value="{{ author.id }}">{{ author.get_full_name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<label class="form-label">Sort By</label>
<select class="form-select" id="sort-filter">
<option value="name">Name</option>
<option value="created">Date Created</option>
<option value="updated">Last Updated</option>
<option value="usage">Usage Count</option>
<option value="popularity">Popularity</option>
</select>
</div>
</div>
<div class="row mt-3">
<div class="col-md-8">
<label class="form-label">Search</label>
<div class="input-group">
<input type="text" class="form-control" id="search-input"
placeholder="Search templates by name, description, or content...">
<button class="btn btn-outline-secondary" type="button" id="search-btn">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<div class="col-md-4 d-flex align-items-end">
<button class="btn btn-outline-primary me-2" onclick="applyFilters()">
<i class="fas fa-filter me-1"></i>Apply
</button>
<button class="btn btn-outline-secondary" onclick="clearFilters()">
<i class="fas fa-times me-1"></i>Clear
</button>
</div>
</div>
</div>
<!-- View Toggle -->
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="btn-group" role="group">
<input type="radio" class="btn-check" name="view-mode" id="card-view" checked>
<label class="btn btn-outline-primary" for="card-view">
<i class="fas fa-th-large me-1"></i>Cards
</label>
<input type="radio" class="btn-check" name="view-mode" id="table-view">
<label class="btn btn-outline-primary" for="table-view">
<i class="fas fa-list me-1"></i>Table
</label>
</div>
<div class="btn-group">
<button class="btn btn-outline-success btn-sm" onclick="exportTemplates('excel')">
<i class="fas fa-file-excel me-1"></i>Excel
</button>
<button class="btn btn-outline-danger btn-sm" onclick="exportTemplates('pdf')">
<i class="fas fa-file-pdf me-1"></i>PDF
</button>
<button class="btn btn-outline-secondary btn-sm" onclick="printTemplates()">
<i class="fas fa-print me-1"></i>Print
</button>
</div>
</div>
<!-- Card View -->
<div id="card-view-container">
<div class="row">
{% for template in templates %}
<div class="col-lg-6 col-xl-4 mb-3">
<div class="template-card">
<div class="template-header">
<div>
<h6 class="mb-1">{{ template.name }}</h6>
<div class="d-flex align-items-center gap-2">
<span class="template-status status-{{ template.status }}">
{{ template.get_status_display }}
</span>
<span class="template-category">
{{ template.get_category_display }}
</span>
</div>
</div>
<div class="dropdown">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle"
data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="{% url 'operating_theatre:surgical_note_template_detail' template.pk %}">
<i class="fas fa-eye me-2"></i>View Details
</a>
</li>
<li>
<a class="dropdown-item" href="{% url 'operating_theatre:surgical_note_template_edit' template.pk %}">
<i class="fas fa-edit me-2"></i>Edit Template
</a>
</li>
<li>
<a class="dropdown-item" href="#" onclick="duplicateTemplate('{{ template.pk }}')">
<i class="fas fa-copy me-2"></i>Duplicate
</a>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item" href="#" onclick="exportTemplate('{{ template.pk }}')">
<i class="fas fa-download me-2"></i>Export
</a>
</li>
{% if template.status != 'active' %}
<li>
<a class="dropdown-item text-danger"
href="{% url 'operating_theatre:surgical_note_template_delete' template.pk %}">
<i class="fas fa-trash me-2"></i>Delete
</a>
</li>
{% endif %}
</ul>
</div>
</div>
<div class="template-content">
<p class="text-muted mb-3">{{ template.description|truncatewords:20 }}</p>
<div class="template-meta">
<div class="meta-item">
<i class="fas fa-user"></i>
{{ template.created_by.get_full_name }}
</div>
<div class="meta-item">
<i class="fas fa-calendar"></i>
{{ template.created_at|date:"M d, Y" }}
</div>
<div class="meta-item">
<i class="fas fa-clock"></i>
{{ template.updated_at|timesince }} ago
</div>
<div class="meta-item">
<i class="fas fa-chart-bar"></i>
Used {{ template.usage_count|default:0 }} times
</div>
</div>
{% if template.tags.all %}
<div class="mb-3">
{% for tag in template.tags.all %}
<span class="badge bg-light text-dark me-1">{{ tag.name }}</span>
{% endfor %}
</div>
{% endif %}
<div class="usage-stats">
<div class="usage-item">
<span class="small text-muted">This Month</span>
<span class="small fw-bold">{{ template.usage_this_month|default:0 }}</span>
</div>
<div class="usage-bar">
<div class="usage-fill" style="width: {{ template.usage_percentage|default:0 }}%"></div>
</div>
</div>
<div class="quick-actions">
<a href="{% url 'operating_theatre:surgical_note_create' %}?template={{ template.pk }}"
class="btn btn-primary btn-template-action">
<i class="fas fa-plus me-1"></i>Use Template
</a>
<a href="{% url 'operating_theatre:surgical_note_template_detail' template.pk %}"
class="btn btn-outline-primary btn-template-action">
<i class="fas fa-eye me-1"></i>Preview
</a>
{% if template.is_favorite %}
<button class="btn btn-outline-warning btn-template-action"
onclick="toggleFavorite('{{ template.pk }}', false)">
<i class="fas fa-star"></i>
</button>
{% else %}
<button class="btn btn-outline-secondary btn-template-action"
onclick="toggleFavorite('{{ template.pk }}', true)">
<i class="far fa-star"></i>
</button>
{% endif %}
</div>
</div>
</div>
</div>
{% empty %}
<div class="col-12">
<div class="text-center py-5">
<i class="fas fa-clipboard-list fa-4x text-muted mb-3"></i>
<h5 class="text-muted">No Templates Found</h5>
<p class="text-muted">Create your first surgical note template to get started</p>
<a href="{% url 'operating_theatre:surgical_note_template_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Create Template
</a>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Table View -->
<div id="table-view-container" style="display: none;">
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table id="templatesTable" class="table table-striped table-bordered align-middle">
<thead>
<tr>
<th width="5%">
<input type="checkbox" id="select-all">
</th>
<th>Template Name</th>
<th>Category</th>
<th>Status</th>
<th>Created By</th>
<th>Usage Count</th>
<th>Last Updated</th>
<th width="15%">Actions</th>
</tr>
</thead>
<tbody>
{% for template in templates %}
<tr>
<td>
<input type="checkbox" class="template-checkbox" value="{{ template.id }}">
</td>
<td>
<div>
<strong>{{ template.name }}</strong><br>
<small class="text-muted">{{ template.description|truncatewords:10 }}</small>
</div>
</td>
<td>
<span class="template-category">
{{ template.get_category_display }}
</span>
</td>
<td>
<span class="template-status status-{{ template.status }}">
{{ template.get_status_display }}
</span>
</td>
<td>{{ template.created_by.get_full_name }}</td>
<td>
<div class="text-center">
<strong>{{ template.usage_count|default:0 }}</strong><br>
<small class="text-muted">{{ template.usage_this_month|default:0 }} this month</small>
</div>
</td>
<td>
<div>{{ template.updated_at|date:"M d, Y" }}</div>
<small class="text-muted">{{ template.updated_at|time:"g:i A" }}</small>
</td>
<td>
<div class="btn-group btn-group-sm">
<a href="{% url 'operating_theatre:surgical_note_template_detail' template.pk %}"
class="btn btn-outline-primary" title="View">
<i class="fas fa-eye"></i>
</a>
<a href="{% url 'operating_theatre:surgical_note_template_edit' template.pk %}"
class="btn btn-outline-warning" title="Edit">
<i class="fas fa-edit"></i>
</a>
<a href="{% url 'operating_theatre:surgical_note_create' %}?template={{ template.pk }}"
class="btn btn-outline-success" title="Use Template">
<i class="fas fa-plus"></i>
</a>
{% if template.status != 'active' %}
<a href="{% url 'operating_theatre:surgical_note_template_delete' template.pk %}"
class="btn btn-outline-danger" title="Delete">
<i class="fas fa-trash"></i>
</a>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Import Template Modal -->
<div class="modal fade" id="importModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-file-import me-2"></i>Import Template
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="import-form" enctype="multipart/form-data">
{% csrf_token %}
<div class="mb-3">
<label for="template-file" class="form-label">Template File</label>
<input type="file" class="form-control" id="template-file"
accept=".json,.xml,.txt" required>
<div class="form-text">Supported formats: JSON, XML, TXT</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="overwrite-existing">
<label class="form-check-label" for="overwrite-existing">
Overwrite existing templates with same name
</label>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i>Cancel
</button>
<button type="button" class="btn btn-primary" onclick="processImport()">
<i class="fas fa-upload me-1"></i>Import Template
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{% static 'assets/plugins/datatables.net/js/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
<script>
$(document).ready(function() {
// Initialize DataTable
const table = $('#templatesTable').DataTable({
responsive: true,
pageLength: 25,
order: [[6, 'desc']], // Sort by last updated descending
columnDefs: [
{ orderable: false, targets: [0, 7] } // Disable sorting for checkbox and actions
],
language: {
search: "",
searchPlaceholder: "Search templates...",
lengthMenu: "Show _MENU_ templates per page",
info: "Showing _START_ to _END_ of _TOTAL_ templates",
infoEmpty: "No templates available",
infoFiltered: "(filtered from _MAX_ total templates)"
}
});
// View mode toggle
$('input[name="view-mode"]').on('change', function() {
if (this.id === 'card-view') {
$('#card-view-container').show();
$('#table-view-container').hide();
} else {
$('#card-view-container').hide();
$('#table-view-container').show();
table.columns.adjust().responsive.recalc();
}
});
// Select all checkbox
$('#select-all').on('change', function() {
$('.template-checkbox').prop('checked', this.checked);
});
// Search functionality
$('#search-input').on('keyup', function() {
table.search(this.value).draw();
filterCards(this.value);
});
// Filter functionality
$('#category-filter, #status-filter, #author-filter, #sort-filter').on('change', function() {
applyFilters();
});
});
function filterCards(searchTerm) {
const cards = document.querySelectorAll('.template-card');
const term = searchTerm.toLowerCase();
cards.forEach(card => {
const text = card.textContent.toLowerCase();
const parent = card.closest('.col-lg-6');
if (text.includes(term)) {
parent.style.display = '';
} else {
parent.style.display = 'none';
}
});
}
function applyFilters() {
const category = document.getElementById('category-filter').value;
const status = document.getElementById('status-filter').value;
const author = document.getElementById('author-filter').value;
const sort = document.getElementById('sort-filter').value;
// Apply filters to DataTable
if (category) {
table.column(2).search(category);
} else {
table.column(2).search('');
}
if (status) {
table.column(3).search(status);
} else {
table.column(3).search('');
}
table.draw();
// Apply filters to cards
filterCardsAdvanced(category, status, author, sort);
}
function filterCardsAdvanced(category, status, author, sort) {
const cards = Array.from(document.querySelectorAll('.template-card'));
const container = document.querySelector('#card-view-container .row');
// Filter cards
const filteredCards = cards.filter(card => {
let show = true;
if (category) {
const cardCategory = card.querySelector('.template-category').textContent.toLowerCase();
if (!cardCategory.includes(category.toLowerCase())) {
show = false;
}
}
if (status) {
const cardStatus = card.querySelector('.template-status').classList;
if (!cardStatus.contains(`status-${status}`)) {
show = false;
}
}
if (author) {
const cardAuthor = card.querySelector('.meta-item').textContent;
if (!cardAuthor.includes(author)) {
show = false;
}
}
return show;
});
// Sort cards
if (sort) {
filteredCards.sort((a, b) => {
switch (sort) {
case 'name':
return a.querySelector('h6').textContent.localeCompare(b.querySelector('h6').textContent);
case 'usage':
const usageA = parseInt(a.querySelector('.usage-item span:last-child').textContent);
const usageB = parseInt(b.querySelector('.usage-item span:last-child').textContent);
return usageB - usageA;
default:
return 0;
}
});
}
// Hide all cards first
cards.forEach(card => {
card.closest('.col-lg-6').style.display = 'none';
});
// Show filtered cards
filteredCards.forEach(card => {
card.closest('.col-lg-6').style.display = '';
});
}
function clearFilters() {
document.getElementById('category-filter').value = '';
document.getElementById('status-filter').value = '';
document.getElementById('author-filter').value = '';
document.getElementById('sort-filter').value = 'name';
document.getElementById('search-input').value = '';
// Clear DataTable filters
table.search('').columns().search('').draw();
// Show all cards
document.querySelectorAll('.template-card').forEach(card => {
card.closest('.col-lg-6').style.display = '';
});
}
function toggleFavorite(templateId, isFavorite) {
fetch(`/operating-theatre/templates/${templateId}/favorite/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json'
},
body: JSON.stringify({ is_favorite: isFavorite })
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
showAlert('Error updating favorite status', 'danger');
}
})
.catch(error => {
showAlert('Error updating favorite status', 'danger');
});
}
function duplicateTemplate(templateId) {
if (confirm('Create a copy of this template?')) {
fetch(`/operating-theatre/templates/${templateId}/duplicate/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert('Template duplicated successfully', 'success');
setTimeout(() => location.reload(), 1500);
} else {
showAlert('Error duplicating template', 'danger');
}
})
.catch(error => {
showAlert('Error duplicating template', 'danger');
});
}
}
function exportTemplate(templateId) {
window.open(`/operating-theatre/templates/${templateId}/export/`, '_blank');
}
function exportTemplates(format = 'json') {
const selectedTemplates = Array.from(document.querySelectorAll('.template-checkbox:checked'))
.map(cb => cb.value);
if (selectedTemplates.length === 0) {
showAlert('Please select templates to export', 'warning');
return;
}
const params = new URLSearchParams({
format: format,
templates: selectedTemplates.join(',')
});
window.open(`/operating-theatre/templates/export/?${params}`, '_blank');
}
function importTemplate() {
new bootstrap.Modal(document.getElementById('importModal')).show();
}
function processImport() {
const form = document.getElementById('import-form');
const formData = new FormData(form);
fetch('/operating-theatre/templates/import/', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert(`Successfully imported ${data.count} templates`, 'success');
bootstrap.Modal.getInstance(document.getElementById('importModal')).hide();
setTimeout(() => location.reload(), 1500);
} else {
showAlert(data.message || 'Error importing templates', 'danger');
}
})
.catch(error => {
showAlert('Error importing templates', 'danger');
});
}
function printTemplates() {
window.print();
}
function showAlert(message, type) {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 1060; min-width: 300px;';
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(alertDiv);
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 5000);
}
</script>
{% endblock %}

View File

@ -0,0 +1,603 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Delete Template - {{ template.name }}{% endblock %}
{% block extra_css %}
<style>
.delete-header {
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.warning-section {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.impact-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;
}
.impact-item {
display: flex;
align-items: center;
padding: 0.75rem;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
margin-bottom: 0.5rem;
background: #f8f9fa;
}
.impact-icon {
width: 40px;
height: 40px;
border-radius: 0.25rem;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
color: white;
}
.impact-critical { background: #dc3545; }
.impact-warning { background: #ffc107; }
.impact-info { background: #17a2b8; }
.impact-details {
flex-grow: 1;
}
.impact-title {
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
}
.impact-description {
font-size: 0.875rem;
color: #6c757d;
}
.alternative-actions {
background: #e7f3ff;
border: 1px solid #b3d9ff;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.action-button {
display: block;
width: 100%;
text-align: left;
margin-bottom: 0.5rem;
}
.confirmation-section {
background: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.type-to-confirm {
background: white;
border: 2px solid #dc3545;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1rem;
}
.blocking-conditions {
background: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1.5rem;
}
.template-info {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.info-item {
display: flex;
flex-direction: column;
}
.info-label {
font-size: 0.875rem;
color: #6c757d;
font-weight: 600;
margin-bottom: 0.25rem;
}
.info-value {
color: #495057;
font-weight: 500;
}
@media (max-width: 768px) {
.delete-header {
padding: 1.5rem;
}
.info-grid {
grid-template-columns: 1fr;
}
.impact-item {
flex-direction: column;
text-align: center;
}
.impact-icon {
margin-right: 0;
margin-bottom: 0.5rem;
}
}
</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:template_list' %}">Templates</a></li>
<li class="breadcrumb-item"><a href="{% url 'operating_theatre:template_detail' template.pk %}">{{ template.name }}</a></li>
<li class="breadcrumb-item active">Delete</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-trash me-2"></i>Delete Surgical Template
</h1>
</div>
<div class="ms-auto">
<a href="{% url 'operating_theatre:template_detail' template.pk %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>Back to Template
</a>
</div>
</div>
<!-- Delete Header -->
<div class="delete-header">
<div class="row">
<div class="col-md-8">
<h2 class="mb-3">
<i class="fas fa-exclamation-triangle me-2"></i>
Delete "{{ template.name }}"
</h2>
<p class="mb-0">
You are about to permanently delete this surgical template. This action cannot be undone.
Please review the impact analysis below before proceeding.
</p>
</div>
<div class="col-md-4 text-end">
<i class="fas fa-file-medical fa-4x opacity-50"></i>
</div>
</div>
</div>
<!-- Template Information -->
<div class="template-info">
<h5 class="mb-3">
<i class="fas fa-info-circle me-2"></i>Template Information
</h5>
<div class="info-grid">
<div class="info-item">
<div class="info-label">Template Name</div>
<div class="info-value">{{ template.name }}</div>
</div>
<div class="info-item">
<div class="info-label">Specialty</div>
<div class="info-value">{{ template.get_specialty_display }}</div>
</div>
<div class="info-item">
<div class="info-label">Complexity</div>
<div class="info-value">{{ template.get_complexity_display }}</div>
</div>
<div class="info-item">
<div class="info-label">Status</div>
<div class="info-value">
{% if template.status == 'active' %}
<span class="badge bg-success">Active</span>
{% elif template.status == 'draft' %}
<span class="badge bg-secondary">Draft</span>
{% elif template.status == 'under_review' %}
<span class="badge bg-warning">Under Review</span>
{% elif template.status == 'archived' %}
<span class="badge bg-dark">Archived</span>
{% endif %}
</div>
</div>
<div class="info-item">
<div class="info-label">Created By</div>
<div class="info-value">{{ template.created_by.get_full_name }}</div>
</div>
<div class="info-item">
<div class="info-label">Created Date</div>
<div class="info-value">{{ template.created_at|date:"M d, Y" }}</div>
</div>
<div class="info-item">
<div class="info-label">Total Uses</div>
<div class="info-value">{{ template.usage_count|default:0 }}</div>
</div>
<div class="info-item">
<div class="info-label">Last Used</div>
<div class="info-value">{{ template.last_used|date:"M d, Y"|default:"Never" }}</div>
</div>
</div>
</div>
<!-- Blocking Conditions -->
{% if blocking_conditions %}
<div class="blocking-conditions">
<h5 class="text-danger mb-3">
<i class="fas fa-ban me-2"></i>Cannot Delete Template
</h5>
<p class="mb-3">This template cannot be deleted due to the following conditions:</p>
<ul class="mb-0">
{% for condition in blocking_conditions %}
<li class="text-danger">{{ condition }}</li>
{% endfor %}
</ul>
<div class="mt-3">
<a href="{% url 'operating_theatre:template_detail' template.pk %}" class="btn btn-primary">
<i class="fas fa-arrow-left me-1"></i>Return to Template
</a>
</div>
</div>
{% else %}
<!-- Warning Section -->
<div class="warning-section">
<h5 class="text-warning mb-3">
<i class="fas fa-exclamation-triangle me-2"></i>Warning
</h5>
<p class="mb-0">
Deleting this template will permanently remove all associated data including procedure steps,
equipment requirements, and team configurations. This action cannot be undone.
</p>
</div>
<!-- Impact Analysis -->
<div class="impact-section">
<div class="section-header">
<i class="fas fa-chart-line me-2"></i>Impact Analysis
</div>
<div class="section-content">
<div class="impact-item">
<div class="impact-icon impact-critical">
<i class="fas fa-list-ol"></i>
</div>
<div class="impact-details">
<div class="impact-title">Procedure Steps</div>
<div class="impact-description">
{{ template.procedure_steps.count }} procedure steps will be permanently deleted
</div>
</div>
</div>
<div class="impact-item">
<div class="impact-icon impact-warning">
<i class="fas fa-tools"></i>
</div>
<div class="impact-details">
<div class="impact-title">Equipment Requirements</div>
<div class="impact-description">
{{ template.equipment_list.count }} equipment requirements will be removed
</div>
</div>
</div>
<div class="impact-item">
<div class="impact-icon impact-info">
<i class="fas fa-users"></i>
</div>
<div class="impact-details">
<div class="impact-title">Team Requirements</div>
<div class="impact-description">
{{ template.team_requirements.count }} team member requirements will be removed
</div>
</div>
</div>
{% if template.active_cases.count > 0 %}
<div class="impact-item">
<div class="impact-icon impact-critical">
<i class="fas fa-calendar-alt"></i>
</div>
<div class="impact-details">
<div class="impact-title">Active Cases</div>
<div class="impact-description">
{{ template.active_cases.count }} active surgical cases are using this template
</div>
</div>
</div>
{% endif %}
{% if template.scheduled_cases.count > 0 %}
<div class="impact-item">
<div class="impact-icon impact-warning">
<i class="fas fa-clock"></i>
</div>
<div class="impact-details">
<div class="impact-title">Scheduled Cases</div>
<div class="impact-description">
{{ template.scheduled_cases.count }} scheduled cases reference this template
</div>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Alternative Actions -->
<div class="alternative-actions">
<h5 class="mb-3">
<i class="fas fa-lightbulb me-2"></i>Alternative Actions
</h5>
<p class="mb-3">Consider these alternatives instead of permanent deletion:</p>
<div class="row">
<div class="col-md-6">
<button class="btn btn-outline-secondary action-button" onclick="archiveTemplate()">
<i class="fas fa-archive me-2"></i>
<div>
<strong>Archive Template</strong><br>
<small>Hide template but preserve data for reference</small>
</div>
</button>
</div>
<div class="col-md-6">
<button class="btn btn-outline-info action-button" onclick="deactivateTemplate()">
<i class="fas fa-pause me-2"></i>
<div>
<strong>Deactivate Template</strong><br>
<small>Prevent new uses while keeping existing data</small>
</div>
</button>
</div>
</div>
<div class="row">
<div class="col-md-6">
<button class="btn btn-outline-success action-button" onclick="exportTemplate()">
<i class="fas fa-download me-2"></i>
<div>
<strong>Export Template</strong><br>
<small>Create backup before deletion</small>
</div>
</button>
</div>
<div class="col-md-6">
<button class="btn btn-outline-warning action-button" onclick="transferTemplate()">
<i class="fas fa-exchange-alt me-2"></i>
<div>
<strong>Transfer Ownership</strong><br>
<small>Assign template to another user</small>
</div>
</button>
</div>
</div>
</div>
<!-- Confirmation Section -->
<div class="confirmation-section">
<h5 class="text-danger mb-3">
<i class="fas fa-shield-alt me-2"></i>Deletion Confirmation
</h5>
<form method="post" id="deleteForm">
{% csrf_token %}
<div class="type-to-confirm">
<label class="form-label">
<strong>Type the template name to confirm deletion:</strong>
</label>
<input type="text" class="form-control" id="confirmationInput"
placeholder="Enter: {{ template.name }}" required>
<small class="text-muted">
This helps prevent accidental deletions
</small>
</div>
<div class="mb-3">
<label class="form-label">Reason for Deletion</label>
<select class="form-select" name="deletion_reason" required>
<option value="">Select reason...</option>
<option value="obsolete">Template is obsolete</option>
<option value="duplicate">Duplicate template</option>
<option value="incorrect">Incorrect information</option>
<option value="replaced">Replaced by newer template</option>
<option value="unused">No longer used</option>
<option value="other">Other</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Additional Notes</label>
<textarea class="form-control" name="deletion_notes" rows="3"
placeholder="Optional: Provide additional context for this deletion..."></textarea>
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="notifyUsers" name="notify_users" value="1">
<label class="form-check-label" for="notifyUsers">
Notify users who have used this template
</label>
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="createBackup" name="create_backup" value="1" checked>
<label class="form-check-label" for="createBackup">
Create backup before deletion
</label>
</div>
<div class="d-flex justify-content-between align-items-center">
<a href="{% url 'operating_theatre:template_detail' template.pk %}" class="btn btn-outline-secondary">
<i class="fas fa-times me-1"></i>Cancel
</a>
<button type="submit" class="btn btn-danger" id="deleteButton" disabled>
<i class="fas fa-trash me-1"></i>Delete Template Permanently
</button>
</div>
</form>
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
<script>
$(document).ready(function() {
// Enable delete button only when confirmation text matches
$('#confirmationInput').on('input', function() {
const inputValue = $(this).val();
const templateName = '{{ template.name }}';
const deleteButton = $('#deleteButton');
if (inputValue === templateName) {
deleteButton.prop('disabled', false);
deleteButton.removeClass('btn-secondary').addClass('btn-danger');
} else {
deleteButton.prop('disabled', true);
deleteButton.removeClass('btn-danger').addClass('btn-secondary');
}
});
// Form submission confirmation
$('#deleteForm').on('submit', function(e) {
const confirmed = confirm(
'Are you absolutely sure you want to delete this template? ' +
'This action cannot be undone and will permanently remove all associated data.'
);
if (!confirmed) {
e.preventDefault();
}
});
});
function archiveTemplate() {
if (confirm('Archive this template? It will be hidden but data will be preserved.')) {
$.ajax({
url: '{% url "operating_theatre:template_archive" template.pk %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
alert('Template archived successfully!');
window.location.href = '{% url "operating_theatre:template_list" %}';
} else {
alert('Error archiving template: ' + response.error);
}
},
error: function() {
alert('Error archiving template');
}
});
}
}
function deactivateTemplate() {
if (confirm('Deactivate this template? It will prevent new uses but preserve existing data.')) {
$.ajax({
url: '{% url "operating_theatre:template_deactivate" template.pk %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
alert('Template deactivated successfully!');
window.location.href = '{% url "operating_theatre:template_detail" template.pk %}';
} else {
alert('Error deactivating template: ' + response.error);
}
},
error: function() {
alert('Error deactivating template');
}
});
}
}
function exportTemplate() {
window.location.href = '{% url "operating_theatre:template_export" template.pk %}';
}
function transferTemplate() {
const newOwner = prompt('Enter the username or email of the new owner:');
if (newOwner) {
$.ajax({
url: '{% url "operating_theatre:template_transfer" template.pk %}',
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'new_owner': newOwner
},
success: function(response) {
if (response.success) {
alert('Template ownership transferred successfully!');
window.location.href = '{% url "operating_theatre:template_list" %}';
} else {
alert('Error transferring template: ' + response.error);
}
},
error: function() {
alert('Error transferring template');
}
});
}
}
// Keyboard shortcuts
$(document).keydown(function(e) {
// Escape key to cancel
if (e.keyCode === 27) {
window.location.href = '{% url "operating_theatre:template_detail" template.pk %}';
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,628 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}{{ template.name }} - Surgical Template{% endblock %}
{% block extra_css %}
<style>
.template-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.info-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;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.info-item {
display: flex;
flex-direction: column;
}
.info-label {
font-size: 0.875rem;
color: #6c757d;
font-weight: 600;
margin-bottom: 0.25rem;
}
.info-value {
color: #495057;
font-weight: 500;
}
.step-item {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1rem;
background: white;
}
.step-header {
display: flex;
justify-content: between;
align-items: center;
margin-bottom: 0.5rem;
}
.step-number {
background: #007bff;
color: white;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 0.875rem;
}
.step-title {
font-weight: 600;
color: #495057;
margin-left: 1rem;
flex-grow: 1;
}
.step-duration {
background: #e9ecef;
color: #495057;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
}
.equipment-item {
display: flex;
align-items: center;
padding: 0.75rem;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
margin-bottom: 0.5rem;
background: #f8f9fa;
}
.equipment-icon {
width: 40px;
height: 40px;
background: #007bff;
color: white;
border-radius: 0.25rem;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
}
.equipment-info {
flex-grow: 1;
}
.equipment-name {
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
}
.equipment-details {
font-size: 0.875rem;
color: #6c757d;
}
.team-member {
display: flex;
align-items: center;
padding: 0.75rem;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
margin-bottom: 0.5rem;
background: white;
}
.member-avatar {
width: 40px;
height: 40px;
background: #28a745;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
font-weight: bold;
}
.member-info {
flex-grow: 1;
}
.member-role {
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
}
.member-requirements {
font-size: 0.875rem;
color: #6c757d;
}
.usage-stats {
background: #e7f3ff;
border: 1px solid #b3d9ff;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
}
.stat-item {
text-align: center;
}
.stat-value {
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 0.25rem;
}
.stat-label {
font-size: 0.875rem;
color: #6c757d;
}
.template-tags {
margin-bottom: 1rem;
}
.template-tag {
display: inline-block;
background: #e9ecef;
color: #495057;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
margin-right: 0.5rem;
margin-bottom: 0.25rem;
}
.complexity-simple { background: #d4edda; color: #155724; }
.complexity-moderate { background: #fff3cd; color: #856404; }
.complexity-complex { background: #f8d7da; color: #721c24; }
.complexity-highly-complex { background: #d1ecf1; color: #0c5460; }
@media (max-width: 768px) {
.template-header {
padding: 1.5rem;
}
.info-grid {
grid-template-columns: 1fr;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.step-header {
flex-direction: column;
align-items: flex-start;
}
.step-title {
margin-left: 0;
margin-top: 0.5rem;
}
}
</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:template_list' %}">Templates</a></li>
<li class="breadcrumb-item active">{{ template.name }}</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-file-medical me-2"></i>{{ template.name }}
</h1>
</div>
<div class="ms-auto">
<div class="btn-group me-2">
<button class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
<i class="fas fa-download me-1"></i>Export
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{% url 'operating_theatre:template_export_pdf' template.pk %}">
<i class="fas fa-file-pdf me-2"></i>Export as PDF
</a></li>
<li><a class="dropdown-item" href="{% url 'operating_theatre:template_export_json' template.pk %}">
<i class="fas fa-file-code me-2"></i>Export as JSON
</a></li>
</ul>
</div>
<a href="{% url 'operating_theatre:template_edit' template.pk %}" class="btn btn-outline-primary me-2">
<i class="fas fa-edit me-1"></i>Edit
</a>
<button class="btn btn-success" onclick="useTemplate()">
<i class="fas fa-play me-1"></i>Use Template
</button>
</div>
</div>
<!-- Template Header -->
<div class="template-header">
<div class="row">
<div class="col-md-8">
<h2 class="mb-3">{{ template.name }}</h2>
<p class="mb-2">{{ template.description }}</p>
<div class="template-tags">
<span class="template-tag">
<i class="fas fa-user-md me-1"></i>{{ template.get_specialty_display }}
</span>
<span class="template-tag complexity-{{ template.complexity }}">
<i class="fas fa-layer-group me-1"></i>{{ template.get_complexity_display }}
</span>
<span class="template-tag">
<i class="fas fa-clock me-1"></i>{{ template.estimated_duration }} minutes
</span>
{% if template.requires_anesthesia %}
<span class="template-tag">
<i class="fas fa-syringe me-1"></i>Anesthesia Required
</span>
{% endif %}
{% if template.is_emergency_procedure %}
<span class="template-tag">
<i class="fas fa-exclamation-triangle me-1"></i>Emergency Procedure
</span>
{% endif %}
</div>
</div>
<div class="col-md-4 text-end">
<div class="mb-2">
{% if template.status == 'active' %}
<span class="badge bg-success fs-6">Active</span>
{% elif template.status == 'draft' %}
<span class="badge bg-secondary fs-6">Draft</span>
{% elif template.status == 'under_review' %}
<span class="badge bg-warning fs-6">Under Review</span>
{% elif template.status == 'archived' %}
<span class="badge bg-dark fs-6">Archived</span>
{% endif %}
</div>
<div class="text-white-50">
Created by {{ template.created_by.get_full_name }}<br>
{{ template.created_at|date:"M d, Y" }}
</div>
</div>
</div>
</div>
<!-- Usage Statistics -->
<div class="usage-stats">
<h5 class="mb-3">
<i class="fas fa-chart-line me-2"></i>Usage Statistics
</h5>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-value text-primary">{{ usage_stats.total_uses }}</div>
<div class="stat-label">Total Uses</div>
</div>
<div class="stat-item">
<div class="stat-value text-success">{{ usage_stats.this_month }}</div>
<div class="stat-label">This Month</div>
</div>
<div class="stat-item">
<div class="stat-value text-info">{{ usage_stats.avg_duration }}</div>
<div class="stat-label">Avg Duration (min)</div>
</div>
<div class="stat-item">
<div class="stat-value text-warning">{{ usage_stats.success_rate }}%</div>
<div class="stat-label">Success Rate</div>
</div>
</div>
</div>
<!-- Basic Information -->
<div class="info-section">
<div class="section-header">
<i class="fas fa-info-circle me-2"></i>Basic Information
</div>
<div class="section-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Procedure Type</div>
<div class="info-value">{{ template.get_procedure_type_display }}</div>
</div>
<div class="info-item">
<div class="info-label">Estimated Duration</div>
<div class="info-value">{{ template.estimated_duration }} minutes</div>
</div>
<div class="info-item">
<div class="info-label">Required Team Size</div>
<div class="info-value">{{ template.required_team_size }} members</div>
</div>
<div class="info-item">
<div class="info-label">Anesthesia Type</div>
<div class="info-value">
{% if template.anesthesia_type %}
{{ template.get_anesthesia_type_display }}
{% else %}
Not specified
{% endif %}
</div>
</div>
<div class="info-item">
<div class="info-label">Patient Position</div>
<div class="info-value">{{ template.patient_position|default:"Not specified" }}</div>
</div>
<div class="info-item">
<div class="info-label">Room Requirements</div>
<div class="info-value">{{ template.room_requirements|default:"Standard OR" }}</div>
</div>
</div>
</div>
</div>
<!-- Procedure Steps -->
<div class="info-section">
<div class="section-header">
<i class="fas fa-list-ol me-2"></i>Procedure Steps
</div>
<div class="section-content">
{% for step in template.procedure_steps.all %}
<div class="step-item">
<div class="step-header">
<div class="step-number">{{ step.step_number }}</div>
<div class="step-title">{{ step.title }}</div>
<div class="step-duration">{{ step.estimated_duration }} min</div>
</div>
<div class="step-description">
{{ step.description }}
</div>
{% if step.critical_points %}
<div class="mt-2">
<small class="text-danger">
<i class="fas fa-exclamation-triangle me-1"></i>
<strong>Critical Points:</strong> {{ step.critical_points }}
</small>
</div>
{% endif %}
</div>
{% empty %}
<div class="text-center py-3">
<i class="fas fa-list-ol fa-2x text-muted mb-2"></i>
<p class="text-muted">No procedure steps defined</p>
</div>
{% endfor %}
</div>
</div>
<!-- Required Equipment -->
<div class="info-section">
<div class="section-header">
<i class="fas fa-tools me-2"></i>Required Equipment
</div>
<div class="section-content">
{% for equipment in template.equipment_list.all %}
<div class="equipment-item">
<div class="equipment-icon">
<i class="fas fa-tools"></i>
</div>
<div class="equipment-info">
<div class="equipment-name">{{ equipment.name }}</div>
<div class="equipment-details">
{{ equipment.category|title }} |
Quantity: {{ equipment.quantity|default:1 }} |
{% if equipment.is_critical %}
<span class="text-danger">Critical</span>
{% else %}
<span class="text-muted">Standard</span>
{% endif %}
</div>
</div>
{% if equipment.alternatives %}
<div class="equipment-alternatives">
<small class="text-muted">
<i class="fas fa-exchange-alt me-1"></i>
Alternatives: {{ equipment.alternatives }}
</small>
</div>
{% endif %}
</div>
{% empty %}
<div class="text-center py-3">
<i class="fas fa-tools fa-2x text-muted mb-2"></i>
<p class="text-muted">No equipment requirements defined</p>
</div>
{% endfor %}
</div>
</div>
<!-- Required Team -->
<div class="info-section">
<div class="section-header">
<i class="fas fa-users me-2"></i>Required Team
</div>
<div class="section-content">
{% for member in template.team_requirements.all %}
<div class="team-member">
<div class="member-avatar">
{{ member.role|first|upper }}
</div>
<div class="member-info">
<div class="member-role">{{ member.get_role_display }}</div>
<div class="member-requirements">
{% if member.specialization %}
Specialization: {{ member.specialization }} |
{% endif %}
Experience: {{ member.min_experience_years|default:"Any" }} years
{% if member.certifications_required %}
| Certifications: {{ member.certifications_required }}
{% endif %}
</div>
</div>
<div class="member-count">
<span class="badge bg-primary">{{ member.count|default:1 }}</span>
</div>
</div>
{% empty %}
<div class="text-center py-3">
<i class="fas fa-users fa-2x text-muted mb-2"></i>
<p class="text-muted">No team requirements defined</p>
</div>
{% endfor %}
</div>
</div>
<!-- Pre-operative Requirements -->
{% if template.preop_requirements %}
<div class="info-section">
<div class="section-header">
<i class="fas fa-clipboard-check me-2"></i>Pre-operative Requirements
</div>
<div class="section-content">
<div class="row">
<div class="col-md-6">
<h6>Patient Preparation</h6>
<ul class="list-unstyled">
{% for req in template.preop_requirements.patient_prep %}
<li class="mb-1">
<i class="fas fa-check text-success me-2"></i>{{ req }}
</li>
{% endfor %}
</ul>
</div>
<div class="col-md-6">
<h6>Required Tests</h6>
<ul class="list-unstyled">
{% for test in template.preop_requirements.required_tests %}
<li class="mb-1">
<i class="fas fa-vial text-info me-2"></i>{{ test }}
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Post-operative Care -->
{% if template.postop_care %}
<div class="info-section">
<div class="section-header">
<i class="fas fa-heart me-2"></i>Post-operative Care
</div>
<div class="section-content">
<div class="row">
<div class="col-md-6">
<h6>Immediate Care</h6>
<ul class="list-unstyled">
{% for care in template.postop_care.immediate %}
<li class="mb-1">
<i class="fas fa-arrow-right text-primary me-2"></i>{{ care }}
</li>
{% endfor %}
</ul>
</div>
<div class="col-md-6">
<h6>Recovery Instructions</h6>
<ul class="list-unstyled">
{% for instruction in template.postop_care.recovery %}
<li class="mb-1">
<i class="fas fa-info-circle text-warning me-2"></i>{{ instruction }}
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Notes and Warnings -->
{% if template.notes or template.warnings %}
<div class="info-section">
<div class="section-header">
<i class="fas fa-sticky-note me-2"></i>Additional Information
</div>
<div class="section-content">
{% if template.warnings %}
<div class="alert alert-warning">
<h6 class="alert-heading">
<i class="fas fa-exclamation-triangle me-2"></i>Warnings
</h6>
<p class="mb-0">{{ template.warnings }}</p>
</div>
{% endif %}
{% if template.notes %}
<div class="alert alert-info">
<h6 class="alert-heading">
<i class="fas fa-info-circle me-2"></i>Notes
</h6>
<p class="mb-0">{{ template.notes }}</p>
</div>
{% endif %}
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
<script>
function useTemplate() {
if (confirm('Use this template to create a new surgical case?')) {
window.location.href = `/operating_theatre/cases/create/?template={{ template.pk }}`;
}
}
// Print functionality
$(document).keydown(function(e) {
// Ctrl/Cmd + P for print
if ((e.ctrlKey || e.metaKey) && e.keyCode === 80) {
e.preventDefault();
window.print();
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,816 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}
{% if template.pk %}Edit Template - {{ template.name }}{% else %}Create Surgical Template{% endif %}
{% endblock %}
{% block extra_css %}
<style>
.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;
display: flex;
align-items: center;
justify-content: between;
}
.section-content {
padding: 1.5rem;
}
.step-builder {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
margin-bottom: 1rem;
}
.step-header {
background: #f8f9fa;
padding: 1rem;
border-bottom: 1px solid #dee2e6;
display: flex;
align-items: center;
justify-content: between;
}
.step-number {
background: #007bff;
color: white;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 0.875rem;
}
.step-content {
padding: 1rem;
}
.equipment-item {
border: 1px solid #dee2e6;
border-radius: 0.25rem;
padding: 1rem;
margin-bottom: 1rem;
background: #f8f9fa;
}
.team-member-item {
border: 1px solid #dee2e6;
border-radius: 0.25rem;
padding: 1rem;
margin-bottom: 1rem;
background: white;
}
.duration-calculator {
background: #e7f3ff;
border: 1px solid #b3d9ff;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1rem;
}
.preview-section {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.form-actions {
background: white;
border-top: 1px solid #dee2e6;
padding: 1.5rem;
position: sticky;
bottom: 0;
z-index: 100;
}
.complexity-indicator {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
}
.complexity-simple { background: #d4edda; color: #155724; }
.complexity-moderate { background: #fff3cd; color: #856404; }
.complexity-complex { background: #f8d7da; color: #721c24; }
.complexity-highly-complex { background: #d1ecf1; color: #0c5460; }
.drag-handle {
cursor: move;
color: #6c757d;
}
.drag-handle:hover {
color: #495057;
}
@media (max-width: 768px) {
.section-content {
padding: 1rem;
}
.step-header {
flex-direction: column;
align-items: flex-start;
}
.form-actions {
position: static;
}
}
</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:template_list' %}">Templates</a></li>
<li class="breadcrumb-item active">
{% if template.pk %}Edit Template{% else %}Create Template{% endif %}
</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-file-medical me-2"></i>
{% if template.pk %}Edit Template{% else %}Create Surgical Template{% endif %}
</h1>
</div>
<div class="ms-auto">
<button type="button" class="btn btn-outline-secondary me-2" onclick="previewTemplate()">
<i class="fas fa-eye me-1"></i>Preview
</button>
<a href="{% url 'operating_theatre:template_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>Back to List
</a>
</div>
</div>
<form method="post" id="templateForm">
{% csrf_token %}
<!-- Basic Information -->
<div class="form-section">
<div class="section-header">
<div>
<i class="fas fa-info-circle me-2"></i>Basic Information
</div>
</div>
<div class="section-content">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Template Name *</label>
{{ form.name }}
{% if form.name.errors %}
<div class="text-danger small">{{ form.name.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Specialty *</label>
{{ form.specialty }}
{% if form.specialty.errors %}
<div class="text-danger small">{{ form.specialty.errors.0 }}</div>
{% endif %}
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
{{ form.description }}
{% if form.description.errors %}
<div class="text-danger small">{{ form.description.errors.0 }}</div>
{% endif %}
</div>
<div class="row">
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Procedure Type</label>
{{ form.procedure_type }}
{% if form.procedure_type.errors %}
<div class="text-danger small">{{ form.procedure_type.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Complexity</label>
{{ form.complexity }}
{% if form.complexity.errors %}
<div class="text-danger small">{{ form.complexity.errors.0 }}</div>
{% endif %}
<div id="complexityIndicator" class="mt-1"></div>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Status</label>
{{ form.status }}
{% if form.status.errors %}
<div class="text-danger small">{{ form.status.errors.0 }}</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- Duration and Requirements -->
<div class="form-section">
<div class="section-header">
<div>
<i class="fas fa-clock me-2"></i>Duration and Requirements
</div>
</div>
<div class="section-content">
<div class="duration-calculator">
<h6 class="mb-3">
<i class="fas fa-calculator me-2"></i>Duration Calculator
</h6>
<div class="row">
<div class="col-md-3">
<label class="form-label">Estimated Duration (minutes)</label>
{{ form.estimated_duration }}
{% if form.estimated_duration.errors %}
<div class="text-danger small">{{ form.estimated_duration.errors.0 }}</div>
{% endif %}
</div>
<div class="col-md-3">
<label class="form-label">Setup Time (minutes)</label>
<input type="number" class="form-control" id="setupTime" value="15" min="0">
</div>
<div class="col-md-3">
<label class="form-label">Cleanup Time (minutes)</label>
<input type="number" class="form-control" id="cleanupTime" value="10" min="0">
</div>
<div class="col-md-3">
<label class="form-label">Total Time</label>
<input type="text" class="form-control" id="totalTime" readonly>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Required Team Size</label>
{{ form.required_team_size }}
{% if form.required_team_size.errors %}
<div class="text-danger small">{{ form.required_team_size.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Anesthesia Type</label>
{{ form.anesthesia_type }}
{% if form.anesthesia_type.errors %}
<div class="text-danger small">{{ form.anesthesia_type.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Patient Position</label>
{{ form.patient_position }}
{% if form.patient_position.errors %}
<div class="text-danger small">{{ form.patient_position.errors.0 }}</div>
{% endif %}
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-check">
{{ form.requires_anesthesia }}
<label class="form-check-label" for="{{ form.requires_anesthesia.id_for_label }}">
Requires Anesthesia
</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check">
{{ form.is_emergency_procedure }}
<label class="form-check-label" for="{{ form.is_emergency_procedure.id_for_label }}">
Emergency Procedure
</label>
</div>
</div>
</div>
</div>
</div>
<!-- Procedure Steps -->
<div class="form-section">
<div class="section-header">
<div>
<i class="fas fa-list-ol me-2"></i>Procedure Steps
</div>
<button type="button" class="btn btn-sm btn-primary" onclick="addStep()">
<i class="fas fa-plus me-1"></i>Add Step
</button>
</div>
<div class="section-content">
<div id="stepsContainer">
<!-- Steps will be dynamically added here -->
</div>
<div class="text-center py-3" id="noStepsMessage">
<i class="fas fa-list-ol fa-2x text-muted mb-2"></i>
<p class="text-muted">No procedure steps added yet</p>
<button type="button" class="btn btn-outline-primary" onclick="addStep()">
<i class="fas fa-plus me-1"></i>Add First Step
</button>
</div>
</div>
</div>
<!-- Required Equipment -->
<div class="form-section">
<div class="section-header">
<div>
<i class="fas fa-tools me-2"></i>Required Equipment
</div>
<button type="button" class="btn btn-sm btn-primary" onclick="addEquipment()">
<i class="fas fa-plus me-1"></i>Add Equipment
</button>
</div>
<div class="section-content">
<div id="equipmentContainer">
<!-- Equipment will be dynamically added here -->
</div>
<div class="text-center py-3" id="noEquipmentMessage">
<i class="fas fa-tools fa-2x text-muted mb-2"></i>
<p class="text-muted">No equipment requirements added yet</p>
<button type="button" class="btn btn-outline-primary" onclick="addEquipment()">
<i class="fas fa-plus me-1"></i>Add Equipment
</button>
</div>
</div>
</div>
<!-- Required Team -->
<div class="form-section">
<div class="section-header">
<div>
<i class="fas fa-users me-2"></i>Required Team
</div>
<button type="button" class="btn btn-sm btn-primary" onclick="addTeamMember()">
<i class="fas fa-plus me-1"></i>Add Team Member
</button>
</div>
<div class="section-content">
<div id="teamContainer">
<!-- Team members will be dynamically added here -->
</div>
<div class="text-center py-3" id="noTeamMessage">
<i class="fas fa-users fa-2x text-muted mb-2"></i>
<p class="text-muted">No team requirements added yet</p>
<button type="button" class="btn btn-outline-primary" onclick="addTeamMember()">
<i class="fas fa-plus me-1"></i>Add Team Member
</button>
</div>
</div>
</div>
<!-- Additional Information -->
<div class="form-section">
<div class="section-header">
<div>
<i class="fas fa-sticky-note me-2"></i>Additional Information
</div>
</div>
<div class="section-content">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Room Requirements</label>
{{ form.room_requirements }}
{% if form.room_requirements.errors %}
<div class="text-danger small">{{ form.room_requirements.errors.0 }}</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Usage Frequency</label>
{{ form.usage_frequency }}
{% if form.usage_frequency.errors %}
<div class="text-danger small">{{ form.usage_frequency.errors.0 }}</div>
{% endif %}
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Notes</label>
{{ form.notes }}
{% if form.notes.errors %}
<div class="text-danger small">{{ form.notes.errors.0 }}</div>
{% endif %}
</div>
<div class="mb-3">
<label class="form-label">Warnings</label>
{{ form.warnings }}
{% if form.warnings.errors %}
<div class="text-danger small">{{ form.warnings.errors.0 }}</div>
{% endif %}
</div>
</div>
</div>
</form>
<!-- Form Actions -->
<div class="form-actions">
<div class="d-flex justify-content-between align-items-center">
<div>
<button type="button" class="btn btn-outline-secondary" onclick="saveDraft()">
<i class="fas fa-save me-1"></i>Save as Draft
</button>
</div>
<div>
<a href="{% url 'operating_theatre:template_list' %}" class="btn btn-outline-secondary me-2">
<i class="fas fa-times me-1"></i>Cancel
</a>
<button type="submit" form="templateForm" class="btn btn-primary">
<i class="fas fa-check me-1"></i>
{% if template.pk %}Update Template{% else %}Create Template{% endif %}
</button>
</div>
</div>
</div>
</div>
<!-- Preview Modal -->
<div class="modal fade" id="previewModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-eye me-2"></i>Template Preview
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="previewContent">
<!-- Preview content will be loaded here -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
let stepCounter = 0;
let equipmentCounter = 0;
let teamCounter = 0;
$(document).ready(function() {
// Initialize duration calculator
updateTotalTime();
$('#setupTime, #cleanupTime').on('input', updateTotalTime);
$('#id_estimated_duration').on('input', updateTotalTime);
// Initialize complexity indicator
updateComplexityIndicator();
$('#id_complexity').on('change', updateComplexityIndicator);
// Load existing data if editing
{% if template.pk %}
loadExistingData();
{% endif %}
});
function updateTotalTime() {
const procedureTime = parseInt($('#id_estimated_duration').val()) || 0;
const setupTime = parseInt($('#setupTime').val()) || 0;
const cleanupTime = parseInt($('#cleanupTime').val()) || 0;
const total = procedureTime + setupTime + cleanupTime;
$('#totalTime').val(total + ' minutes');
}
function updateComplexityIndicator() {
const complexity = $('#id_complexity').val();
const indicator = $('#complexityIndicator');
indicator.removeClass('complexity-simple complexity-moderate complexity-complex complexity-highly-complex');
if (complexity) {
indicator.addClass('complexity-' + complexity.replace('_', '-'));
indicator.text(complexity.replace('_', ' ').toUpperCase());
indicator.show();
} else {
indicator.hide();
}
}
function addStep() {
stepCounter++;
const stepHtml = `
<div class="step-builder" data-step="${stepCounter}">
<div class="step-header">
<div class="d-flex align-items-center">
<div class="step-number">${stepCounter}</div>
<span class="ms-2 fw-bold">Step ${stepCounter}</span>
</div>
<div>
<button type="button" class="btn btn-sm btn-outline-secondary me-1 drag-handle">
<i class="fas fa-grip-vertical"></i>
</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeStep(${stepCounter})">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<div class="step-content">
<div class="row">
<div class="col-md-8">
<div class="mb-3">
<label class="form-label">Step Title</label>
<input type="text" class="form-control" name="step_${stepCounter}_title" required>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Duration (minutes)</label>
<input type="number" class="form-control" name="step_${stepCounter}_duration" min="1">
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea class="form-control" name="step_${stepCounter}_description" rows="3"></textarea>
</div>
<div class="mb-3">
<label class="form-label">Critical Points</label>
<textarea class="form-control" name="step_${stepCounter}_critical_points" rows="2"
placeholder="Important safety considerations or critical steps..."></textarea>
</div>
</div>
</div>
`;
$('#stepsContainer').append(stepHtml);
$('#noStepsMessage').hide();
}
function removeStep(stepId) {
if (confirm('Remove this step?')) {
$(`.step-builder[data-step="${stepId}"]`).remove();
if ($('#stepsContainer .step-builder').length === 0) {
$('#noStepsMessage').show();
}
// Renumber steps
renumberSteps();
}
}
function renumberSteps() {
$('#stepsContainer .step-builder').each(function(index) {
const newNumber = index + 1;
$(this).find('.step-number').text(newNumber);
$(this).find('.step-header span').text(`Step ${newNumber}`);
});
}
function addEquipment() {
equipmentCounter++;
const equipmentHtml = `
<div class="equipment-item" data-equipment="${equipmentCounter}">
<div class="d-flex justify-content-between align-items-start mb-3">
<h6 class="mb-0">Equipment ${equipmentCounter}</h6>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeEquipment(${equipmentCounter})">
<i class="fas fa-trash"></i>
</button>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Equipment Name</label>
<input type="text" class="form-control" name="equipment_${equipmentCounter}_name" required>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Category</label>
<select class="form-select" name="equipment_${equipmentCounter}_category">
<option value="">Select Category</option>
<option value="surgical_instruments">Surgical Instruments</option>
<option value="monitoring">Monitoring Equipment</option>
<option value="anesthesia">Anesthesia Equipment</option>
<option value="imaging">Imaging Equipment</option>
<option value="electrosurgical">Electrosurgical</option>
<option value="positioning">Positioning Equipment</option>
<option value="other">Other</option>
</select>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Quantity</label>
<input type="number" class="form-control" name="equipment_${equipmentCounter}_quantity" value="1" min="1">
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Alternatives</label>
<input type="text" class="form-control" name="equipment_${equipmentCounter}_alternatives"
placeholder="Alternative equipment options...">
</div>
</div>
<div class="col-md-6">
<div class="form-check mt-4">
<input type="checkbox" class="form-check-input" name="equipment_${equipmentCounter}_critical" value="1">
<label class="form-check-label">Critical Equipment</label>
</div>
</div>
</div>
</div>
`;
$('#equipmentContainer').append(equipmentHtml);
$('#noEquipmentMessage').hide();
}
function removeEquipment(equipmentId) {
if (confirm('Remove this equipment requirement?')) {
$(`.equipment-item[data-equipment="${equipmentId}"]`).remove();
if ($('#equipmentContainer .equipment-item').length === 0) {
$('#noEquipmentMessage').show();
}
}
}
function addTeamMember() {
teamCounter++;
const teamHtml = `
<div class="team-member-item" data-team="${teamCounter}">
<div class="d-flex justify-content-between align-items-start mb-3">
<h6 class="mb-0">Team Member ${teamCounter}</h6>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeTeamMember(${teamCounter})">
<i class="fas fa-trash"></i>
</button>
</div>
<div class="row">
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Role</label>
<select class="form-select" name="team_${teamCounter}_role" required>
<option value="">Select Role</option>
<option value="surgeon">Surgeon</option>
<option value="assistant_surgeon">Assistant Surgeon</option>
<option value="anesthesiologist">Anesthesiologist</option>
<option value="nurse_anesthetist">Nurse Anesthetist</option>
<option value="scrub_nurse">Scrub Nurse</option>
<option value="circulating_nurse">Circulating Nurse</option>
<option value="surgical_technologist">Surgical Technologist</option>
<option value="perfusionist">Perfusionist</option>
<option value="other">Other</option>
</select>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Specialization</label>
<input type="text" class="form-control" name="team_${teamCounter}_specialization"
placeholder="Required specialization...">
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Count</label>
<input type="number" class="form-control" name="team_${teamCounter}_count" value="1" min="1">
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Min Experience (years)</label>
<input type="number" class="form-control" name="team_${teamCounter}_experience" min="0">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Required Certifications</label>
<input type="text" class="form-control" name="team_${teamCounter}_certifications"
placeholder="Required certifications...">
</div>
</div>
</div>
</div>
`;
$('#teamContainer').append(teamHtml);
$('#noTeamMessage').hide();
}
function removeTeamMember(teamId) {
if (confirm('Remove this team member requirement?')) {
$(`.team-member-item[data-team="${teamId}"]`).remove();
if ($('#teamContainer .team-member-item').length === 0) {
$('#noTeamMessage').show();
}
}
}
function previewTemplate() {
// Collect form data and show preview
const formData = new FormData($('#templateForm')[0]);
$.ajax({
url: '{% url "operating_theatre:template_preview" %}',
method: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
$('#previewContent').html(response.html);
$('#previewModal').modal('show');
},
error: function() {
alert('Error generating preview');
}
});
}
function saveDraft() {
$('#id_status').val('draft');
$('#templateForm').submit();
}
function loadExistingData() {
// Load existing steps, equipment, and team data
// This would be populated from the backend
}
// Form validation
$('#templateForm').on('submit', function(e) {
let isValid = true;
// Validate required fields
$(this).find('[required]').each(function() {
if (!$(this).val()) {
isValid = false;
$(this).addClass('is-invalid');
} else {
$(this).removeClass('is-invalid');
}
});
if (!isValid) {
e.preventDefault();
alert('Please fill in all required fields');
}
});
// Auto-save functionality
let autoSaveTimer;
$('#templateForm input, #templateForm textarea, #templateForm select').on('input change', function() {
clearTimeout(autoSaveTimer);
autoSaveTimer = setTimeout(function() {
// Auto-save logic here
console.log('Auto-saving...');
}, 30000); // Auto-save every 30 seconds
});
</script>
{% endblock %}

View File

@ -0,0 +1,602 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Surgical Templates{% endblock %}
{% block extra_css %}
<style>
.stats-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.stat-item {
background: white;
border-radius: 0.375rem;
padding: 1.5rem;
text-align: center;
border: 1px solid #dee2e6;
}
.stat-value {
font-size: 2rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.stat-label {
color: #6c757d;
font-size: 0.875rem;
}
.template-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1rem;
transition: all 0.3s ease;
}
.template-card:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.template-header {
display: flex;
justify-content: between;
align-items: flex-start;
margin-bottom: 1rem;
}
.template-title {
font-size: 1.25rem;
font-weight: 600;
color: #495057;
margin-bottom: 0.5rem;
}
.template-meta {
font-size: 0.875rem;
color: #6c757d;
margin-bottom: 1rem;
}
.template-tags {
margin-bottom: 1rem;
}
.template-tag {
display: inline-block;
background: #e9ecef;
color: #495057;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
margin-right: 0.5rem;
margin-bottom: 0.25rem;
}
.template-actions {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.filter-section {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.search-section {
background: #f8f9fa;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1.5rem;
}
.template-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 1rem;
}
.usage-indicator {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
}
.usage-high {
background: #d4edda;
color: #155724;
}
.usage-medium {
background: #fff3cd;
color: #856404;
}
.usage-low {
background: #f8d7da;
color: #721c24;
}
@media (max-width: 768px) {
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.template-grid {
grid-template-columns: 1fr;
}
.template-header {
flex-direction: column;
}
.template-actions {
margin-top: 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 active">Surgical Templates</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-file-medical me-2"></i>Surgical Templates
</h1>
</div>
<div class="ms-auto">
<button class="btn btn-outline-secondary me-2" onclick="exportTemplates()">
<i class="fas fa-download me-1"></i>Export
</button>
<a href="{% url 'operating_theatre:template_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Create Template
</a>
</div>
</div>
<!-- Statistics Cards -->
<div class="stats-card">
<div class="row">
<div class="col-md-8">
<h3 class="mb-2">Surgical Template Management</h3>
<p class="mb-0">Manage standardized surgical procedure templates to ensure consistency and efficiency in the operating theatre.</p>
</div>
<div class="col-md-4 text-end">
<i class="fas fa-file-medical fa-3x opacity-50"></i>
</div>
</div>
</div>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-value text-primary">{{ stats.total_templates }}</div>
<div class="stat-label">Total Templates</div>
</div>
<div class="stat-item">
<div class="stat-value text-success">{{ stats.active_templates }}</div>
<div class="stat-label">Active Templates</div>
</div>
<div class="stat-item">
<div class="stat-value text-info">{{ stats.used_this_month }}</div>
<div class="stat-label">Used This Month</div>
</div>
<div class="stat-item">
<div class="stat-value text-warning">{{ stats.pending_review }}</div>
<div class="stat-label">Pending Review</div>
</div>
</div>
<!-- Search and Filters -->
<div class="search-section">
<div class="row">
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-search"></i>
</span>
<input type="text" class="form-control" id="searchInput"
placeholder="Search templates by name, procedure, or specialty...">
</div>
</div>
<div class="col-md-6">
<div class="d-flex gap-2">
<button class="btn btn-outline-primary" data-bs-toggle="collapse" data-bs-target="#filterSection">
<i class="fas fa-filter me-1"></i>Filters
</button>
<button class="btn btn-outline-secondary" onclick="clearFilters()">
<i class="fas fa-times me-1"></i>Clear
</button>
</div>
</div>
</div>
</div>
<!-- Filter Section -->
<div class="collapse" id="filterSection">
<div class="filter-section">
<div class="row">
<div class="col-md-3">
<label class="form-label">Specialty</label>
<select class="form-select" id="specialtyFilter">
<option value="">All Specialties</option>
<option value="general_surgery">General Surgery</option>
<option value="orthopedic">Orthopedic</option>
<option value="cardiac">Cardiac</option>
<option value="neurosurgery">Neurosurgery</option>
<option value="plastic">Plastic Surgery</option>
<option value="urology">Urology</option>
<option value="gynecology">Gynecology</option>
<option value="ophthalmology">Ophthalmology</option>
<option value="ent">ENT</option>
<option value="vascular">Vascular</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Status</label>
<select class="form-select" id="statusFilter">
<option value="">All Statuses</option>
<option value="active">Active</option>
<option value="draft">Draft</option>
<option value="under_review">Under Review</option>
<option value="archived">Archived</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Complexity</label>
<select class="form-select" id="complexityFilter">
<option value="">All Complexities</option>
<option value="simple">Simple</option>
<option value="moderate">Moderate</option>
<option value="complex">Complex</option>
<option value="highly_complex">Highly Complex</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Duration Range</label>
<select class="form-select" id="durationFilter">
<option value="">All Durations</option>
<option value="0-60">0-60 minutes</option>
<option value="60-120">1-2 hours</option>
<option value="120-240">2-4 hours</option>
<option value="240+">4+ hours</option>
</select>
</div>
</div>
<div class="row mt-3">
<div class="col-md-12">
<button class="btn btn-primary" onclick="applyFilters()">
<i class="fas fa-check me-1"></i>Apply Filters
</button>
</div>
</div>
</div>
</div>
<!-- Templates Grid -->
<div class="template-grid" id="templatesContainer">
{% for template in templates %}
<div class="template-card" data-template-id="{{ template.id }}">
<div class="template-header">
<div class="flex-grow-1">
<div class="template-title">{{ template.name }}</div>
<div class="template-meta">
<i class="fas fa-user-md me-1"></i>{{ template.specialty|title }} |
<i class="fas fa-clock me-1"></i>{{ template.estimated_duration }} min |
<i class="fas fa-calendar me-1"></i>Created {{ template.created_at|date:"M d, Y" }}
</div>
</div>
<div class="ms-2">
{% if template.status == 'active' %}
<span class="badge bg-success">Active</span>
{% elif template.status == 'draft' %}
<span class="badge bg-secondary">Draft</span>
{% elif template.status == 'under_review' %}
<span class="badge bg-warning">Under Review</span>
{% elif template.status == 'archived' %}
<span class="badge bg-dark">Archived</span>
{% endif %}
</div>
</div>
<div class="template-description mb-3">
{{ template.description|truncatewords:20 }}
</div>
<div class="template-tags">
<span class="template-tag">
<i class="fas fa-layer-group me-1"></i>{{ template.get_complexity_display }}
</span>
{% if template.requires_anesthesia %}
<span class="template-tag">
<i class="fas fa-syringe me-1"></i>Anesthesia Required
</span>
{% endif %}
{% if template.is_emergency_procedure %}
<span class="template-tag">
<i class="fas fa-exclamation-triangle me-1"></i>Emergency
</span>
{% endif %}
<span class="template-tag usage-{{ template.usage_frequency|lower }}">
<i class="fas fa-chart-line me-1"></i>{{ template.get_usage_frequency_display }} Usage
</span>
</div>
<div class="row mb-3">
<div class="col-6">
<small class="text-muted">Equipment Items:</small>
<div class="fw-bold">{{ template.equipment_list.count }}</div>
</div>
<div class="col-6">
<small class="text-muted">Team Members:</small>
<div class="fw-bold">{{ template.required_team_size }}</div>
</div>
</div>
<div class="template-actions">
<a href="{% url 'operating_theatre:template_detail' template.pk %}" class="btn btn-outline-primary btn-sm">
<i class="fas fa-eye me-1"></i>View
</a>
<a href="{% url 'operating_theatre:template_edit' template.pk %}" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-edit me-1"></i>Edit
</a>
<button class="btn btn-outline-success btn-sm" onclick="useTemplate('{{ template.pk }}')">
<i class="fas fa-play me-1"></i>Use
</button>
<button class="btn btn-outline-info btn-sm" onclick="duplicateTemplate('{{ template.pk }}')">
<i class="fas fa-copy me-1"></i>Duplicate
</button>
<div class="dropdown d-inline">
<button class="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-h"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{% url 'operating_theatre:template_export' template.pk %}">
<i class="fas fa-download me-2"></i>Export
</a></li>
<li><a class="dropdown-item" href="{% url 'operating_theatre:template_share' template.pk %}">
<i class="fas fa-share me-2"></i>Share
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="{% url 'operating_theatre:template_delete' template.pk %}">
<i class="fas fa-trash me-2"></i>Delete
</a></li>
</ul>
</div>
</div>
</div>
{% empty %}
<div class="col-12">
<div class="text-center py-5">
<i class="fas fa-file-medical fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No surgical templates found</h5>
<p class="text-muted">Create your first surgical template to standardize procedures.</p>
<a href="{% url 'operating_theatre:template_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>Create Template
</a>
</div>
</div>
{% endfor %}
</div>
<!-- Pagination -->
{% if is_paginated %}
<nav aria-label="Templates pagination" class="mt-4">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1">&laquo; First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">Previous</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">Last &raquo;</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
<script>
$(document).ready(function() {
// Initialize search
$('#searchInput').on('input', function() {
filterTemplates();
});
// Load statistics
loadStats();
});
function loadStats() {
$.ajax({
url: '{% url "operating_theatre:template_stats" %}',
success: function(data) {
$('.stat-item:nth-child(1) .stat-value').text(data.total_templates);
$('.stat-item:nth-child(2) .stat-value').text(data.active_templates);
$('.stat-item:nth-child(3) .stat-value').text(data.used_this_month);
$('.stat-item:nth-child(4) .stat-value').text(data.pending_review);
}
});
}
function filterTemplates() {
const searchTerm = $('#searchInput').val().toLowerCase();
const specialty = $('#specialtyFilter').val();
const status = $('#statusFilter').val();
const complexity = $('#complexityFilter').val();
const duration = $('#durationFilter').val();
$('.template-card').each(function() {
const card = $(this);
const title = card.find('.template-title').text().toLowerCase();
const description = card.find('.template-description').text().toLowerCase();
const cardSpecialty = card.data('specialty');
const cardStatus = card.data('status');
const cardComplexity = card.data('complexity');
const cardDuration = card.data('duration');
let show = true;
// Search filter
if (searchTerm && !title.includes(searchTerm) && !description.includes(searchTerm)) {
show = false;
}
// Specialty filter
if (specialty && cardSpecialty !== specialty) {
show = false;
}
// Status filter
if (status && cardStatus !== status) {
show = false;
}
// Complexity filter
if (complexity && cardComplexity !== complexity) {
show = false;
}
// Duration filter
if (duration && !matchesDurationRange(cardDuration, duration)) {
show = false;
}
card.toggle(show);
});
}
function matchesDurationRange(duration, range) {
const durationNum = parseInt(duration);
switch(range) {
case '0-60':
return durationNum <= 60;
case '60-120':
return durationNum > 60 && durationNum <= 120;
case '120-240':
return durationNum > 120 && durationNum <= 240;
case '240+':
return durationNum > 240;
default:
return true;
}
}
function applyFilters() {
filterTemplates();
$('#filterSection').collapse('hide');
}
function clearFilters() {
$('#searchInput').val('');
$('#specialtyFilter').val('');
$('#statusFilter').val('');
$('#complexityFilter').val('');
$('#durationFilter').val('');
filterTemplates();
}
function useTemplate(templateId) {
if (confirm('Use this template to create a new surgical case?')) {
window.location.href = `/operating_theatre/cases/create/?template=${templateId}`;
}
}
function duplicateTemplate(templateId) {
if (confirm('Create a copy of this template?')) {
$.ajax({
url: `{% url "operating_theatre:template_duplicate" "0" %}`.replace('0', templateId),
method: 'POST',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
alert('Template duplicated successfully!');
window.location.href = response.edit_url;
} else {
alert('Error duplicating template: ' + response.error);
}
},
error: function() {
alert('Error duplicating template');
}
});
}
}
function exportTemplates() {
const selectedTemplates = [];
$('.template-card input[type="checkbox"]:checked').each(function() {
selectedTemplates.push($(this).closest('.template-card').data('template-id'));
});
if (selectedTemplates.length === 0) {
// Export all visible templates
window.location.href = '{% url "operating_theatre:templates_export" %}';
} else {
// Export selected templates
window.location.href = `{% url "operating_theatre:templates_export" %}?ids=${selectedTemplates.join(',')}`;
}
}
// Keyboard shortcuts
$(document).keydown(function(e) {
// Ctrl/Cmd + K for search
if ((e.ctrlKey || e.metaKey) && e.keyCode === 75) {
e.preventDefault();
$('#searchInput').focus();
}
// Ctrl/Cmd + N for new template
if ((e.ctrlKey || e.metaKey) && e.keyCode === 78) {
e.preventDefault();
window.location.href = '{% url "operating_theatre:template_create" %}';
}
});
</script>
{% endblock %}

File diff suppressed because it is too large Load Diff