410 lines
23 KiB
HTML
410 lines
23 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Appointment Requests - Appointments{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Breadcrumb -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="page-title-box d-sm-flex align-items-center justify-content-between">
|
|
<h4 class="mb-sm-0">Appointment Requests</h4>
|
|
<div class="page-title-right">
|
|
<ol class="breadcrumb m-0">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'appointments:dashboard' %}">Appointments</a></li>
|
|
<li class="breadcrumb-item active">Requests</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Bar -->
|
|
<div class="row mb-3">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-body py-2">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div class="d-flex gap-2">
|
|
<a href="{% url 'appointments:appointment_request_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-1"></i>
|
|
New Request
|
|
</a>
|
|
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="collapse" data-bs-target="#filterCollapse">
|
|
<i class="fas fa-filter me-1"></i>
|
|
Filters
|
|
</button>
|
|
<button type="button" class="btn btn-outline-success" onclick="exportData()">
|
|
<i class="fas fa-download me-1"></i>
|
|
Export
|
|
</button>
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
<div class="input-group" style="width: 300px;">
|
|
<input type="text" class="form-control" placeholder="Search requests..."
|
|
id="searchInput" value="{{ request.GET.search }}">
|
|
<button class="btn btn-outline-secondary" type="button" onclick="performSearch()">
|
|
<i class="fas fa-search"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="collapse {% if request.GET %}show{% endif %}" id="filterCollapse">
|
|
<div class="row mb-3">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<form method="get" id="filterForm">
|
|
<div class="row g-3">
|
|
<div class="col-md-3">
|
|
{{ search_form.appointment_type }}
|
|
</div>
|
|
<div class="col-md-3">
|
|
{{ search_form.status }}
|
|
</div>
|
|
<div class="col-md-3">
|
|
{{ search_form.priority }}
|
|
</div>
|
|
<div class="col-md-3">
|
|
{{ search_form.provider }}
|
|
</div>
|
|
<div class="col-md-3">
|
|
{{ search_form.date_from }}
|
|
</div>
|
|
<div class="col-md-3">
|
|
{{ search_form.date_to }}
|
|
</div>
|
|
<div class="col-md-3">
|
|
{{ search_form.department }}
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="form-check mt-2">
|
|
{{ search_form.is_telemedicine }}
|
|
<label class="form-check-label" for="{{ search_form.is_telemedicine.id_for_label }}">
|
|
Telemedicine Only
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-12">
|
|
<div class="d-flex gap-2">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-filter me-1"></i>
|
|
Apply Filters
|
|
</button>
|
|
<a href="{% url 'appointments:appointment_request_list' %}" class="btn btn-secondary">
|
|
<i class="fas fa-times me-1"></i>
|
|
Clear
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Appointment Requests List -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-calendar-alt me-2"></i>
|
|
Appointment Requests
|
|
<span class="badge bg-primary ms-2">{{ object_list.count }}</span>
|
|
</h5>
|
|
<div class="d-flex gap-2">
|
|
<div class="btn-group" role="group">
|
|
<input type="radio" class="btn-check" name="viewMode" id="listView" checked>
|
|
<label class="btn btn-outline-secondary btn-sm" for="listView">
|
|
<i class="fas fa-list"></i>
|
|
</label>
|
|
<input type="radio" class="btn-check" name="viewMode" id="cardView">
|
|
<label class="btn btn-outline-secondary btn-sm" for="cardView">
|
|
<i class="fas fa-th"></i>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
{% if object_list %}
|
|
<!-- Table View -->
|
|
<div id="tableView" class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="selectAll">
|
|
</div>
|
|
</th>
|
|
<th>Patient</th>
|
|
<th>Provider</th>
|
|
<th>Date & Time</th>
|
|
<th>Type</th>
|
|
<th>Status</th>
|
|
<th>Priority</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for appointment in object_list %}
|
|
<tr>
|
|
<td>
|
|
<div class="form-check">
|
|
<input class="form-check-input row-select" type="checkbox" value="{{ appointment.pk }}">
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<div class="avatar-sm me-2">
|
|
<div class="avatar-title bg-light text-primary rounded-circle">
|
|
{{ appointment.patient.first_name.0 }}{{ appointment.patient.last_name.0 }}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h6 class="mb-0">{{ appointment.patient.get_full_name }}</h6>
|
|
<small class="text-muted">{{ appointment.patient.patient_id }}</small>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div>
|
|
<h6 class="mb-0">{{ appointment.provider.get_full_name }}</h6>
|
|
<small class="text-muted">{{ appointment.department|default:"General" }}</small>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div>
|
|
<h6 class="mb-0">{{ appointment.scheduled_datetime|date:"M d, Y" }}</h6>
|
|
<small class="text-muted">{{ appointment.scheduled_datetime|time:"g:i A" }}</small>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-info">{{ appointment.get_appointment_type_display }}</span>
|
|
{% if appointment.is_telemedicine %}
|
|
<br><small class="text-info"><i class="fas fa-video me-1"></i>Virtual</small>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if appointment.status == 'scheduled' %}primary{% elif appointment.status == 'completed' %}success{% elif appointment.status == 'cancelled' %}danger{% else %}warning{% endif %}">
|
|
{{ appointment.get_status_display }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if appointment.priority == 'high' %}danger{% elif appointment.priority == 'medium' %}warning{% else %}success{% endif %}">
|
|
{{ appointment.get_priority_display }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<div class="dropdown">
|
|
<button class="btn btn-sm btn-outline-secondary 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 'appointments:appointment_request_detail' appointment.pk %}">
|
|
<i class="fas fa-eye me-2"></i>View Details
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="{% url 'appointments:appointment_request_update' appointment.pk %}">
|
|
<i class="fas fa-edit me-2"></i>Edit
|
|
</a></li>
|
|
{% if appointment.status == 'scheduled' %}
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item text-success" href="#">
|
|
<i class="fas fa-check me-2"></i>Check In
|
|
</a></li>
|
|
<li><a class="dropdown-item text-warning" href="#">
|
|
<i class="fas fa-clock me-2"></i>Reschedule
|
|
</a></li>
|
|
{% endif %}
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item text-danger" href="{% url 'appointments:appointment_request_delete' appointment.pk %}">
|
|
<i class="fas fa-trash me-2"></i>Delete
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Card View (Hidden by default) -->
|
|
<div id="cardView" class="row g-3 p-3" style="display: none;">
|
|
{% for appointment in object_list %}
|
|
<div class="col-md-6 col-lg-4">
|
|
<div class="card h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
<div class="form-check">
|
|
<input class="form-check-input row-select" type="checkbox" value="{{ appointment.pk }}">
|
|
</div>
|
|
<span class="badge bg-{% if appointment.status == 'scheduled' %}primary{% elif appointment.status == 'completed' %}success{% elif appointment.status == 'cancelled' %}danger{% else %}warning{% endif %}">
|
|
{{ appointment.get_status_display }}
|
|
</span>
|
|
</div>
|
|
<h6 class="card-title">{{ appointment.patient.get_full_name }}</h6>
|
|
<p class="card-text">
|
|
<small class="text-muted">{{ appointment.patient.patient_id }}</small><br>
|
|
<strong>Provider:</strong> {{ appointment.provider.get_full_name }}<br>
|
|
<strong>Date:</strong> {{ appointment.scheduled_datetime|date:"M d, Y" }}<br>
|
|
<strong>Time:</strong> {{ appointment.scheduled_datetime|time:"g:i A" }}<br>
|
|
<strong>Type:</strong> {{ appointment.get_appointment_type_display }}
|
|
{% if appointment.is_telemedicine %}
|
|
<i class="fas fa-video text-info ms-1"></i>
|
|
{% endif %}
|
|
</p>
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<span class="badge bg-{% if appointment.priority == 'high' %}danger{% elif appointment.priority == 'medium' %}warning{% else %}success{% endif %}">
|
|
{{ appointment.get_priority_display }}
|
|
</span>
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="{% url 'appointments:appointment_request_detail' appointment.pk %}" class="btn btn-outline-primary">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
<a href="{% url 'appointments:appointment_request_update' appointment.pk %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-edit"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-calendar-times fa-3x text-muted mb-3"></i>
|
|
<h5 class="text-muted">No Appointment Requests Found</h5>
|
|
<p class="text-muted">No appointment requests match your current filters.</p>
|
|
<a href="{% url 'appointments:appointment_request_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-1"></i>
|
|
Create First Request
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if is_paginated %}
|
|
<div class="card-footer">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ page_obj.paginator.count }} entries
|
|
</div>
|
|
<nav>
|
|
<ul class="pagination mb-0">
|
|
{% 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 }}</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>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// View mode toggle
|
|
const listViewBtn = document.getElementById('listView');
|
|
const cardViewBtn = document.getElementById('cardView');
|
|
const tableView = document.getElementById('tableView');
|
|
const cardView = document.getElementById('cardView');
|
|
|
|
listViewBtn.addEventListener('change', function() {
|
|
if (this.checked) {
|
|
tableView.style.display = 'block';
|
|
cardView.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
cardViewBtn.addEventListener('change', function() {
|
|
if (this.checked) {
|
|
tableView.style.display = 'none';
|
|
cardView.style.display = 'block';
|
|
}
|
|
});
|
|
|
|
// Select all functionality
|
|
const selectAllCheckbox = document.getElementById('selectAll');
|
|
const rowCheckboxes = document.querySelectorAll('.row-select');
|
|
|
|
selectAllCheckbox.addEventListener('change', function() {
|
|
rowCheckboxes.forEach(checkbox => {
|
|
checkbox.checked = this.checked;
|
|
});
|
|
});
|
|
|
|
// Search functionality
|
|
const searchInput = document.getElementById('searchInput');
|
|
searchInput.addEventListener('keypress', function(e) {
|
|
if (e.key === 'Enter') {
|
|
performSearch();
|
|
}
|
|
});
|
|
});
|
|
|
|
function performSearch() {
|
|
const searchTerm = document.getElementById('searchInput').value;
|
|
const url = new URL(window.location);
|
|
if (searchTerm) {
|
|
url.searchParams.set('search', searchTerm);
|
|
} else {
|
|
url.searchParams.delete('search');
|
|
}
|
|
url.searchParams.delete('page'); // Reset to first page
|
|
window.location.href = url.toString();
|
|
}
|
|
|
|
function exportData() {
|
|
const selectedIds = Array.from(document.querySelectorAll('.row-select:checked')).map(cb => cb.value);
|
|
const params = new URLSearchParams(window.location.search);
|
|
|
|
if (selectedIds.length > 0) {
|
|
params.set('selected_ids', selectedIds.join(','));
|
|
}
|
|
|
|
params.set('export', 'csv');
|
|
window.location.href = '?' + params.toString();
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|