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

1242 lines
46 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}{% if assessment %}Edit{% else %}Create{% endif %} Risk Assessment{% endblock %}
{% block css %}
<style>
.form-header {
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.form-layout {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.form-main {
display: flex;
flex-direction: column;
gap: 2rem;
}
.form-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-matrix-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
margin-bottom: 1.5rem;
}
.matrix-input-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.matrix-input {
text-align: center;
font-size: 1.25rem;
font-weight: bold;
color: #dc3545;
border: 2px solid #dee2e6;
border-radius: 0.375rem;
padding: 1rem;
transition: all 0.2s;
}
.matrix-input:focus {
border-color: #dc3545;
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
}
.matrix-label {
font-size: 0.875rem;
color: #6c757d;
font-weight: 600;
text-transform: uppercase;
text-align: center;
}
.risk-calculator {
background: #f8f9fa;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1.5rem;
}
.calculator-title {
font-weight: 600;
color: #495057;
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.risk-score-display {
text-align: center;
padding: 1rem;
background: white;
border-radius: 0.375rem;
border: 2px solid #dee2e6;
margin-bottom: 1rem;
}
.risk-score-value {
font-size: 2rem;
font-weight: bold;
color: #dc3545;
margin-bottom: 0.25rem;
}
.risk-score-label {
font-size: 0.875rem;
color: #6c757d;
font-weight: 600;
text-transform: uppercase;
}
.risk-level-indicator {
padding: 0.5rem 1rem;
border-radius: 0.25rem;
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
text-align: center;
margin-bottom: 1rem;
}
.risk-critical { background: #dc3545; color: white; }
.risk-high { background: #fd7e14; color: white; }
.risk-medium { background: #ffc107; color: #212529; }
.risk-low { background: #28a745; color: white; }
.mitigation-section {
margin-top: 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-inputs {
display: grid;
grid-template-columns: 2fr 1fr 1fr;
gap: 0.75rem;
margin-bottom: 0.75rem;
}
.mitigation-actions {
display: flex;
gap: 0.5rem;
}
.btn-remove-mitigation {
padding: 0.25rem 0.5rem;
border: 1px solid #dc3545;
background: white;
color: #dc3545;
border-radius: 0.25rem;
cursor: pointer;
transition: all 0.2s;
font-size: 0.75rem;
}
.btn-remove-mitigation:hover {
background: #dc3545;
color: white;
}
.form-tabs {
display: flex;
background: #f8f9fa;
border-radius: 0.5rem 0.5rem 0 0;
border: 1px solid #dee2e6;
border-bottom: none;
}
.tab-button {
flex: 1;
padding: 1rem;
border: none;
background: transparent;
cursor: pointer;
transition: all 0.2s;
font-weight: 600;
color: #6c757d;
}
.tab-button.active {
background: white;
color: #dc3545;
border-bottom: 2px solid #dc3545;
}
.tab-content {
display: none;
background: white;
border: 1px solid #dee2e6;
border-radius: 0 0 0.5rem 0.5rem;
padding: 1.5rem;
}
.tab-content.active {
display: block;
}
.auto-save-indicator {
position: fixed;
top: 20px;
right: 20px;
background: #28a745;
color: white;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
font-size: 0.875rem;
z-index: 1050;
opacity: 0;
transition: opacity 0.3s;
}
.auto-save-indicator.show {
opacity: 1;
}
.form-preview {
background: #f8f9fa;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1rem;
}
.preview-title {
font-weight: 600;
color: #495057;
margin-bottom: 0.75rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.preview-content {
font-size: 0.875rem;
color: #6c757d;
line-height: 1.5;
}
.help-section {
background: #e3f2fd;
border-left: 4px solid #2196f3;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1rem;
}
.help-title {
font-weight: 600;
color: #1976d2;
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.help-content {
font-size: 0.875rem;
color: #1976d2;
line-height: 1.5;
}
.help-list {
list-style: none;
padding: 0;
margin: 0;
}
.help-item {
display: flex;
align-items: start;
gap: 0.5rem;
margin-bottom: 0.5rem;
font-size: 0.875rem;
color: #1976d2;
}
.help-icon {
width: 16px;
height: 16px;
border-radius: 50%;
background: #2196f3;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
flex-shrink: 0;
margin-top: 0.125rem;
}
.form-actions {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
padding: 1.5rem;
display: flex;
justify-content: between;
align-items: center;
gap: 1rem;
}
.alternative-actions {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.btn-alternative {
padding: 0.5rem 1rem;
border: 1px solid #dee2e6;
background: white;
color: #6c757d;
border-radius: 0.25rem;
cursor: pointer;
transition: all 0.2s;
font-size: 0.875rem;
text-decoration: none;
}
.btn-alternative:hover {
border-color: #dc3545;
color: #dc3545;
text-decoration: none;
}
.primary-actions {
display: flex;
gap: 0.5rem;
}
@media (max-width: 1200px) {
.form-layout {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.form-sidebar {
order: -1;
}
}
@media (max-width: 768px) {
.form-header {
padding: 1.5rem;
}
.risk-matrix-grid {
grid-template-columns: 1fr;
}
.mitigation-inputs {
grid-template-columns: 1fr;
}
.form-actions {
flex-direction: column;
align-items: stretch;
}
.alternative-actions, .primary-actions {
justify-content: center;
}
}
@media print {
.form-sidebar, .form-actions {
display: none !important;
}
.form-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">{% if assessment %}Edit{% else %}Create{% endif %}</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-shield-alt me-2"></i>{% if assessment %}Edit{% else %}Create{% endif %} Risk Assessment
</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>
<!-- Form Header -->
<div class="form-header">
<div class="row align-items-center">
<div class="col-md-8">
<h2 class="mb-2">
{% if assessment %}
Edit Risk Assessment: {{ assessment.title }}
{% else %}
Create New Risk Assessment
{% endif %}
</h2>
<p class="mb-0">Complete all sections to create a comprehensive risk assessment for your organization.</p>
</div>
<div class="col-md-4 text-md-end">
<div class="risk-score-display" id="risk-score-preview">
<div class="risk-score-value" id="calculated-score">-</div>
<div class="risk-score-label">Risk Score</div>
</div>
</div>
</div>
</div>
<!-- Auto-save Indicator -->
<div class="auto-save-indicator" id="auto-save-indicator">
<i class="fas fa-check me-1"></i>Changes saved
</div>
<form method="post" id="assessment-form" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-layout">
<!-- Main Form Content -->
<div class="form-main">
<!-- Basic Information -->
<div class="section-card">
<div class="form-tabs">
<button type="button" class="tab-button active" onclick="switchTab('basic')">
<i class="fas fa-info-circle me-1"></i>Basic Information
</button>
<button type="button" class="tab-button" onclick="switchTab('risk')">
<i class="fas fa-chart-line me-1"></i>Risk Analysis
</button>
<button type="button" class="tab-button" onclick="switchTab('mitigation')">
<i class="fas fa-shield-alt me-1"></i>Mitigation
</button>
</div>
<!-- Basic Information Tab -->
<div class="tab-content active" id="basic-tab">
<div class="row">
<div class="col-md-8">
<div class="mb-3">
<label class="form-label">Assessment Title <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="title"
value="{{ assessment.title|default:'' }}" required>
<div class="form-text">Provide a clear, descriptive title for this risk assessment</div>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Assessment ID</label>
<input type="text" class="form-control" name="assessment_id"
value="{{ assessment.assessment_id|default:'' }}" placeholder="Auto-generated">
<div class="form-text">Leave blank for auto-generation</div>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Description <span class="text-danger">*</span></label>
<textarea class="form-control" name="description" rows="4" required>{{ assessment.description|default:'' }}</textarea>
<div class="form-text">Detailed description of the risk being assessed</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Department <span class="text-danger">*</span></label>
<select class="form-select" name="department" required>
<option value="">Select Department</option>
{% for dept in departments %}
<option value="{{ dept.id }}" {% if assessment.department_id == dept.id %}selected{% endif %}>
{{ dept.name }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Assessor <span class="text-danger">*</span></label>
<select class="form-select" name="assessor" required>
<option value="">Select Assessor</option>
{% for user in assessors %}
<option value="{{ user.id }}" {% if assessment.assessor_id == user.id %}selected{% endif %}>
{{ user.get_full_name }}
</option>
{% endfor %}
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Assessment Date</label>
<input type="date" class="form-control" name="assessment_date"
value="{{ assessment.assessment_date|date:'Y-m-d'|default:'' }}">
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Next Review Date</label>
<input type="date" class="form-control" name="next_review_date"
value="{{ assessment.next_review_date|date:'Y-m-d'|default:'' }}">
</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" {% if assessment.priority == 'low' %}selected{% endif %}>Low</option>
<option value="medium" {% if assessment.priority == 'medium' %}selected{% endif %}>Medium</option>
<option value="high" {% if assessment.priority == 'high' %}selected{% endif %}>High</option>
<option value="critical" {% if assessment.priority == 'critical' %}selected{% endif %}>Critical</option>
</select>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Risk Factors</label>
<textarea class="form-control" name="risk_factors" rows="3">{{ assessment.risk_factors|default:'' }}</textarea>
<div class="form-text">Identify and describe the key factors contributing to this risk</div>
</div>
<div class="mb-3">
<label class="form-label">Potential Consequences</label>
<textarea class="form-control" name="potential_consequences" rows="3">{{ assessment.potential_consequences|default:'' }}</textarea>
<div class="form-text">Describe what could happen if this risk materializes</div>
</div>
</div>
<!-- Risk Analysis Tab -->
<div class="tab-content" id="risk-tab">
<div class="risk-calculator">
<div class="calculator-title">
<i class="fas fa-calculator"></i>Risk Matrix Calculator
</div>
<div class="risk-matrix-grid">
<div class="matrix-input-group">
<input type="number" class="form-control matrix-input" name="likelihood"
value="{{ assessment.likelihood|default:'' }}" min="1" max="5"
placeholder="1-5" onchange="calculateRiskScore()">
<div class="matrix-label">Likelihood</div>
<div class="form-text">1 = Very Unlikely, 5 = Very Likely</div>
</div>
<div class="matrix-input-group">
<input type="number" class="form-control matrix-input" name="impact"
value="{{ assessment.impact|default:'' }}" min="1" max="5"
placeholder="1-5" onchange="calculateRiskScore()">
<div class="matrix-label">Impact</div>
<div class="form-text">1 = Minimal, 5 = Catastrophic</div>
</div>
</div>
<div class="risk-score-display">
<div class="risk-score-value" id="risk-score-value">-</div>
<div class="risk-score-label">Calculated Risk Score</div>
</div>
<div class="risk-level-indicator" id="risk-level-indicator">
Risk Level: Not Calculated
</div>
<input type="hidden" name="risk_score" id="risk-score-input">
<input type="hidden" name="risk_level" id="risk-level-input">
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Current Controls</label>
<textarea class="form-control" name="current_controls" rows="4">{{ assessment.current_controls|default:'' }}</textarea>
<div class="form-text">Existing measures in place to manage this risk</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Control Effectiveness</label>
<select class="form-select" name="control_effectiveness">
<option value="">Select Effectiveness</option>
<option value="poor" {% if assessment.control_effectiveness == 'poor' %}selected{% endif %}>Poor</option>
<option value="fair" {% if assessment.control_effectiveness == 'fair' %}selected{% endif %}>Fair</option>
<option value="good" {% if assessment.control_effectiveness == 'good' %}selected{% endif %}>Good</option>
<option value="excellent" {% if assessment.control_effectiveness == 'excellent' %}selected{% endif %}>Excellent</option>
</select>
<div class="form-text">How effective are the current controls?</div>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Residual Risk Score</label>
<input type="number" class="form-control" name="residual_risk"
value="{{ assessment.residual_risk|default:'' }}" min="1" max="25">
<div class="form-text">Risk score after considering current controls</div>
</div>
</div>
<!-- Mitigation Tab -->
<div class="tab-content" id="mitigation-tab">
<div class="mitigation-section">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6><i class="fas fa-shield-alt me-2"></i>Mitigation Actions</h6>
<button type="button" class="btn btn-outline-danger btn-sm" onclick="addMitigation()">
<i class="fas fa-plus me-1"></i>Add Mitigation
</button>
</div>
<div id="mitigation-container">
{% for mitigation in assessment.mitigation_actions.all %}
<div class="mitigation-item">
<div class="mitigation-icon">
<i class="fas fa-shield-alt"></i>
</div>
<div class="mitigation-content">
<div class="mitigation-inputs">
<input type="text" class="form-control" name="mitigation_title[]"
value="{{ mitigation.title }}" placeholder="Mitigation title">
<select class="form-select" name="mitigation_priority[]">
<option value="low" {% if mitigation.priority == 'low' %}selected{% endif %}>Low</option>
<option value="medium" {% if mitigation.priority == 'medium' %}selected{% endif %}>Medium</option>
<option value="high" {% if mitigation.priority == 'high' %}selected{% endif %}>High</option>
<option value="critical" {% if mitigation.priority == 'critical' %}selected{% endif %}>Critical</option>
</select>
<input type="date" class="form-control" name="mitigation_due_date[]"
value="{{ mitigation.due_date|date:'Y-m-d' }}">
</div>
<textarea class="form-control mb-2" name="mitigation_description[]" rows="2"
placeholder="Describe the mitigation action...">{{ mitigation.description }}</textarea>
<div class="mitigation-actions">
<button type="button" class="btn-remove-mitigation" onclick="removeMitigation(this)">
<i class="fas fa-trash me-1"></i>Remove
</button>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="text-center py-3" id="no-mitigations" {% if assessment.mitigation_actions.exists %}style="display: none;"{% endif %}>
<div class="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>
</div>
</div>
<div class="row mt-4">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Review Frequency</label>
<select class="form-select" name="review_frequency">
<option value="">Select Frequency</option>
<option value="monthly" {% if assessment.review_frequency == 'monthly' %}selected{% endif %}>Monthly</option>
<option value="quarterly" {% if assessment.review_frequency == 'quarterly' %}selected{% endif %}>Quarterly</option>
<option value="semi-annually" {% if assessment.review_frequency == 'semi-annually' %}selected{% endif %}>Semi-Annually</option>
<option value="annually" {% if assessment.review_frequency == 'annually' %}selected{% endif %}>Annually</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Monitoring Method</label>
<select class="form-select" name="monitoring_method">
<option value="">Select Method</option>
<option value="manual" {% if assessment.monitoring_method == 'manual' %}selected{% endif %}>Manual Review</option>
<option value="automated" {% if assessment.monitoring_method == 'automated' %}selected{% endif %}>Automated Monitoring</option>
<option value="hybrid" {% if assessment.monitoring_method == 'hybrid' %}selected{% endif %}>Hybrid Approach</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="form-sidebar">
<!-- Live Preview -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-eye"></i>
Live Preview
</div>
<div class="section-content">
<div class="form-preview">
<div class="preview-title">
<i class="fas fa-file-alt"></i>Assessment Preview
</div>
<div class="preview-content" id="preview-content">
<p class="text-muted">Start filling the form to see a preview...</p>
</div>
</div>
</div>
</div>
<!-- Help & Tips -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-question-circle"></i>
Help & Tips
</div>
<div class="section-content">
<div class="help-section">
<div class="help-title">
<i class="fas fa-lightbulb"></i>Risk Assessment Tips
</div>
<ul class="help-list">
<li class="help-item">
<div class="help-icon">1</div>
<span>Use clear, specific language in your risk description</span>
</li>
<li class="help-item">
<div class="help-icon">2</div>
<span>Consider both likelihood and impact when scoring</span>
</li>
<li class="help-item">
<div class="help-icon">3</div>
<span>Include multiple mitigation strategies for high risks</span>
</li>
<li class="help-item">
<div class="help-icon">4</div>
<span>Schedule regular reviews to keep assessments current</span>
</li>
</ul>
</div>
<div class="help-section">
<div class="help-title">
<i class="fas fa-chart-line"></i>Risk Scoring Guide
</div>
<div class="help-content">
<p><strong>Likelihood Scale:</strong></p>
<p>1 = Very Unlikely (0-5%)<br>
2 = Unlikely (6-25%)<br>
3 = Possible (26-50%)<br>
4 = Likely (51-75%)<br>
5 = Very Likely (76-100%)</p>
<p><strong>Impact Scale:</strong></p>
<p>1 = Minimal<br>
2 = Minor<br>
3 = Moderate<br>
4 = Major<br>
5 = Catastrophic</p>
</div>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-bolt"></i>
Quick Actions
</div>
<div class="section-content">
<div class="d-grid gap-2">
<button type="button" class="btn btn-outline-info btn-sm" onclick="loadTemplate()">
<i class="fas fa-file-import me-1"></i>Load Template
</button>
<button type="button" class="btn btn-outline-success btn-sm" onclick="saveTemplate()">
<i class="fas fa-save me-1"></i>Save as Template
</button>
<button type="button" class="btn btn-outline-warning btn-sm" onclick="validateForm()">
<i class="fas fa-check-circle me-1"></i>Validate Form
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="resetForm()">
<i class="fas fa-undo me-1"></i>Reset Form
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Form Actions -->
<div class="form-actions">
<div class="alternative-actions">
<button type="button" class="btn-alternative" onclick="saveDraft()">
<i class="fas fa-save me-1"></i>Save Draft
</button>
<button type="button" class="btn-alternative" onclick="previewAssessment()">
<i class="fas fa-eye me-1"></i>Preview
</button>
<a href="{% url 'quality:risk_assessment_list' %}" class="btn-alternative">
<i class="fas fa-times me-1"></i>Cancel
</a>
</div>
<div class="primary-actions">
<button type="submit" name="action" value="save" class="btn btn-outline-danger">
<i class="fas fa-save me-1"></i>Save Assessment
</button>
<button type="submit" name="action" value="save_and_submit" class="btn btn-danger">
<i class="fas fa-paper-plane me-1"></i>Save & Submit for Review
</button>
</div>
</div>
</form>
</div>
<!-- Load Template Modal -->
<div class="modal fade" id="templateModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-file-import me-2"></i>Load Assessment Template
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row">
{% for template in templates %}
<div class="col-md-6 mb-3">
<div class="card">
<div class="card-body">
<h6 class="card-title">{{ template.name }}</h6>
<p class="card-text">{{ template.description|truncatechars:100 }}</p>
<button type="button" class="btn btn-outline-danger btn-sm"
onclick="applyTemplate({{ template.id }})">
<i class="fas fa-download me-1"></i>Use Template
</button>
</div>
</div>
</div>
{% empty %}
<div class="col-12 text-center py-4">
<div class="text-muted">
<i class="fas fa-file-alt fa-2x mb-2"></i>
<p>No templates available</p>
</div>
</div>
{% endfor %}
</div>
</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>Close
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script>
let autoSaveTimer;
let mitigationCount = {{ assessment.mitigation_actions.count|default:0 }};
$(document).ready(function() {
// Initialize form
calculateRiskScore();
updatePreview();
// Auto-save functionality
$('#assessment-form input, #assessment-form textarea, #assessment-form select').on('input change', function() {
clearTimeout(autoSaveTimer);
autoSaveTimer = setTimeout(autoSave, 2000);
updatePreview();
});
// Calculate risk score when likelihood or impact changes
$('input[name="likelihood"], input[name="impact"]').on('input', calculateRiskScore);
});
function switchTab(tabName) {
$('.tab-button').removeClass('active');
$('.tab-content').removeClass('active');
$(`.tab-button:contains('${tabName.charAt(0).toUpperCase() + tabName.slice(1)}')`).addClass('active');
$(`#${tabName}-tab`).addClass('active');
}
function calculateRiskScore() {
const likelihood = parseInt($('input[name="likelihood"]').val()) || 0;
const impact = parseInt($('input[name="impact"]').val()) || 0;
if (likelihood > 0 && impact > 0) {
const riskScore = likelihood * impact;
let riskLevel, riskClass;
if (riskScore >= 20) {
riskLevel = 'Critical';
riskClass = 'risk-critical';
} else if (riskScore >= 15) {
riskLevel = 'High';
riskClass = 'risk-high';
} else if (riskScore >= 8) {
riskLevel = 'Medium';
riskClass = 'risk-medium';
} else {
riskLevel = 'Low';
riskClass = 'risk-low';
}
$('#risk-score-value, #calculated-score').text(riskScore);
$('#risk-level-indicator').text(`Risk Level: ${riskLevel}`).removeClass().addClass(`risk-level-indicator ${riskClass}`);
$('#risk-score-input').val(riskScore);
$('#risk-level-input').val(riskLevel.toLowerCase());
} else {
$('#risk-score-value, #calculated-score').text('-');
$('#risk-level-indicator').text('Risk Level: Not Calculated').removeClass().addClass('risk-level-indicator');
$('#risk-score-input').val('');
$('#risk-level-input').val('');
}
}
function addMitigation() {
mitigationCount++;
const mitigationHtml = `
<div class="mitigation-item">
<div class="mitigation-icon">
<i class="fas fa-shield-alt"></i>
</div>
<div class="mitigation-content">
<div class="mitigation-inputs">
<input type="text" class="form-control" name="mitigation_title[]"
placeholder="Mitigation title">
<select class="form-select" name="mitigation_priority[]">
<option value="low">Low</option>
<option value="medium" selected>Medium</option>
<option value="high">High</option>
<option value="critical">Critical</option>
</select>
<input type="date" class="form-control" name="mitigation_due_date[]">
</div>
<textarea class="form-control mb-2" name="mitigation_description[]" rows="2"
placeholder="Describe the mitigation action..."></textarea>
<div class="mitigation-actions">
<button type="button" class="btn-remove-mitigation" onclick="removeMitigation(this)">
<i class="fas fa-trash me-1"></i>Remove
</button>
</div>
</div>
</div>
`;
$('#mitigation-container').append(mitigationHtml);
$('#no-mitigations').hide();
}
function removeMitigation(button) {
$(button).closest('.mitigation-item').remove();
if ($('#mitigation-container .mitigation-item').length === 0) {
$('#no-mitigations').show();
}
}
function updatePreview() {
const title = $('input[name="title"]').val();
const description = $('textarea[name="description"]').val();
const department = $('select[name="department"] option:selected').text();
const riskScore = $('#risk-score-input').val();
const riskLevel = $('#risk-level-input').val();
let previewHtml = '';
if (title) {
previewHtml += `<p><strong>Title:</strong> ${title}</p>`;
}
if (description) {
previewHtml += `<p><strong>Description:</strong> ${description.substring(0, 100)}${description.length > 100 ? '...' : ''}</p>`;
}
if (department && department !== 'Select Department') {
previewHtml += `<p><strong>Department:</strong> ${department}</p>`;
}
if (riskScore && riskLevel) {
previewHtml += `<p><strong>Risk:</strong> ${riskLevel} (Score: ${riskScore})</p>`;
}
if (!previewHtml) {
previewHtml = '<p class="text-muted">Start filling the form to see a preview...</p>';
}
$('#preview-content').html(previewHtml);
}
function autoSave() {
const formData = new FormData(document.getElementById('assessment-form'));
formData.append('auto_save', 'true');
fetch(window.location.href, {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAutoSaveIndicator();
}
})
.catch(error => {
console.log('Auto-save failed:', error);
});
}
function showAutoSaveIndicator() {
$('#auto-save-indicator').addClass('show');
setTimeout(() => {
$('#auto-save-indicator').removeClass('show');
}, 2000);
}
function saveDraft() {
const form = document.getElementById('assessment-form');
const actionInput = document.createElement('input');
actionInput.type = 'hidden';
actionInput.name = 'action';
actionInput.value = 'draft';
form.appendChild(actionInput);
form.submit();
}
function previewAssessment() {
const formData = new FormData(document.getElementById('assessment-form'));
// Open preview in new window
const previewWindow = window.open('', '_blank');
previewWindow.document.write('<html><head><title>Assessment Preview</title></head><body><h1>Loading preview...</h1></body></html>');
fetch('/quality/risk-assessments/preview/', {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
}
})
.then(response => response.text())
.then(html => {
previewWindow.document.open();
previewWindow.document.write(html);
previewWindow.document.close();
})
.catch(error => {
previewWindow.document.write('<h1>Error loading preview</h1>');
});
}
function loadTemplate() {
new bootstrap.Modal(document.getElementById('templateModal')).show();
}
function applyTemplate(templateId) {
fetch(`/quality/risk-assessments/templates/${templateId}/`, {
method: 'GET',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Populate form with template data
Object.keys(data.template).forEach(key => {
const field = document.querySelector(`[name="${key}"]`);
if (field) {
field.value = data.template[key];
}
});
showAlert('Template loaded successfully', 'success');
updatePreview();
calculateRiskScore();
} else {
showAlert('Error loading template', 'danger');
}
})
.catch(error => {
showAlert('Error loading template', 'danger');
});
bootstrap.Modal.getInstance(document.getElementById('templateModal')).hide();
}
function saveTemplate() {
const templateName = prompt('Enter template name:');
if (templateName) {
const formData = new FormData(document.getElementById('assessment-form'));
formData.append('template_name', templateName);
fetch('/quality/risk-assessments/save-template/', {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert('Template saved successfully', 'success');
} else {
showAlert('Error saving template', 'danger');
}
})
.catch(error => {
showAlert('Error saving template', 'danger');
});
}
}
function validateForm() {
const form = document.getElementById('assessment-form');
const requiredFields = form.querySelectorAll('[required]');
let isValid = true;
let errors = [];
requiredFields.forEach(field => {
if (!field.value.trim()) {
isValid = false;
errors.push(`${field.previousElementSibling.textContent.replace(' *', '')} is required`);
field.classList.add('is-invalid');
} else {
field.classList.remove('is-invalid');
}
});
if (isValid) {
showAlert('Form validation passed', 'success');
} else {
showAlert(`Validation failed: ${errors.join(', ')}`, 'danger');
}
}
function resetForm() {
if (confirm('Are you sure you want to reset the form? All unsaved changes will be lost.')) {
document.getElementById('assessment-form').reset();
$('#mitigation-container').empty();
$('#no-mitigations').show();
calculateRiskScore();
updatePreview();
showAlert('Form reset successfully', 'info');
}
}
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 %}