588 lines
25 KiB
HTML
588 lines
25 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}Analytics Dashboards - Hospital Management{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="content">
|
|
<div class="container-fluid">
|
|
<!-- Page Header -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="page-header">
|
|
<div class="page-title">
|
|
<h4>Analytics Dashboards</h4>
|
|
<h6>Manage and view analytics dashboards</h6>
|
|
</div>
|
|
<div class="page-btn">
|
|
<a href="{% url 'analytics:dashboard_create' %}" class="btn btn-added">
|
|
<i class="fas fa-plus me-1"></i>Add Dashboard
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Dashboard Statistics -->
|
|
<div class="row">
|
|
<div class="col-lg-3 col-sm-6 col-12">
|
|
<div class="dash-widget">
|
|
<div class="dash-widgetimg">
|
|
<span><i class="fas fa-tachometer-alt"></i></span>
|
|
</div>
|
|
<div class="dash-widgetcontent">
|
|
<h5 id="total-dashboards">{{ stats.total_dashboards|default:"0" }}</h5>
|
|
<h6>Total Dashboards</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-sm-6 col-12">
|
|
<div class="dash-widget">
|
|
<div class="dash-widgetimg">
|
|
<span><i class="fas fa-chart-line"></i></span>
|
|
</div>
|
|
<div class="dash-widgetcontent">
|
|
<h5 id="active-dashboards">{{ stats.active_dashboards|default:"0" }}</h5>
|
|
<h6>Active Dashboards</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-sm-6 col-12">
|
|
<div class="dash-widget">
|
|
<div class="dash-widgetimg">
|
|
<span><i class="fas fa-users"></i></span>
|
|
</div>
|
|
<div class="dash-widgetcontent">
|
|
<h5 id="shared-dashboards">{{ stats.shared_dashboards|default:"0" }}</h5>
|
|
<h6>Shared Dashboards</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-sm-6 col-12">
|
|
<div class="dash-widget">
|
|
<div class="dash-widgetimg">
|
|
<span><i class="fas fa-th-large"></i></span>
|
|
</div>
|
|
<div class="dash-widgetcontent">
|
|
<h5 id="total-widgets">{{ stats.total_widgets|default:"0" }}</h5>
|
|
<h6>Total Widgets</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Dashboard Grid -->
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="table-top">
|
|
<div class="search-set">
|
|
<div class="search-input">
|
|
<a class="btn btn-searchset">
|
|
<i class="fas fa-search"></i>
|
|
</a>
|
|
<input type="text" id="searchInput" placeholder="Search dashboards...">
|
|
</div>
|
|
</div>
|
|
<div class="wordset">
|
|
<ul>
|
|
<li>
|
|
<a data-bs-toggle="tooltip" data-bs-placement="top" title="Filter" onclick="toggleFilters()">
|
|
<i class="fas fa-filter"></i>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a data-bs-toggle="tooltip" data-bs-placement="top" title="Grid View" onclick="toggleView('grid')" id="gridViewBtn" class="active">
|
|
<i class="fas fa-th"></i>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a data-bs-toggle="tooltip" data-bs-placement="top" title="List View" onclick="toggleView('list')" id="listViewBtn">
|
|
<i class="fas fa-list"></i>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters Panel -->
|
|
<div id="filtersPanel" class="filters-panel" style="display: none;">
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label for="dashboardType" class="form-label">Dashboard Type</label>
|
|
<select class="form-select" id="dashboardType">
|
|
<option value="">All Types</option>
|
|
{% for choice in dashboard_type_choices %}
|
|
<option value="{{ choice.0 }}">{{ choice.1 }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label for="statusFilter" class="form-label">Status</label>
|
|
<select class="form-select" id="statusFilter">
|
|
<option value="">All Status</option>
|
|
{% for choice in status_choices %}
|
|
<option value="{{ choice.0 }}">{{ choice.1 }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label for="createdBy" class="form-label">Created By</label>
|
|
<select class="form-select" id="createdBy">
|
|
<option value="">All Users</option>
|
|
<option value="me">My Dashboards</option>
|
|
<option value="shared">Shared with Me</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="form-group">
|
|
<label class="form-label"> </label>
|
|
<div class="d-flex gap-2">
|
|
<button type="button" class="btn btn-primary btn-sm" onclick="applyFilters()">
|
|
<i class="fas fa-check me-1"></i>Apply
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="clearFilters()">
|
|
<i class="fas fa-times me-1"></i>Clear
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Grid View -->
|
|
<div id="gridView" class="dashboard-grid">
|
|
<div class="row">
|
|
{% for dashboard in dashboards %}
|
|
<div class="col-lg-4 col-md-6 col-sm-12 mb-4">
|
|
<div class="dashboard-card">
|
|
<div class="dashboard-header">
|
|
<div class="dashboard-type">
|
|
<span class="badge bg-{{ dashboard.get_type_color }}">{{ dashboard.get_dashboard_type_display }}</span>
|
|
<div class="dashboard-actions">
|
|
<div class="dropdown">
|
|
<a href="#" class="dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i class="fas fa-ellipsis-v"></i>
|
|
</a>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="{% url 'analytics:dashboard_detail' dashboard.pk %}"><i class="fas fa-eye me-2"></i>View</a></li>
|
|
<li><a class="dropdown-item" href="{% url 'analytics:dashboard_update' dashboard.pk %}"><i class="fas fa-edit me-2"></i>Edit</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="duplicateDashboard({{ dashboard.pk }})"><i class="fas fa-copy me-2"></i>Duplicate</a></li>
|
|
<li><a class="dropdown-item" href="#" onclick="shareDashboard({{ dashboard.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 'analytics:dashboard_delete' dashboard.pk %}"><i class="fas fa-trash me-2"></i>Delete</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<h5 class="dashboard-title">{{ dashboard.name }}</h5>
|
|
<p class="dashboard-description">{{ dashboard.description|default:"No description available"|truncatechars:100 }}</p>
|
|
</div>
|
|
<div class="dashboard-preview">
|
|
<div class="preview-widgets">
|
|
{% for widget in dashboard.widgets.all|slice:":4" %}
|
|
<div class="widget-mini">
|
|
<i class="fas fa-{{ widget.get_icon }} text-{{ widget.get_color }}"></i>
|
|
<span>{{ widget.title|truncatechars:15 }}</span>
|
|
</div>
|
|
{% empty %}
|
|
<div class="widget-mini">
|
|
<i class="fas fa-chart-line text-muted"></i>
|
|
<span>No widgets</span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
<div class="dashboard-footer">
|
|
<div class="dashboard-meta">
|
|
<span class="meta-item">
|
|
<i class="fas fa-th-large me-1"></i>
|
|
{{ dashboard.widgets.count }} widget{{ dashboard.widgets.count|pluralize }}
|
|
</span>
|
|
<span class="meta-item">
|
|
<i class="fas fa-user me-1"></i>
|
|
{{ dashboard.created_by.get_full_name|default:dashboard.created_by.username }}
|
|
</span>
|
|
<span class="meta-item">
|
|
<i class="fas fa-clock me-1"></i>
|
|
{{ dashboard.updated_at|timesince }} ago
|
|
</span>
|
|
</div>
|
|
<div class="dashboard-status">
|
|
{% if dashboard.is_active %}
|
|
<span class="badge bg-success">Active</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">Inactive</span>
|
|
{% endif %}
|
|
{% if dashboard.is_shared %}
|
|
<span class="badge bg-info">Shared</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="col-12">
|
|
<div class="empty-state text-center py-5">
|
|
<i class="fas fa-tachometer-alt fa-3x text-muted mb-3"></i>
|
|
<h5 class="text-muted">No Dashboards Found</h5>
|
|
<p class="text-muted">Create your first dashboard to get started with analytics.</p>
|
|
<a href="{% url 'analytics:dashboard_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus me-1"></i>Create Dashboard
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- List View -->
|
|
<div id="listView" class="dashboard-list" style="display: none;">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Dashboard</th>
|
|
<th>Type</th>
|
|
<th>Widgets</th>
|
|
<th>Status</th>
|
|
<th>Created By</th>
|
|
<th>Last Updated</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for dashboard in dashboards %}
|
|
<tr>
|
|
<td>
|
|
<div>
|
|
<h6 class="mb-1">{{ dashboard.name }}</h6>
|
|
<small class="text-muted">{{ dashboard.description|default:"No description"|truncatechars:50 }}</small>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{{ dashboard.get_type_color }}">{{ dashboard.get_dashboard_type_display }}</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-light text-dark">{{ dashboard.widgets.count }}</span>
|
|
</td>
|
|
<td>
|
|
{% if dashboard.is_active %}
|
|
<span class="badge bg-success">Active</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">Inactive</span>
|
|
{% endif %}
|
|
{% if dashboard.is_shared %}
|
|
<span class="badge bg-info ms-1">Shared</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ dashboard.created_by.get_full_name|default:dashboard.created_by.username }}</td>
|
|
<td>{{ dashboard.updated_at|date:"M d, Y H:i" }}</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="{% url 'analytics:dashboard_detail' dashboard.pk %}" class="btn btn-outline-primary" title="View">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
<a href="{% url 'analytics:dashboard_update' dashboard.pk %}" class="btn btn-outline-secondary" title="Edit">
|
|
<i class="fas fa-edit"></i>
|
|
</a>
|
|
<button class="btn btn-outline-info" onclick="duplicateDashboard({{ dashboard.pk }})" title="Duplicate">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
<a href="{% url 'analytics:dashboard_delete' dashboard.pk %}" class="btn btn-outline-danger" title="Delete">
|
|
<i class="fas fa-trash"></i>
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="7" class="text-center py-4">
|
|
<div class="empty-state">
|
|
<i class="fas fa-tachometer-alt fa-2x text-muted mb-2"></i>
|
|
<p class="text-muted">No dashboards found matching your criteria.</p>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if is_paginated %}
|
|
<div class="pagination-wrapper mt-4">
|
|
<nav aria-label="Dashboard pagination">
|
|
<ul class="pagination justify-content-center">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page=1{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">First</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% 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 }}{% if request.GET.search %}&search={{ request.GET.search }}{% 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 }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">Next</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">Last</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Load dashboard statistics
|
|
{#function loadDashboardStats() {#}
|
|
{# $.ajax({#}
|
|
{# url: '{% url "analytics:dashboard_stats" %}',#}
|
|
{# success: function(data) {#}
|
|
{# $('#total-dashboards').text(data.total_dashboards);#}
|
|
{# $('#active-dashboards').text(data.active_dashboards);#}
|
|
{# $('#shared-dashboards').text(data.shared_dashboards);#}
|
|
{# $('#total-widgets').text(data.total_widgets);#}
|
|
{# },#}
|
|
{# error: function() {#}
|
|
{# console.log('Failed to load dashboard statistics');#}
|
|
{# }#}
|
|
{# });#}
|
|
{# }#}
|
|
|
|
// Toggle between grid and list view
|
|
function toggleView(view) {
|
|
if (view === 'grid') {
|
|
$('#gridView').show();
|
|
$('#listView').hide();
|
|
$('#gridViewBtn').addClass('active');
|
|
$('#listViewBtn').removeClass('active');
|
|
localStorage.setItem('dashboardView', 'grid');
|
|
} else {
|
|
$('#gridView').hide();
|
|
$('#listView').show();
|
|
$('#listViewBtn').addClass('active');
|
|
$('#gridViewBtn').removeClass('active');
|
|
localStorage.setItem('dashboardView', 'list');
|
|
}
|
|
}
|
|
|
|
// Toggle filters panel
|
|
function toggleFilters() {
|
|
$('#filtersPanel').slideToggle();
|
|
}
|
|
|
|
// Apply filters
|
|
function applyFilters() {
|
|
const type = $('#dashboardType').val();
|
|
const status = $('#statusFilter').val();
|
|
const createdBy = $('#createdBy').val();
|
|
const search = $('#searchInput').val();
|
|
|
|
let url = new URL(window.location);
|
|
url.searchParams.set('type', type);
|
|
url.searchParams.set('status', status);
|
|
url.searchParams.set('created_by', createdBy);
|
|
url.searchParams.set('search', search);
|
|
|
|
window.location.href = url.toString();
|
|
}
|
|
|
|
// Clear filters
|
|
function clearFilters() {
|
|
$('#dashboardType').val('');
|
|
$('#statusFilter').val('');
|
|
$('#createdBy').val('');
|
|
$('#searchInput').val('');
|
|
|
|
let url = new URL(window.location);
|
|
url.search = '';
|
|
window.location.href = url.toString();
|
|
}
|
|
|
|
// Duplicate dashboard
|
|
function duplicateDashboard(dashboardId) {
|
|
if (confirm('Are you sure you want to duplicate this dashboard?')) {
|
|
$.ajax({
|
|
url: `/analytics/dashboards/${dashboardId}/duplicate/`,
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
|
},
|
|
success: function(data) {
|
|
if (data.success) {
|
|
alert('Dashboard duplicated successfully!');
|
|
window.location.reload();
|
|
} else {
|
|
alert('Failed to duplicate dashboard: ' + data.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('Error duplicating dashboard');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Share dashboard
|
|
function shareDashboard(dashboardId) {
|
|
// Implement share functionality
|
|
alert('Share functionality will be implemented');
|
|
}
|
|
|
|
// Search functionality
|
|
$('#searchInput').on('keyup', function(e) {
|
|
if (e.key === 'Enter') {
|
|
applyFilters();
|
|
}
|
|
});
|
|
|
|
// Initialize page
|
|
$(document).ready(function() {
|
|
// Load statistics
|
|
loadDashboardStats();
|
|
|
|
// Restore view preference
|
|
const savedView = localStorage.getItem('dashboardView') || 'grid';
|
|
toggleView(savedView);
|
|
|
|
// Initialize tooltips
|
|
$('[data-bs-toggle="tooltip"]').tooltip();
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.dashboard-grid .dashboard-card {
|
|
border: 1px solid #e9ecef;
|
|
border-radius: 8px;
|
|
transition: all 0.3s ease;
|
|
height: 100%;
|
|
}
|
|
|
|
.dashboard-card:hover {
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.dashboard-header {
|
|
padding: 20px;
|
|
border-bottom: 1px solid #f8f9fa;
|
|
}
|
|
|
|
.dashboard-type {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.dashboard-title {
|
|
margin-bottom: 8px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.dashboard-description {
|
|
color: #6c757d;
|
|
font-size: 0.875rem;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.dashboard-preview {
|
|
padding: 15px 20px;
|
|
background-color: #f8f9fa;
|
|
}
|
|
|
|
.preview-widgets {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
}
|
|
|
|
.widget-mini {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
font-size: 0.75rem;
|
|
color: #6c757d;
|
|
}
|
|
|
|
.dashboard-footer {
|
|
padding: 15px 20px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
border-top: 1px solid #f8f9fa;
|
|
}
|
|
|
|
.dashboard-meta {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 15px;
|
|
}
|
|
|
|
.meta-item {
|
|
font-size: 0.75rem;
|
|
color: #6c757d;
|
|
}
|
|
|
|
.dashboard-status {
|
|
display: flex;
|
|
gap: 5px;
|
|
}
|
|
|
|
.filters-panel {
|
|
background-color: #f8f9fa;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.empty-state {
|
|
padding: 60px 20px;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.dashboard-meta {
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.dashboard-footer {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
}
|
|
|
|
.preview-widgets {
|
|
justify-content: center;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|