2025-08-12 13:33:25 +03:00

644 lines
32 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}Risk Assessments{% endblock %}
{% block content %}
<!-- BEGIN breadcrumb -->
<ol class="breadcrumb float-xl-end">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{% url 'quality:dashboard' %}">Quality</a></li>
<li class="breadcrumb-item active">Risk Assessments</li>
</ol>
<!-- END breadcrumb -->
<!-- BEGIN page-header -->
<h1 class="page-header">
Risk Assessments
<small>Identify and evaluate potential risks to patient safety and quality</small>
</h1>
<!-- END page-header -->
<!-- BEGIN row -->
<div class="row">
<div class="col-xl-12">
<!-- Statistics Cards -->
<div class="row mb-3">
<div class="col-xl-3 col-md-6">
<div class="widget widget-stats bg-danger text-white">
<div class="stats-icon stats-icon-lg">
<i class="fa fa-exclamation-triangle"></i>
</div>
<div class="stats-content">
<div class="stats-title">Critical Risk</div>
<div class="stats-number">{{ stats.critical_count|default:0 }}</div>
<div class="stats-progress progress">
<div class="progress-bar" style="width: {{ stats.critical_percentage|default:0 }}%;"></div>
</div>
<div class="stats-desc">Immediate attention required</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="widget widget-stats bg-warning text-white">
<div class="stats-icon stats-icon-lg">
<i class="fa fa-exclamation-circle"></i>
</div>
<div class="stats-content">
<div class="stats-title">High Risk</div>
<div class="stats-number">{{ stats.high_count|default:0 }}</div>
<div class="stats-progress progress">
<div class="progress-bar" style="width: {{ stats.high_percentage|default:0 }}%;"></div>
</div>
<div class="stats-desc">Priority mitigation needed</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="widget widget-stats bg-info text-white">
<div class="stats-icon stats-icon-lg">
<i class="fa fa-shield-alt"></i>
</div>
<div class="stats-content">
<div class="stats-title">Mitigated</div>
<div class="stats-number">{{ stats.mitigated_count|default:0 }}</div>
<div class="stats-progress progress">
<div class="progress-bar" style="width: {{ stats.mitigated_percentage|default:0 }}%;"></div>
</div>
<div class="stats-desc">Controls implemented</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="widget widget-stats bg-success text-white">
<div class="stats-icon stats-icon-lg">
<i class="fa fa-check-circle"></i>
</div>
<div class="stats-content">
<div class="stats-title">Total Assessments</div>
<div class="stats-number">{{ stats.total_count|default:0 }}</div>
<div class="stats-progress progress">
<div class="progress-bar" style="width: 100%;"></div>
</div>
<div class="stats-desc">All risk assessments</div>
</div>
</div>
</div>
</div>
<!-- Filters and Actions -->
<div class="panel panel-inverse">
<div class="panel-heading">
<h4 class="panel-title">Risk Assessment Management</h4>
<div class="panel-heading-btn">
<a href="{% url 'quality:create_risk_assessment' %}" class="btn btn-primary btn-sm">
<i class="fa fa-plus me-1"></i>New Assessment
</a>
</div>
</div>
<div class="panel-body">
<!-- Filters -->
<form method="get" id="filterForm" class="row g-3 mb-3">
<div class="col-md-3">
<div class="form-floating">
<input type="text" class="form-control" id="search" name="search"
value="{{ request.GET.search }}" placeholder="Search assessments...">
<label for="search">Search</label>
</div>
</div>
<div class="col-md-2">
<div class="form-floating">
<select class="form-select" id="risk_level" name="risk_level">
<option value="">All Levels</option>
<option value="critical" {% if request.GET.risk_level == 'critical' %}selected{% endif %}>Critical</option>
<option value="high" {% if request.GET.risk_level == 'high' %}selected{% endif %}>High</option>
<option value="medium" {% if request.GET.risk_level == 'medium' %}selected{% endif %}>Medium</option>
<option value="low" {% if request.GET.risk_level == 'low' %}selected{% endif %}>Low</option>
</select>
<label for="risk_level">Risk Level</label>
</div>
</div>
<div class="col-md-2">
<div class="form-floating">
<select class="form-select" id="status" name="status">
<option value="">All Status</option>
<option value="draft" {% if request.GET.status == 'draft' %}selected{% endif %}>Draft</option>
<option value="active" {% if request.GET.status == 'active' %}selected{% endif %}>Active</option>
<option value="mitigated" {% if request.GET.status == 'mitigated' %}selected{% endif %}>Mitigated</option>
<option value="closed" {% if request.GET.status == 'closed' %}selected{% endif %}>Closed</option>
</select>
<label for="status">Status</label>
</div>
</div>
<div class="col-md-2">
<div class="form-floating">
<select class="form-select" id="department" name="department">
<option value="">All Departments</option>
{% for dept in departments %}
<option value="{{ dept.id }}" {% if request.GET.department == dept.id|stringformat:"s" %}selected{% endif %}>
{{ dept.name }}
</option>
{% endfor %}
</select>
<label for="department">Department</label>
</div>
</div>
<div class="col-md-2">
<div class="form-floating">
<select class="form-select" id="assessor" name="assessor">
<option value="">All Assessors</option>
{% for user in assessors %}
<option value="{{ user.id }}" {% if request.GET.assessor == user.id|stringformat:"s" %}selected{% endif %}>
{{ user.get_full_name }}
</option>
{% endfor %}
</select>
<label for="assessor">Assessor</label>
</div>
</div>
<div class="col-md-1">
<button type="submit" class="btn btn-outline-primary h-100">
<i class="fa fa-search"></i>
</button>
</div>
</form>
<!-- Bulk Actions -->
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="bulk-actions" style="display: none;">
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle"
data-bs-toggle="dropdown">
<i class="fa fa-cog me-1"></i>Bulk Actions
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="bulkAction('review')">
<i class="fa fa-eye me-2"></i>Mark for Review
</a></li>
<li><a class="dropdown-item" href="#" onclick="bulkAction('mitigate')">
<i class="fa fa-shield-alt me-2"></i>Mark as Mitigated
</a></li>
<li><a class="dropdown-item" href="#" onclick="bulkAction('export')">
<i class="fa fa-download me-2"></i>Export Selected
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="#" onclick="bulkAction('delete')">
<i class="fa fa-trash me-2"></i>Delete Selected
</a></li>
</ul>
</div>
<span class="ms-2 text-muted">
<span id="selectedCount">0</span> assessment(s) selected
</span>
</div>
<div class="view-options">
<div class="btn-group" role="group">
<input type="radio" class="btn-check" name="viewMode" id="tableView" checked>
<label class="btn btn-outline-secondary btn-sm" for="tableView">
<i class="fa fa-table"></i>
</label>
<input type="radio" class="btn-check" name="viewMode" id="cardView">
<label class="btn btn-outline-secondary btn-sm" for="cardView">
<i class="fa fa-th-large"></i>
</label>
</div>
<div class="btn-group ms-2">
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle"
data-bs-toggle="dropdown">
<i class="fa fa-download me-1"></i>Export
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="?{{ request.GET.urlencode }}&export=csv">
<i class="fa fa-file-csv me-2"></i>CSV
</a></li>
<li><a class="dropdown-item" href="?{{ request.GET.urlencode }}&export=excel">
<i class="fa fa-file-excel me-2"></i>Excel
</a></li>
<li><a class="dropdown-item" href="?{{ request.GET.urlencode }}&export=pdf">
<i class="fa fa-file-pdf me-2"></i>PDF
</a></li>
</ul>
</div>
</div>
</div>
<!-- Table View -->
<div id="tableViewContent">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th width="30">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="selectAll">
</div>
</th>
<th>Assessment</th>
<th>Risk Level</th>
<th>Status</th>
<th>Department</th>
<th>Assessor</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for assessment in assessments %}
<tr>
<td>
<div class="form-check">
<input class="form-check-input assessment-checkbox" type="checkbox"
value="{{ assessment.id }}">
</div>
</td>
<td>
<div class="d-flex align-items-center">
<div>
<div class="fw-bold">
<a href="{% url 'quality:risk_assessment_detail' assessment.pk %}"
class="text-decoration-none">
{{ assessment.title }}
</a>
</div>
<div class="text-muted small">
{{ assessment.description|truncatechars:60 }}
</div>
</div>
</div>
</td>
<td>
{% if assessment.risk_level == 'critical' %}
<span class="badge bg-danger">Critical</span>
{% elif assessment.risk_level == 'high' %}
<span class="badge bg-warning text-dark">High</span>
{% elif assessment.risk_level == 'medium' %}
<span class="badge bg-info">Medium</span>
{% elif assessment.risk_level == 'low' %}
<span class="badge bg-success">Low</span>
{% endif %}
</td>
<td>
{% if assessment.status == 'draft' %}
<span class="badge bg-secondary">Draft</span>
{% elif assessment.status == 'active' %}
<span class="badge bg-warning text-dark">Active</span>
{% elif assessment.status == 'mitigated' %}
<span class="badge bg-info">Mitigated</span>
{% elif assessment.status == 'closed' %}
<span class="badge bg-success">Closed</span>
{% endif %}
</td>
<td>
{% if assessment.department %}
{{ assessment.department.name }}
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>
{% if assessment.assessor %}
{{ assessment.assessor.get_full_name }}
{% else %}
<span class="text-muted">Unassigned</span>
{% endif %}
</td>
<td>
<div>{{ assessment.assessment_date|date:"M d, Y" }}</div>
<div class="text-muted small">{{ assessment.assessment_date|timesince }} ago</div>
</td>
<td>
<div class="btn-group">
<a href="{% url 'quality:risk_assessment_detail' assessment.pk %}"
class="btn btn-outline-primary btn-sm" title="View">
<i class="fa fa-eye"></i>
</a>
<a href="{% url 'quality:risk_assessment_edit' assessment.pk %}"
class="btn btn-outline-secondary btn-sm" title="Edit">
<i class="fa fa-edit"></i>
</a>
<button type="button" class="btn btn-outline-info btn-sm"
onclick="showMitigationModal({{ assessment.id }})" title="Mitigation">
<i class="fa fa-shield-alt"></i>
</button>
<a href="{% url 'quality:risk_assessment_delete' assessment.pk %}"
class="btn btn-outline-danger btn-sm" title="Delete">
<i class="fa fa-trash"></i>
</a>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="8" class="text-center py-4">
<div class="text-muted">
<i class="fa fa-search fa-2x mb-3"></i>
<p>No risk assessments found matching your criteria.</p>
<a href="{% url 'quality:create_risk_assessment' %}" class="btn btn-primary">
<i class="fa fa-plus me-1"></i>Create First Assessment
</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Card View -->
<div id="cardViewContent" style="display: none;">
<div class="row">
{% for assessment in assessments %}
<div class="col-xl-4 col-lg-6 mb-3">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<div class="form-check">
<input class="form-check-input assessment-checkbox" type="checkbox"
value="{{ assessment.id }}">
</div>
{% if assessment.risk_level == 'critical' %}
<span class="badge bg-danger">Critical</span>
{% elif assessment.risk_level == 'high' %}
<span class="badge bg-warning text-dark">High</span>
{% elif assessment.risk_level == 'medium' %}
<span class="badge bg-info">Medium</span>
{% elif assessment.risk_level == 'low' %}
<span class="badge bg-success">Low</span>
{% endif %}
</div>
<div class="card-body">
<h6 class="card-title">
<a href="{% url 'quality:risk_assessment_detail' assessment.pk %}"
class="text-decoration-none">
{{ assessment.title }}
</a>
</h6>
<p class="card-text text-muted small">
{{ assessment.description|truncatechars:100 }}
</p>
<div class="row text-center">
<div class="col-6">
<div class="fw-bold">Status</div>
{% if assessment.status == 'draft' %}
<span class="badge bg-secondary">Draft</span>
{% elif assessment.status == 'active' %}
<span class="badge bg-warning text-dark">Active</span>
{% elif assessment.status == 'mitigated' %}
<span class="badge bg-info">Mitigated</span>
{% elif assessment.status == 'closed' %}
<span class="badge bg-success">Closed</span>
{% endif %}
</div>
<div class="col-6">
<div class="fw-bold">Date</div>
<div class="small">{{ assessment.assessment_date|date:"M d, Y" }}</div>
</div>
</div>
</div>
<div class="card-footer">
<div class="d-flex justify-content-between">
<small class="text-muted">
{% if assessment.assessor %}
{{ assessment.assessor.get_full_name }}
{% else %}
Unassigned
{% endif %}
</small>
<div class="btn-group">
<a href="{% url 'quality:risk_assessment_detail' assessment.pk %}"
class="btn btn-outline-primary btn-sm">
<i class="fa fa-eye"></i>
</a>
<a href="{% url 'quality:risk_assessment_edit' assessment.pk %}"
class="btn btn-outline-secondary btn-sm">
<i class="fa fa-edit"></i>
</a>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Pagination -->
{% if is_paginated %}
<nav aria-label="Risk assessments pagination">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?{{ request.GET.urlencode }}&page=1">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?{{ request.GET.urlencode }}&page={{ page_obj.previous_page_number }}">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="?{{ request.GET.urlencode }}&page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?{{ request.GET.urlencode }}&page={{ page_obj.next_page_number }}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?{{ request.GET.urlencode }}&page={{ page_obj.paginator.num_pages }}">Last</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
</div>
</div>
</div>
<!-- END row -->
<!-- Mitigation Modal -->
<div class="modal fade" id="mitigationModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Risk Mitigation</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="mitigationContent">
<!-- Content loaded via AJAX -->
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script>
$(document).ready(function() {
// Auto-submit filter form on change
$('#filterForm select').on('change', function() {
$('#filterForm').submit();
});
// Search with delay
let searchTimer;
$('#search').on('input', function() {
clearTimeout(searchTimer);
searchTimer = setTimeout(function() {
$('#filterForm').submit();
}, 500);
});
// View mode toggle
$('input[name="viewMode"]').on('change', function() {
if ($(this).attr('id') === 'tableView') {
$('#tableViewContent').show();
$('#cardViewContent').hide();
} else {
$('#tableViewContent').hide();
$('#cardViewContent').show();
}
});
// Select all functionality
$('#selectAll').on('change', function() {
$('.assessment-checkbox').prop('checked', $(this).is(':checked'));
updateBulkActions();
});
$('.assessment-checkbox').on('change', function() {
updateBulkActions();
// Update select all checkbox
const totalCheckboxes = $('.assessment-checkbox').length;
const checkedCheckboxes = $('.assessment-checkbox:checked').length;
$('#selectAll').prop('checked', totalCheckboxes === checkedCheckboxes);
});
// Auto-refresh every 30 seconds
setInterval(function() {
if ($('.assessment-checkbox:checked').length === 0) {
location.reload();
}
}, 30000);
});
function updateBulkActions() {
const selectedCount = $('.assessment-checkbox:checked').length;
$('#selectedCount').text(selectedCount);
if (selectedCount > 0) {
$('.bulk-actions').show();
} else {
$('.bulk-actions').hide();
}
}
function bulkAction(action) {
const selectedIds = $('.assessment-checkbox:checked').map(function() {
return $(this).val();
}).get();
if (selectedIds.length === 0) {
toastr.warning('Please select at least one assessment');
return;
}
let confirmMessage = '';
let url = '';
switch (action) {
case 'review':
confirmMessage = `Mark ${selectedIds.length} assessment(s) for review?`;
url = '{% url "quality:bulk_review_assessments" %}';
break;
case 'mitigate':
confirmMessage = `Mark ${selectedIds.length} assessment(s) as mitigated?`;
url = '{% url "quality:bulk_mitigate_assessments" %}';
break;
case 'export':
window.open(`{% url "quality:export_assessments" %}?ids=${selectedIds.join(',')}`);
return;
case 'delete':
confirmMessage = `Delete ${selectedIds.length} assessment(s)? This action cannot be undone.`;
url = '{% url "quality:bulk_delete_assessments" %}';
break;
}
if (confirm(confirmMessage)) {
$.ajax({
url: url,
method: 'POST',
data: {
'assessment_ids': selectedIds,
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function(response) {
if (response.success) {
toastr.success(response.message);
location.reload();
} else {
toastr.error(response.message || 'Operation failed');
}
},
error: function() {
toastr.error('Operation failed');
}
});
}
}
function showMitigationModal(assessmentId) {
$('#mitigationModal').modal('show');
$('#mitigationContent').html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> Loading...</div>');
$.ajax({
url: '{% url "quality:assessment_mitigation" %}',
method: 'GET',
data: { 'assessment_id': assessmentId },
success: function(response) {
$('#mitigationContent').html(response);
},
error: function() {
$('#mitigationContent').html('<div class="alert alert-danger">Failed to load mitigation information</div>');
}
});
}
// Real-time updates
function refreshStats() {
$.ajax({
url: '{% url "quality:assessment_stats" %}',
method: 'GET',
success: function(response) {
if (response.stats) {
// Update statistics cards
$('.stats-number').each(function(index) {
const statTypes = ['critical_count', 'high_count', 'mitigated_count', 'total_count'];
if (statTypes[index] && response.stats[statTypes[index]]) {
$(this).text(response.stats[statTypes[index]]);
}
});
}
}
});
}
// Refresh stats every 60 seconds
setInterval(refreshStats, 60000);
</script>
{% endblock %}