hospital-management/templates/core/global_search.html
2025-08-12 13:33:25 +03:00

583 lines
27 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}Global Search{% endblock %}
{% block css %}
<link href="{% static 'assets/plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<div id="content" class="app-content">
<div class="container">
<ul class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item active">Global Search</li>
</ul>
<div class="row align-items-center mb-3">
<div class="col">
<h1 class="page-header">Global Search</h1>
<p class="text-muted">Search across all system modules and data</p>
</div>
</div>
<!-- Search Interface -->
<div class="card mb-4">
<div class="card-body">
<form id="globalSearchForm" class="row g-3">
<div class="col-md-6">
<label class="form-label">Search Query</label>
<div class="input-group">
<input type="text" name="query" class="form-control form-control-lg"
placeholder="Search patients, appointments, bills, medications..."
value="{{ request.GET.query }}" autofocus>
<button type="submit" class="btn btn-primary">
<i class="fa fa-search me-2"></i>Search
</button>
</div>
</div>
<div class="col-md-3">
<label class="form-label">Search In</label>
<select name="modules" class="form-select" multiple>
<option value="patients" {% if 'patients' in selected_modules %}selected{% endif %}>Patients</option>
<option value="appointments" {% if 'appointments' in selected_modules %}selected{% endif %}>Appointments</option>
<option value="billing" {% if 'billing' in selected_modules %}selected{% endif %}>Billing</option>
<option value="pharmacy" {% if 'pharmacy' in selected_modules %}selected{% endif %}>Pharmacy</option>
<option value="laboratory" {% if 'laboratory' in selected_modules %}selected{% endif %}>Laboratory</option>
<option value="radiology" {% if 'radiology' in selected_modules %}selected{% endif %}>Radiology</option>
<option value="inpatients" {% if 'inpatients' in selected_modules %}selected{% endif %}>Inpatients</option>
<option value="hr" {% if 'hr' in selected_modules %}selected{% endif %}>HR</option>
<option value="inventory" {% if 'inventory' in selected_modules %}selected{% endif %}>Inventory</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Date Range</label>
<select name="date_range" class="form-select">
<option value="">Any time</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="year" {% if request.GET.date_range == 'year' %}selected{% endif %}>This year</option>
</select>
</div>
</form>
<!-- Advanced Search Toggle -->
<div class="mt-3">
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="toggleAdvancedSearch()">
<i class="fa fa-cog me-2"></i>Advanced Search
</button>
</div>
<!-- Advanced Search Options -->
<div id="advancedSearch" class="mt-3" style="display: none;">
<div class="row g-3">
<div class="col-md-3">
<label class="form-label">Exact Phrase</label>
<input type="text" name="exact_phrase" class="form-control"
placeholder="Exact phrase" value="{{ request.GET.exact_phrase }}">
</div>
<div class="col-md-3">
<label class="form-label">Exclude Words</label>
<input type="text" name="exclude" class="form-control"
placeholder="Words to exclude" value="{{ request.GET.exclude }}">
</div>
<div class="col-md-3">
<label class="form-label">File Type</label>
<select name="file_type" class="form-select">
<option value="">Any file type</option>
<option value="pdf" {% if request.GET.file_type == 'pdf' %}selected{% endif %}>PDF</option>
<option value="doc" {% if request.GET.file_type == 'doc' %}selected{% endif %}>Word Document</option>
<option value="xls" {% if request.GET.file_type == 'xls' %}selected{% endif %}>Excel</option>
<option value="img" {% if request.GET.file_type == 'img' %}selected{% endif %}>Images</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Sort By</label>
<select name="sort_by" class="form-select">
<option value="relevance" {% if request.GET.sort_by == 'relevance' %}selected{% endif %}>Relevance</option>
<option value="date" {% if request.GET.sort_by == 'date' %}selected{% endif %}>Date</option>
<option value="name" {% if request.GET.sort_by == 'name' %}selected{% endif %}>Name</option>
<option value="type" {% if request.GET.sort_by == 'type' %}selected{% endif %}>Type</option>
</select>
</div>
</div>
</div>
</div>
</div>
{% if query %}
<!-- Search Results Summary -->
<div class="row mb-4">
<div class="col-md-12">
<div class="alert alert-info">
<i class="fa fa-info-circle me-2"></i>
Found <strong>{{ total_results }}</strong> results for "<strong>{{ query }}</strong>"
{% if search_time %}in {{ search_time }}ms{% endif %}
</div>
</div>
</div>
<!-- Results by Category -->
<div class="row">
<div class="col-md-3">
<!-- Search Filters -->
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title">Filter Results</h5>
</div>
<div class="card-body">
<div class="mb-3">
<h6>By Module</h6>
{% for module, count in results_by_module.items %}
<div class="form-check">
<input class="form-check-input module-filter" type="checkbox"
value="{{ module }}" {% if module in selected_modules %}checked{% endif %}>
<label class="form-check-label">
{{ module|title }} ({{ count }})
</label>
</div>
{% endfor %}
</div>
<div class="mb-3">
<h6>By Date</h6>
<div class="form-check">
<input class="form-check-input date-filter" type="radio" name="date_filter" value="today">
<label class="form-check-label">Today ({{ results_today }})</label>
</div>
<div class="form-check">
<input class="form-check-input date-filter" type="radio" name="date_filter" value="week">
<label class="form-check-label">This Week ({{ results_week }})</label>
</div>
<div class="form-check">
<input class="form-check-input date-filter" type="radio" name="date_filter" value="month">
<label class="form-check-label">This Month ({{ results_month }})</label>
</div>
</div>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="clearFilters()">
Clear Filters
</button>
</div>
</div>
<!-- Quick Actions -->
<div class="card">
<div class="card-header">
<h5 class="card-title">Quick Actions</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="exportResults()">
<i class="fa fa-download me-2"></i>Export Results
</button>
<button type="button" class="btn btn-outline-info btn-sm" onclick="saveSearch()">
<i class="fa fa-bookmark me-2"></i>Save Search
</button>
<button type="button" class="btn btn-outline-success btn-sm" onclick="createAlert()">
<i class="fa fa-bell me-2"></i>Create Alert
</button>
</div>
</div>
</div>
</div>
<div class="col-md-9">
<!-- Search Results -->
<div class="search-results">
{% for result in search_results %}
<div class="card mb-3 search-result-item" data-module="{{ result.module }}">
<div class="card-body">
<div class="d-flex align-items-start">
<div class="result-icon me-3">
<div class="w-40px h-40px bg-{{ result.module_color }} bg-opacity-20 d-flex align-items-center justify-content-center rounded-circle">
<i class="fa fa-{{ result.module_icon }} text-{{ result.module_color }}"></i>
</div>
</div>
<div class="result-content flex-1">
<div class="d-flex align-items-center justify-content-between mb-2">
<h5 class="result-title mb-0">
<a href="{{ result.url }}" class="text-decoration-none">{{ result.title }}</a>
</h5>
<div class="result-meta">
<span class="badge bg-{{ result.module_color }}">{{ result.module|title }}</span>
{% if result.score %}
<span class="badge bg-secondary">{{ result.score }}% match</span>
{% endif %}
</div>
</div>
<div class="result-description text-muted mb-2">
{{ result.description|safe }}
</div>
<div class="result-details">
<div class="row">
{% for field, value in result.fields.items %}
<div class="col-md-4">
<small class="text-muted">{{ field|title }}:</small>
<div class="fw-bold">{{ value }}</div>
</div>
{% endfor %}
</div>
</div>
<div class="result-footer mt-2">
<div class="d-flex align-items-center justify-content-between">
<div class="result-info text-muted small">
<i class="fa fa-clock me-1"></i>{{ result.date|date:"M d, Y g:i A" }}
{% if result.user %}
<span class="ms-2">by {{ result.user.get_full_name }}</span>
{% endif %}
</div>
<div class="result-actions">
<a href="{{ result.url }}" class="btn btn-outline-primary btn-sm">
<i class="fa fa-eye me-1"></i>View
</a>
{% if result.edit_url %}
<a href="{{ result.edit_url }}" class="btn btn-outline-secondary btn-sm">
<i class="fa fa-edit me-1"></i>Edit
</a>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% empty %}
<div class="text-center py-5">
<i class="fa fa-search fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No results found</h5>
<p class="text-muted">Try adjusting your search terms or filters</p>
</div>
{% endfor %}
</div>
<!-- Pagination -->
{% if is_paginated %}
<nav aria-label="Search results pagination">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1&query={{ query }}{% if selected_modules %}&modules={{ selected_modules|join:',' }}{% endif %}{% if request.GET.date_range %}&date_range={{ request.GET.date_range }}{% endif %}">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}&query={{ query }}{% if selected_modules %}&modules={{ selected_modules|join:',' }}{% endif %}{% if request.GET.date_range %}&date_range={{ request.GET.date_range }}{% endif %}">Previous</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}&query={{ query }}{% if selected_modules %}&modules={{ selected_modules|join:',' }}{% endif %}{% if request.GET.date_range %}&date_range={{ request.GET.date_range }}{% endif %}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}&query={{ query }}{% if selected_modules %}&modules={{ selected_modules|join:',' }}{% endif %}{% if request.GET.date_range %}&date_range={{ request.GET.date_range }}{% endif %}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}&query={{ query }}{% if selected_modules %}&modules={{ selected_modules|join:',' }}{% endif %}{% if request.GET.date_range %}&date_range={{ request.GET.date_range }}{% endif %}">Last</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
</div>
{% else %}
<!-- Search Suggestions -->
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">Search Suggestions</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<h6>Common Searches</h6>
<ul class="list-unstyled">
<li><a href="?query=patient+john" class="text-decoration-none">Patient records</a></li>
<li><a href="?query=appointment+today" class="text-decoration-none">Today's appointments</a></li>
<li><a href="?query=pending+bills" class="text-decoration-none">Pending bills</a></li>
<li><a href="?query=medication+inventory" class="text-decoration-none">Medication inventory</a></li>
<li><a href="?query=lab+results" class="text-decoration-none">Lab results</a></li>
</ul>
</div>
<div class="col-md-4">
<h6>Search Tips</h6>
<ul class="list-unstyled">
<li>• Use quotes for exact phrases</li>
<li>• Use - to exclude words</li>
<li>• Use * as wildcard</li>
<li>• Search by ID numbers</li>
<li>• Filter by date ranges</li>
</ul>
</div>
<div class="col-md-4">
<h6>Recent Searches</h6>
<ul class="list-unstyled">
{% for search in recent_searches %}
<li><a href="?query={{ search.query }}" class="text-decoration-none">{{ search.query }}</a></li>
{% empty %}
<li class="text-muted">No recent searches</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Save Search Modal -->
<div class="modal fade" id="saveSearchModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Save Search</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="saveSearchForm">
<div class="modal-body">
{% csrf_token %}
<div class="mb-3">
<label class="form-label">Search Name</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea name="description" class="form-control" rows="3"></textarea>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="create_alert">
<label class="form-check-label">Create alert for new results</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save Search</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script src="{% static 'assets/plugins/select2/dist/js/select2.min.js' %}"></script>
<script>
$(document).ready(function() {
// Initialize Select2
$('select[name="modules"]').select2({
placeholder: "Select modules to search",
allowClear: true
});
// Auto-submit form on filter changes
$('.module-filter, .date-filter').change(function() {
applyFilters();
});
// Save search form
$('#saveSearchForm').submit(function(e) {
e.preventDefault();
var formData = new FormData(this);
formData.append('query', '{{ query }}');
formData.append('modules', '{{ selected_modules|join:"," }}');
formData.append('filters', JSON.stringify(getCurrentFilters()));
$.post('{% url "core:save_search" %}', formData, function(response) {
if (response.success) {
$('#saveSearchModal').modal('hide');
toastr.success('Search saved successfully');
} else {
toastr.error('Failed to save search: ' + response.error);
}
}).fail(function() {
toastr.error('Failed to save search');
});
});
});
function toggleAdvancedSearch() {
$('#advancedSearch').toggle();
}
function applyFilters() {
var selectedModules = [];
$('.module-filter:checked').each(function() {
selectedModules.push($(this).val());
});
var selectedDate = $('.date-filter:checked').val();
// Hide/show results based on filters
$('.search-result-item').each(function() {
var module = $(this).data('module');
var showModule = selectedModules.length === 0 || selectedModules.includes(module);
if (showModule) {
$(this).show();
} else {
$(this).hide();
}
});
// Update URL with filters
var url = new URL(window.location);
if (selectedModules.length > 0) {
url.searchParams.set('modules', selectedModules.join(','));
} else {
url.searchParams.delete('modules');
}
if (selectedDate) {
url.searchParams.set('date_filter', selectedDate);
} else {
url.searchParams.delete('date_filter');
}
window.history.replaceState({}, '', url);
}
function clearFilters() {
$('.module-filter, .date-filter').prop('checked', false);
$('.search-result-item').show();
var url = new URL(window.location);
url.searchParams.delete('modules');
url.searchParams.delete('date_filter');
window.history.replaceState({}, '', url);
}
function exportResults() {
var url = new URL('{% url "core:export_search_results" %}', window.location.origin);
url.searchParams.set('query', '{{ query }}');
url.searchParams.set('modules', '{{ selected_modules|join:"," }}');
url.searchParams.set('format', 'csv');
window.location.href = url.toString();
}
function saveSearch() {
$('#saveSearchModal').modal('show');
}
function createAlert() {
var url = '{% url "core:create_search_alert" %}?query={{ query }}&modules={{ selected_modules|join:"," }}';
window.location.href = url;
}
function getCurrentFilters() {
var filters = {};
var selectedModules = [];
$('.module-filter:checked').each(function() {
selectedModules.push($(this).val());
});
if (selectedModules.length > 0) {
filters.modules = selectedModules;
}
var selectedDate = $('.date-filter:checked').val();
if (selectedDate) {
filters.date = selectedDate;
}
return filters;
}
// Keyboard shortcuts
$(document).keydown(function(e) {
// Ctrl+K or Cmd+K to focus search
if ((e.ctrlKey || e.metaKey) && e.keyCode === 75) {
e.preventDefault();
$('input[name="query"]').focus();
}
// Escape to clear search
if (e.keyCode === 27) {
$('input[name="query"]').val('').focus();
}
});
// Highlight search terms in results
function highlightSearchTerms() {
var query = '{{ query }}';
if (query) {
var terms = query.split(' ');
terms.forEach(function(term) {
if (term.length > 2) {
$('.result-description').highlight(term);
}
});
}
}
// Simple highlight function
$.fn.highlight = function(text) {
return this.each(function() {
var regex = new RegExp('(' + text + ')', 'gi');
$(this).html($(this).html().replace(regex, '<mark>$1</mark>'));
});
};
// Call highlight function if results exist
{% if search_results %}
highlightSearchTerms();
{% endif %}
</script>
<style>
.search-result-item {
transition: all 0.3s ease;
}
.search-result-item:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.result-title a:hover {
color: #007bff !important;
}
.result-description mark {
background-color: #fff3cd;
padding: 0 2px;
border-radius: 2px;
}
.result-icon {
flex-shrink: 0;
}
.form-control-lg {
font-size: 1.1rem;
}
.search-results {
min-height: 400px;
}
</style>
{% endblock %}