Marwan Alwali 0a037d3d9d update
2025-09-01 11:26:11 +03:00

1252 lines
38 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}{{ assessment.title }} - Risk Assessment{% endblock %}
{% block extra_css %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
.assessment-header {
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.assessment-layout {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.assessment-main {
display: flex;
flex-direction: column;
gap: 2rem;
}
.assessment-sidebar {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.section-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
overflow: hidden;
}
.section-header {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 1rem 1.5rem;
font-weight: 600;
color: #495057;
display: flex;
align-items: center;
gap: 0.5rem;
}
.section-content {
padding: 1.5rem;
}
.risk-level-badge {
padding: 0.5rem 1rem;
border-radius: 0.25rem;
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
}
.risk-critical { background: #dc3545; color: white; }
.risk-high { background: #fd7e14; color: white; }
.risk-medium { background: #ffc107; color: #212529; }
.risk-low { background: #28a745; color: white; }
.status-badge {
padding: 0.5rem 1rem;
border-radius: 0.25rem;
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
}
.status-draft { background: #6c757d; color: white; }
.status-pending { background: #ffc107; color: #212529; }
.status-approved { background: #28a745; color: white; }
.status-rejected { background: #dc3545; color: white; }
.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;
gap: 0.25rem;
}
.info-label {
font-size: 0.75rem;
color: #6c757d;
font-weight: 600;
text-transform: uppercase;
}
.info-value {
font-size: 0.875rem;
color: #495057;
font-weight: 500;
}
.risk-matrix {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
margin-bottom: 1.5rem;
}
.matrix-item {
text-align: center;
padding: 1rem;
background: #f8f9fa;
border-radius: 0.375rem;
border: 2px solid transparent;
transition: all 0.2s;
}
.matrix-item:hover {
border-color: #dc3545;
background: #fff5f5;
}
.matrix-value {
font-size: 2rem;
font-weight: bold;
color: #dc3545;
margin-bottom: 0.5rem;
}
.matrix-label {
font-size: 0.875rem;
color: #6c757d;
font-weight: 600;
text-transform: uppercase;
}
.risk-description {
background: #f8f9fa;
border-left: 4px solid #dc3545;
padding: 1rem;
border-radius: 0.375rem;
margin-bottom: 1.5rem;
}
.mitigation-item {
display: flex;
align-items: start;
gap: 0.75rem;
padding: 1rem;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
margin-bottom: 1rem;
background: white;
transition: all 0.2s;
}
.mitigation-item:hover {
border-color: #28a745;
background: #f8fff8;
}
.mitigation-icon {
width: 40px;
height: 40px;
border-radius: 0.375rem;
background: #28a745;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
flex-shrink: 0;
}
.mitigation-content {
flex: 1;
}
.mitigation-title {
font-weight: 600;
color: #495057;
margin-bottom: 0.5rem;
}
.mitigation-description {
color: #6c757d;
font-size: 0.875rem;
margin-bottom: 0.75rem;
line-height: 1.5;
}
.mitigation-meta {
display: flex;
gap: 1rem;
font-size: 0.75rem;
color: #6c757d;
}
.mitigation-status {
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.status-completed { background: #d4edda; color: #155724; }
.status-in-progress { background: #fff3cd; color: #856404; }
.status-pending { background: #f8d7da; color: #721c24; }
.progress-section {
margin-bottom: 1.5rem;
}
.progress-header {
display: flex;
justify-content: between;
align-items: center;
margin-bottom: 0.5rem;
}
.progress-title {
font-weight: 600;
color: #495057;
}
.progress-percentage {
font-weight: bold;
color: #28a745;
}
.progress-bar-container {
height: 12px;
background: #e9ecef;
border-radius: 6px;
overflow: hidden;
margin-bottom: 0.5rem;
}
.progress-bar-fill {
height: 100%;
background: linear-gradient(90deg, #28a745, #20c997);
border-radius: 6px;
transition: width 0.3s ease;
}
.progress-details {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
font-size: 0.75rem;
color: #6c757d;
}
.timeline-item {
display: flex;
align-items: start;
gap: 0.75rem;
padding: 1rem;
border-bottom: 1px solid #f0f0f0;
transition: background 0.2s;
}
.timeline-item:hover {
background: #f8f9fa;
}
.timeline-item:last-child {
border-bottom: none;
}
.timeline-icon {
width: 32px;
height: 32px;
border-radius: 50%;
background: #dc3545;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.875rem;
flex-shrink: 0;
}
.timeline-content {
flex: 1;
}
.timeline-title {
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
}
.timeline-description {
color: #6c757d;
font-size: 0.875rem;
margin-bottom: 0.5rem;
}
.timeline-meta {
font-size: 0.75rem;
color: #6c757d;
}
.alert-item {
display: flex;
align-items: start;
gap: 0.75rem;
padding: 1rem;
background: #fff3cd;
border: 1px solid #ffeaa7;
border-left: 4px solid #ffc107;
border-radius: 0.375rem;
margin-bottom: 1rem;
}
.alert-icon {
width: 32px;
height: 32px;
border-radius: 50%;
background: #ffc107;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.875rem;
flex-shrink: 0;
}
.alert-content {
flex: 1;
}
.alert-title {
font-weight: 600;
color: #856404;
margin-bottom: 0.25rem;
}
.alert-message {
color: #856404;
font-size: 0.875rem;
margin-bottom: 0.5rem;
}
.alert-meta {
font-size: 0.75rem;
color: #856404;
}
.team-member {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem;
border-bottom: 1px solid #f0f0f0;
transition: background 0.2s;
}
.team-member:hover {
background: #f8f9fa;
}
.member-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #dc3545;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
flex-shrink: 0;
}
.member-info {
flex: 1;
}
.member-name {
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
}
.member-role {
font-size: 0.75rem;
color: #6c757d;
}
.member-contact {
font-size: 0.75rem;
color: #6c757d;
}
.action-buttons {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
margin-bottom: 2rem;
}
.btn-action {
padding: 0.5rem 1rem;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
background: white;
cursor: pointer;
transition: all 0.2s;
font-size: 0.875rem;
text-decoration: none;
color: #495057;
}
.btn-action:hover {
border-color: #dc3545;
color: #dc3545;
text-decoration: none;
}
.btn-primary-action {
background: #dc3545;
border-color: #dc3545;
color: white;
}
.btn-primary-action:hover {
background: #c82333;
border-color: #c82333;
color: white;
}
.chart-container {
height: 300px;
margin-bottom: 1rem;
}
.chart-controls {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-bottom: 1rem;
}
.chart-control {
padding: 0.25rem 0.75rem;
border: 1px solid #dee2e6;
background: white;
border-radius: 0.25rem;
cursor: pointer;
transition: all 0.2s;
font-size: 0.75rem;
}
.chart-control.active {
background: #dc3545;
color: white;
border-color: #dc3545;
}
.documents-list {
list-style: none;
padding: 0;
margin: 0;
}
.document-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem;
border-bottom: 1px solid #f0f0f0;
transition: background 0.2s;
}
.document-item:hover {
background: #f8f9fa;
}
.document-icon {
width: 32px;
height: 32px;
border-radius: 0.375rem;
background: #6c757d;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.875rem;
flex-shrink: 0;
}
.document-info {
flex: 1;
}
.document-name {
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
}
.document-meta {
font-size: 0.75rem;
color: #6c757d;
}
@media (max-width: 1200px) {
.assessment-layout {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.assessment-sidebar {
order: -1;
}
}
@media (max-width: 768px) {
.assessment-header {
padding: 1.5rem;
}
.info-grid {
grid-template-columns: 1fr;
}
.risk-matrix {
grid-template-columns: 1fr;
}
.progress-details {
grid-template-columns: 1fr;
}
.action-buttons {
flex-direction: column;
align-items: stretch;
}
}
@media print {
.assessment-sidebar, .action-buttons {
display: none !important;
}
.assessment-layout {
grid-template-columns: 1fr;
}
.section-header {
background: none;
border-bottom: 2px solid #000;
color: #000;
}
}
</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 'quality:dashboard' %}">Quality</a></li>
<li class="breadcrumb-item"><a href="{% url 'quality:risk_assessment_list' %}">Risk Assessments</a></li>
<li class="breadcrumb-item active">{{ assessment.title|truncatechars:30 }}</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-shield-alt me-2"></i>Risk Assessment Details
</h1>
</div>
<div class="ms-auto">
<a href="{% url 'quality:risk_assessment_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>Back to List
</a>
</div>
</div>
<!-- Assessment Header -->
<div class="assessment-header">
<div class="row align-items-center">
<div class="col-md-8">
<h2 class="mb-2">{{ assessment.title }}</h2>
<p class="mb-3">{{ assessment.description|default:"No description provided" }}</p>
<div class="d-flex gap-2 flex-wrap">
<span class="risk-level-badge risk-{{ assessment.risk_level|lower }}">
{{ assessment.get_risk_level_display }} Risk
</span>
<span class="status-badge status-{{ assessment.status|lower }}">
{{ assessment.get_status_display }}
</span>
</div>
</div>
<div class="col-md-4 text-md-end">
<div class="risk-matrix">
<div class="matrix-item">
<div class="matrix-value">{{ assessment.risk_score|default:"-" }}</div>
<div class="matrix-label">Risk Score</div>
</div>
<div class="matrix-item">
<div class="matrix-value">{{ assessment.residual_risk|default:"-" }}</div>
<div class="matrix-label">Residual Risk</div>
</div>
</div>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="action-buttons">
{% if assessment.can_edit %}
<a href="{% url 'quality:risk_assessment_edit' assessment.id %}" class="btn-action">
<i class="fas fa-edit me-1"></i>Edit Assessment
</a>
{% endif %}
{% if assessment.can_approve %}
<button type="button" class="btn-action" onclick="approveAssessment()">
<i class="fas fa-check me-1"></i>Approve
</button>
{% endif %}
<button type="button" class="btn-primary-action" onclick="addMitigation()">
<i class="fas fa-shield-alt me-1"></i>Add Mitigation
</button>
<button type="button" class="btn-action" onclick="scheduleReview()">
<i class="fas fa-calendar-plus me-1"></i>Schedule Review
</button>
<button type="button" class="btn-action" onclick="generateReport()">
<i class="fas fa-file-pdf me-1"></i>Generate Report
</button>
<button type="button" class="btn-action" onclick="exportData()">
<i class="fas fa-download me-1"></i>Export
</button>
<button type="button" class="btn-action" onclick="window.print()">
<i class="fas fa-print me-1"></i>Print
</button>
</div>
<div class="assessment-layout">
<!-- Main Content -->
<div class="assessment-main">
<!-- Assessment Information -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-info-circle"></i>
Assessment Information
</div>
<div class="section-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Assessment ID</div>
<div class="info-value">{{ assessment.assessment_id|default:assessment.id }}</div>
</div>
<div class="info-item">
<div class="info-label">Department</div>
<div class="info-value">{{ assessment.department.name|default:"Not specified" }}</div>
</div>
<div class="info-item">
<div class="info-label">Assessor</div>
<div class="info-value">{{ assessment.assessor.get_full_name|default:"Not assigned" }}</div>
</div>
<div class="info-item">
<div class="info-label">Assessment Date</div>
<div class="info-value">{{ assessment.assessment_date|date:"M d, Y"|default:"Not set" }}</div>
</div>
<div class="info-item">
<div class="info-label">Next Review</div>
<div class="info-value">{{ assessment.next_review_date|date:"M d, Y"|default:"Not scheduled" }}</div>
</div>
<div class="info-item">
<div class="info-label">Priority</div>
<div class="info-value">{{ assessment.get_priority_display|default:"Not set" }}</div>
</div>
<div class="info-item">
<div class="info-label">Created</div>
<div class="info-value">{{ assessment.created_at|date:"M d, Y" }}</div>
</div>
<div class="info-item">
<div class="info-label">Last Updated</div>
<div class="info-value">{{ assessment.updated_at|date:"M d, Y" }}</div>
</div>
</div>
{% if assessment.description %}
<div class="risk-description">
<h6><i class="fas fa-file-alt me-2"></i>Description</h6>
<p class="mb-0">{{ assessment.description }}</p>
</div>
{% endif %}
</div>
</div>
<!-- Risk Analysis -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-chart-line"></i>
Risk Analysis
</div>
<div class="section-content">
<div class="risk-matrix">
<div class="matrix-item">
<div class="matrix-value">{{ assessment.likelihood|default:"-" }}</div>
<div class="matrix-label">Likelihood</div>
</div>
<div class="matrix-item">
<div class="matrix-value">{{ assessment.impact|default:"-" }}</div>
<div class="matrix-label">Impact</div>
</div>
<div class="matrix-item">
<div class="matrix-value">{{ assessment.risk_score|default:"-" }}</div>
<div class="matrix-label">Risk Score</div>
</div>
<div class="matrix-item">
<div class="matrix-value">{{ assessment.residual_risk|default:"-" }}</div>
<div class="matrix-label">Residual Risk</div>
</div>
</div>
{% if assessment.risk_factors %}
<div class="risk-description">
<h6><i class="fas fa-exclamation-triangle me-2"></i>Risk Factors</h6>
<p class="mb-0">{{ assessment.risk_factors }}</p>
</div>
{% endif %}
{% if assessment.potential_consequences %}
<div class="risk-description">
<h6><i class="fas fa-warning me-2"></i>Potential Consequences</h6>
<p class="mb-0">{{ assessment.potential_consequences }}</p>
</div>
{% endif %}
</div>
</div>
<!-- Mitigation Actions -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-shield-alt"></i>
Mitigation Actions ({{ assessment.mitigation_actions.count }})
</div>
<div class="section-content">
{% if assessment.mitigation_progress %}
<div class="progress-section">
<div class="progress-header">
<div class="progress-title">Overall Mitigation Progress</div>
<div class="progress-percentage">{{ assessment.mitigation_progress|default:0 }}%</div>
</div>
<div class="progress-bar-container">
<div class="progress-bar-fill" style="width: {{ assessment.mitigation_progress|default:0 }}%;"></div>
</div>
<div class="progress-details">
<div>Completed: {{ assessment.completed_mitigations|default:0 }}</div>
<div>In Progress: {{ assessment.in_progress_mitigations|default:0 }}</div>
<div>Pending: {{ assessment.pending_mitigations|default:0 }}</div>
</div>
</div>
{% endif %}
{% for mitigation in assessment.mitigation_actions.all %}
<div class="mitigation-item">
<div class="mitigation-icon">
{% if mitigation.status == 'completed' %}
<i class="fas fa-check"></i>
{% elif mitigation.status == 'in_progress' %}
<i class="fas fa-clock"></i>
{% else %}
<i class="fas fa-hourglass-start"></i>
{% endif %}
</div>
<div class="mitigation-content">
<div class="mitigation-title">{{ mitigation.title }}</div>
<div class="mitigation-description">{{ mitigation.description|default:"No description provided" }}</div>
<div class="mitigation-meta">
<span class="mitigation-status status-{{ mitigation.status|lower }}">
{{ mitigation.get_status_display }}
</span>
<span>Due: {{ mitigation.due_date|date:"M d, Y"|default:"Not set" }}</span>
<span>Assigned: {{ mitigation.assigned_to.get_full_name|default:"Not assigned" }}</span>
</div>
</div>
</div>
{% empty %}
<div class="text-center py-4 text-muted">
<i class="fas fa-shield-alt fa-2x mb-2"></i>
<p>No mitigation actions defined</p>
<button type="button" class="btn btn-outline-danger btn-sm" onclick="addMitigation()">
<i class="fas fa-plus me-1"></i>Add First Mitigation
</button>
</div>
{% endfor %}
</div>
</div>
<!-- Activity Timeline -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-history"></i>
Activity Timeline
</div>
<div class="section-content">
{% for activity in assessment.activities.all %}
<div class="timeline-item">
<div class="timeline-icon">
{% if activity.action == 'created' %}
<i class="fas fa-plus"></i>
{% elif activity.action == 'updated' %}
<i class="fas fa-edit"></i>
{% elif activity.action == 'approved' %}
<i class="fas fa-check"></i>
{% elif activity.action == 'reviewed' %}
<i class="fas fa-eye"></i>
{% else %}
<i class="fas fa-circle"></i>
{% endif %}
</div>
<div class="timeline-content">
<div class="timeline-title">{{ activity.get_action_display }}</div>
<div class="timeline-description">{{ activity.description|default:"No description" }}</div>
<div class="timeline-meta">
{{ activity.created_at|date:"M d, Y g:i A" }} by {{ activity.user.get_full_name|default:"System" }}
</div>
</div>
</div>
{% empty %}
<div class="text-center py-4 text-muted">
<i class="fas fa-history fa-2x mb-2"></i>
<p>No activity recorded</p>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Sidebar -->
<div class="assessment-sidebar">
<!-- Risk Trend Chart -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-chart-area"></i>
Risk Trend
</div>
<div class="section-content">
<div class="chart-controls">
<button type="button" class="chart-control active" onclick="updateChart('30d')">30D</button>
<button type="button" class="chart-control" onclick="updateChart('90d')">90D</button>
<button type="button" class="chart-control" onclick="updateChart('1y')">1Y</button>
</div>
<div class="chart-container">
<canvas id="riskTrendChart"></canvas>
</div>
</div>
</div>
<!-- Active Alerts -->
{% if assessment.alerts.exists %}
<div class="section-card">
<div class="section-header">
<i class="fas fa-exclamation-triangle"></i>
Active Alerts ({{ assessment.alerts.count }})
</div>
<div class="section-content">
{% for alert in assessment.alerts.all %}
<div class="alert-item">
<div class="alert-icon">
<i class="fas fa-exclamation"></i>
</div>
<div class="alert-content">
<div class="alert-title">{{ alert.title }}</div>
<div class="alert-message">{{ alert.message|truncatechars:100 }}</div>
<div class="alert-meta">
{{ alert.created_at|date:"M d, Y" }} - {{ alert.get_severity_display }}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Team Members -->
{% if assessment.team_members.exists %}
<div class="section-card">
<div class="section-header">
<i class="fas fa-users"></i>
Team Members ({{ assessment.team_members.count }})
</div>
<div class="section-content">
{% for member in assessment.team_members.all %}
<div class="team-member">
<div class="member-avatar">
{{ member.first_name.0|upper }}{{ member.last_name.0|upper }}
</div>
<div class="member-info">
<div class="member-name">{{ member.get_full_name }}</div>
<div class="member-role">{{ member.profile.role|default:"Team Member" }}</div>
<div class="member-contact">{{ member.email }}</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Documents -->
{% if assessment.documents.exists %}
<div class="section-card">
<div class="section-header">
<i class="fas fa-file-alt"></i>
Documents ({{ assessment.documents.count }})
</div>
<div class="section-content">
<ul class="documents-list">
{% for document in assessment.documents.all %}
<li class="document-item">
<div class="document-icon">
<i class="fas fa-file-pdf"></i>
</div>
<div class="document-info">
<div class="document-name">{{ document.name }}</div>
<div class="document-meta">
{{ document.file_size|filesizeformat }} - {{ document.uploaded_at|date:"M d, Y" }}
</div>
</div>
<a href="{{ document.file.url }}" target="_blank" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-download"></i>
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
<!-- Quick Stats -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-chart-bar"></i>
Quick Statistics
</div>
<div class="section-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Days Since Created</div>
<div class="info-value">{{ assessment.days_since_created|default:0 }}</div>
</div>
<div class="info-item">
<div class="info-label">Days Until Review</div>
<div class="info-value">{{ assessment.days_until_review|default:"N/A" }}</div>
</div>
<div class="info-item">
<div class="info-label">Mitigation Actions</div>
<div class="info-value">{{ assessment.mitigation_actions.count }}</div>
</div>
<div class="info-item">
<div class="info-label">Completion Rate</div>
<div class="info-value">{{ assessment.mitigation_progress|default:0 }}%</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Add Mitigation Modal -->
<div class="modal fade" id="addMitigationModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-shield-alt me-2"></i>Add Mitigation Action
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="mitigation-form">
<div class="row">
<div class="col-md-8">
<div class="mb-3">
<label class="form-label">Title</label>
<input type="text" class="form-control" name="title" required>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Priority</label>
<select class="form-select" name="priority">
<option value="low">Low</option>
<option value="medium" selected>Medium</option>
<option value="high">High</option>
<option value="critical">Critical</option>
</select>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea class="form-control" name="description" rows="3" required></textarea>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Assigned To</label>
<select class="form-select" name="assigned_to">
<option value="">Select team member</option>
{% for member in assessment.team_members.all %}
<option value="{{ member.id }}">{{ member.get_full_name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Due Date</label>
<input type="date" class="form-control" name="due_date">
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Expected Outcome</label>
<textarea class="form-control" name="expected_outcome" rows="2"></textarea>
</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-danger" onclick="saveMitigation()">
<i class="fas fa-save me-1"></i>Add Mitigation
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
let riskTrendChart;
$(document).ready(function() {
initializeRiskTrendChart();
});
function initializeRiskTrendChart() {
const ctx = document.getElementById('riskTrendChart').getContext('2d');
riskTrendChart = new Chart(ctx, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'Risk Score',
data: [12, 15, 13, 11, 9, 8],
borderColor: '#dc3545',
backgroundColor: 'rgba(220, 53, 69, 0.1)',
borderWidth: 2,
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
max: 20
}
}
}
});
}
function updateChart(period) {
$('.chart-control').removeClass('active');
$(`.chart-control:contains('${period.toUpperCase()}')`).addClass('active');
// Update chart data based on period
let newData, newLabels;
switch(period) {
case '30d':
newLabels = ['Week 1', 'Week 2', 'Week 3', 'Week 4'];
newData = [12, 11, 9, 8];
break;
case '90d':
newLabels = ['Month 1', 'Month 2', 'Month 3'];
newData = [15, 12, 8];
break;
case '1y':
newLabels = ['Q1', 'Q2', 'Q3', 'Q4'];
newData = [18, 15, 12, 8];
break;
}
riskTrendChart.data.labels = newLabels;
riskTrendChart.data.datasets[0].data = newData;
riskTrendChart.update();
}
function approveAssessment() {
if (confirm('Approve this risk assessment?')) {
fetch(`/quality/risk-assessments/{{ assessment.id }}/approve/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert('Assessment approved successfully', 'success');
setTimeout(() => location.reload(), 1500);
} else {
showAlert('Error approving assessment', 'danger');
}
})
.catch(error => {
showAlert('Error approving assessment', 'danger');
});
}
}
function addMitigation() {
new bootstrap.Modal(document.getElementById('addMitigationModal')).show();
}
function saveMitigation() {
const form = document.getElementById('mitigation-form');
const formData = new FormData(form);
fetch(`/quality/risk-assessments/{{ assessment.id }}/add-mitigation/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
},
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert('Mitigation action added successfully', 'success');
setTimeout(() => location.reload(), 1500);
} else {
showAlert('Error adding mitigation action', 'danger');
}
})
.catch(error => {
showAlert('Error adding mitigation action', 'danger');
});
bootstrap.Modal.getInstance(document.getElementById('addMitigationModal')).hide();
}
function scheduleReview() {
const reviewDate = prompt('Enter review date (YYYY-MM-DD):');
if (reviewDate) {
fetch(`/quality/risk-assessments/{{ assessment.id }}/schedule-review/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json'
},
body: JSON.stringify({
review_date: reviewDate
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert('Review scheduled successfully', 'success');
setTimeout(() => location.reload(), 1500);
} else {
showAlert('Error scheduling review', 'danger');
}
})
.catch(error => {
showAlert('Error scheduling review', 'danger');
});
}
}
function generateReport() {
showAlert('Generating assessment report...', 'info');
window.open(`/quality/risk-assessments/{{ assessment.id }}/report/`, '_blank');
}
function exportData() {
window.open(`/quality/risk-assessments/{{ assessment.id }}/export/`, '_blank');
}
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 %}