redesign single risk frontend

This commit is contained in:
Kevin Heyer 2025-09-11 15:30:42 +02:00
parent 324347e849
commit 0ba879c40e
2 changed files with 173 additions and 219 deletions

View file

@ -20,6 +20,10 @@
.has-background-prosoft a, .has-background-prosoft a,
.has-background-prosoft abbr { color: var(--prosoft-inv); } .has-background-prosoft abbr { color: var(--prosoft-inv); }
.table thead tr.has-background-prosoft th {
color: var(--prosoft-inv) !important;
}
abbr { text-decoration: none; } abbr { text-decoration: none; }
/* ========================= /* =========================
@ -282,7 +286,6 @@ body.dark-mode a { color: #bb86fc; }
.erp-tabs { .erp-tabs {
display: flex; display: flex;
border-bottom: 2px solid var(--prosoft-normal); border-bottom: 2px solid var(--prosoft-normal);
margin-bottom: 1rem;
} }
.erp-tabs a { .erp-tabs a {

View file

@ -1,53 +1,29 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n risk_extras %} {% load i18n risk_extras %}
{% block crumbs %} {% block crumbs %}
<li><a href="{% url 'risks:list_risks' %}">{% trans "Risk analysis" %}</a></li> <li><a href="{% url 'risks:list_risks' %}">{% trans "Risk analysis" %}</a></li>
<li><a href="{% url 'risks:show_risk' risk.id %}">{{ risk.title }}</a></li> <li><a href="{% url 'risks:show_risk' risk.id %}">{% trans "Risk" %}: {{ risk.title }}</a></li>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container">
<section class="hero is-small"> <!-- ERP-style tabs under breadcrumb -->
<div class="hero-body"> <div class="erp-tabs">
<p class="title">{% trans "Risk" %}: {{ risk.title }}</p> <a class="is-active" data-tab="overview">{% trans "Overview" %}</a>
<p class="subtitle is-6">{{ risk.description }}</p> <a data-tab="measures">{% trans "Measures" %}</a>
<a data-tab="incidents">{% trans "Incidents" %}</a>
<a data-tab="history">{% trans "History" %}</a>
</div> </div>
</section>
<!-- Überblick--> <!-- Tab: Overview -->
<div class="tab-panel" data-tab="overview">
<div class="card"> <div class="card">
<header class="card-header"> <header class="card-header">
<p class="card-header-title">{% trans "Overview" %}</p> <p class="card-header-title">{% trans "Overview" %}</p>
{% if request.user.is_staff or risk.owner_id == request.user.id %}
<form method="post" action="{% url 'risks:update_risk_status' risk.id %}" class="card-header-icon" style="margin-left:auto;">
{% csrf_token %}
<div class="field has-addons">
<div class="control">
<div class="select is-small">
<select name="status">
{% for value,label in risk.STATUS_CHOICES %}
<option value="{{ value }}" {% if risk.status == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="control">
<button class="button is-small is-link" title="{% trans 'Update status' %}">
<span class="icon"><i class="fas fa-save"></i></span>
</button>
</div>
</div>
</form>
<a class="card-header-icon has-text-warning" href="{% url 'admin:risks_risk_change' risk.pk %}" title="Risiko bearbeiten">
<span class="icon"><i class="fas fa-edit" aria-hidden="true"></i></span>
</a>
<a class="card-header-icon has-text-danger" href="{% url 'admin:risks_risk_delete' risk.pk %}" title="Risiko Löschen (WARNUNG!)">
<span class="icon"><i class="fas fa-trash" aria-hidden="true"></i></span>
</a>
{% endif %}
</header> </header>
<!-- Inhalt Überblick-->
<div class="card-content"> <div class="card-content">
<!-- General info -->
<div class="columns is-multiline"> <div class="columns is-multiline">
<div class="column is-half"> <div class="column is-half">
<p><strong>{% trans "Asset" %}:</strong> {{ risk.asset|default:"-" }}</p> <p><strong>{% trans "Asset" %}:</strong> {{ risk.asset|default:"-" }}</p>
@ -61,54 +37,58 @@
<span class="has-text-grey">{% trans "Not yet assigned" %}</span> <span class="has-text-grey">{% trans "Not yet assigned" %}</span>
{% endif %} {% endif %}
</p> </p>
<p><strong>{% trans "Status" %}:</strong> {{ risk.status }}</p> <p><strong>{% trans "Status" %}:</strong> {{ risk.get_status_display }}</p>
</div> </div>
<div class="column is-half"> <div class="column is-half">
<p><strong>{% trans "Risk owner" %}:</strong> {{ risk.owner|user_display|default:"-" }}</p> <p><strong>{% trans "Risk owner" %}:</strong> {{ risk.owner|user_display|default:"-" }}</p>
<p><strong>{% trans "Created at" %}:</strong> {{ risk.created_at|date:'d.m.Y H:i' }}</p> <p><strong>{% trans "Created at" %}:</strong> {{ risk.created_at|date:'d.m.Y H:i' }}</p>
<p><strong>{% trans "updated at" %}:</strong> {{ risk.updated_at|date:'d.m.Y H:i' }}</p> <p><strong>{% trans "Updated at" %}:</strong> {{ risk.updated_at|date:'d.m.Y H:i' }}</p>
<p><strong>{% trans "Resubmission" %}:</strong> {{ risk.follow_up|date:'d.m.Y' }}</p> <p><strong>{% trans "Resubmission" %}:</strong> {{ risk.follow_up|date:'d.m.Y' }}</p>
</div> </div>
</div> </div>
<!-- Effects -->
<section> <section>
<h3>Auswirkungen</h3> <h3 class="title is-6">{% trans "Effects" %}</h3>
<p>{{ risk.effects }}</p> <p>{{ risk.effects|default:"" }}</p>
</section> </section>
<!-- Risikobewertung -->
<!-- Risk assessment -->
<section class="hero has-text-centered is-small"> <section class="hero has-text-centered is-small">
<div class="hero-body"> <div class="hero-body">
<p class="title">{% trans "Risk assessment" %}</p> <p class="title">{% trans "Risk assessment" %}</p>
</div> </div>
</section> </section>
<div class="columns is-multiline"> <div class="columns is-multiline">
<!-- Bruttorisiko --> <!-- Gross risk -->
<div class="column is-full-mobile is-full-tablet is-half-desktop has-text-centered is-flex"> <div class="column is-full-mobile is-half-desktop has-text-centered is-flex">
<div class="box is-flex is-flex-direction-column is-flex-grow-1"> <div class="box is-flex is-flex-direction-column is-flex-grow-1">
<h4>{% trans "Gross (before measures)" %}</h4> <h4 class="title is-6">{% trans "Gross (before measures)" %}</h4>
<div class="columns is-multiline"> <div class="columns is-multiline">
<!-- Eintrittswahrscheinlichkeit --> <!-- Likelihood -->
<div class="column is-full-mobile is-full-tablet is-half-desktop has-text-centered"> <div class="column is-half">
<button class="risk-chip {{ risk.likelihood|likelihood_class }}" type="button" aria-label="{% trans 'Likelihood' as likelihood_long_name %}"> <button class="risk-chip {{ risk.likelihood|likelihood_class }}" type="button">
<span class="chip-head">{% trans "Probability of occurrence" %}</span> <span class="chip-head">{% trans "Likelihood" %}</span>
<span class="chip-id">{{ risk.likelihood }}</span> <span class="chip-id">{{ risk.likelihood }}</span>
<span class="chip-label">{{ risk.get_likelihood_display }}</span> <span class="chip-label">{{ risk.get_likelihood_display }}</span>
</button> </button>
</div> </div>
<!-- Schadensausmaß --> <!-- Impact -->
<div class="column is-full-mobile is-full-tablet is-half-desktop has-text-centered"> <div class="column is-half">
<button class="risk-chip {{ risk.impact|impact_class }}" type="button" aria-label="{% trans 'Impact' as impact_long_name %}"> <button class="risk-chip {{ risk.impact|impact_class }}" type="button">
<span class="chip-head">{% trans "Extent of damage" %}</span> <span class="chip-head">{% trans "Impact" %}</span>
<span class="chip-id">{{ risk.impact }}</span> <span class="chip-id">{{ risk.impact }}</span>
<span class="chip-label">{{ risk.get_impact_display }}</span> <span class="chip-label">{{ risk.get_impact_display }}</span>
</button> </button>
</div> </div>
<!-- Stufe --> <!-- Level -->
<div class="column is-full-mobile is-full-tablet is-half-desktop has-text-centered"> <div class="column is-half">
<button class="risk-chip {{ risk.level|level_class }}" type="button" aria-label="{% trans 'Level' %}"> <button class="risk-chip {{ risk.level|level_class }}" type="button">
<span class="chip-head">{% trans "Level" %}</span> <span class="chip-head">{% trans "Level" %}</span>
<span class="chip-id">{{ risk.level|level_id }}</span> <span class="chip-id">{{ risk.level|level_id }}</span>
<span class="chip-label">{{ risk.level }}</span> <span class="chip-label">{{ risk.level }}</span>
@ -116,45 +96,45 @@
</div> </div>
<!-- Score --> <!-- Score -->
<div class="column is-full-mobile is-full-tablet is-half-desktop has-text-centered"> <div class="column is-half">
<button class="risk-chip {{ risk.score|score_class }}" type="button" aria-label="{% trans 'Score' %}"> <button class="risk-chip {{ risk.score|score_class }}" type="button">
<span class="chip-head">{% trans "Score" %}</span> <span class="chip-head">{% trans "Score" %}</span>
<span class="chip-id">{{ risk.score }}</span> <span class="chip-id">{{ risk.score }}</span>
<span class="chip-label">Score (max. 20)</span> <span class="chip-label">Max. 20</span>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> <!-- Ende Bruttorisiko --> </div><!-- Gross risk End -->
<!-- Nettorisiko --> <!-- Net risk -->
<div class="column is-full-mobile is-full-tablet is-half-desktop has-text-centered is-flex"> <div class="column is-full-mobile is-half-desktop has-text-centered is-flex">
<div class="box is-flex is-flex-direction-column is-flex-grow-1"> <div class="box is-flex is-flex-direction-column is-flex-grow-1">
<h4>{% trans "Net (after measures)" %}</h4> <h4 class="title is-6">{% trans "Net (after measures)" %}</h4>
<div class="columns is-multiline"> <div class="columns is-multiline">
{% if risk.residual_risk %} {% if risk.residual_risk %}
<!-- Eintrittswahrscheinlichkeit --> <!-- Likelihood -->
<div class="column is-full-mobile is-full-tablet is-half-desktop has-text-centered"> <div class="column is-half">
<button class="risk-chip {{ risk.residual_risk.likelihood|likelihood_class }}" type="button" aria-label="{% trans 'Likelihood' as likelihood_long_name %}"> <button class="risk-chip {{ risk.residual_risk.likelihood|likelihood_class }}" type="button">
<span class="chip-head">{% trans "Probability of occurrence" %}</span> <span class="chip-head">{% trans "Likelihood" %}</span>
<span class="chip-id">{{ risk.residual_risk.likelihood }}</span> <span class="chip-id">{{ risk.residual_risk.likelihood }}</span>
<span class="chip-label">{{ risk.residual_risk.get_likelihood_display }}</span> <span class="chip-label">{{ risk.residual_risk.get_likelihood_display }}</span>
</button> </button>
</div> </div>
<!-- Schadensausmaß --> <!-- Impact -->
<div class="column is-full-mobile is-full-tablet is-half-desktop has-text-centered"> <div class="column is-half">
<button class="risk-chip {{ risk.residual_risk.impact|impact_class }}" type="button" aria-label="{% trans 'Impact' as impact_long_name %}"> <button class="risk-chip {{ risk.residual_risk.impact|impact_class }}" type="button">
<span class="chip-head">{% trans "Extent of damage" %}</span> <span class="chip-head">{% trans "Impact" %}</span>
<span class="chip-id">{{ risk.residual_risk.impact }}</span> <span class="chip-id">{{ risk.residual_risk.impact }}</span>
<span class="chip-label">{{ risk.residual_risk.get_impact_display }}</span> <span class="chip-label">{{ risk.residual_risk.get_impact_display }}</span>
</button> </button>
</div> </div>
<!-- Stufe --> <!-- Level -->
<div class="column is-full-mobile is-full-tablet is-half-desktop has-text-centered"> <div class="column is-half">
<button class="risk-chip {{ risk.residual_risk.level|level_class }}" type="button" aria-label="{% trans 'Level' %}"> <button class="risk-chip {{ risk.residual_risk.level|level_class }}" type="button">
<span class="chip-head">{% trans "Level" %}</span> <span class="chip-head">{% trans "Level" %}</span>
<span class="chip-id">{{ risk.residual_risk.level|level_id }}</span> <span class="chip-id">{{ risk.residual_risk.level|level_id }}</span>
<span class="chip-label">{{ risk.residual_risk.level }}</span> <span class="chip-label">{{ risk.residual_risk.level }}</span>
@ -162,153 +142,124 @@
</div> </div>
<!-- Score --> <!-- Score -->
<div class="column is-full-mobile is-full-tablet is-half-desktop has-text-centered" aria-label="{% trans 'Score' %}"> <div class="column is-half">
<button class="risk-chip {{ risk.residual_risk.score|score_class }}" type="button"> <button class="risk-chip {{ risk.residual_risk.score|score_class }}" type="button">
<span class="chip-head">{% trans "Score" %}</span> <span class="chip-head">{% trans "Score" %}</span>
<span class="chip-id">{{ risk.residual_risk.score }}</span> <span class="chip-id">{{ risk.residual_risk.score }}</span>
<span class="chip-label">(max. 20)</span> <span class="chip-label">Max. 20</span>
</button> </button>
</div> </div>
{% else %} {% else %}
<p class="has-text-grey">{% trans "No net risk recorded yet." %}</p> <p class="has-text-grey">{% trans "No net risk recorded yet." %}</p>
{% endif %} {% endif %}
</div><br>
{% if request.user.is_staff or risk.owner_id == request.user.id %}
<form method="post" action="{% url 'risks:update_residual_review' risk.id %}">
{% csrf_token %}
<label class="checkbox">
<input type="checkbox" name="review_required" value="on" {% if risk.residual_risk and risk.residual_risk.review_required %}checked{% endif %}>
{% trans "Review required" %}
</label>
<button class="button is-small is-link" style="margin-left:.5rem;">
{% trans "Save" %}
</button>
</form>
{% endif %}
</div> </div>
</div> <!-- Ende Nettorisiko --> </div>
</div><!-- Net risk End -->
</div> <!-- Ende Risikobewertung --> </div><!-- Risk assessment End -->
</div> <!-- Ende Inhalt Überblick --> </div>
</div> <!-- Ende Überblick --> </div>
</div><!-- Overview Tab End -->
<!-- Maßnahmen --> <!-- Tab: Measures -->
<div class="card"> <div class="tab-panel is-hidden" data-tab="measures">
<header class="card-header"> <div class="table-container">
<p class="card-header-title">{% trans "Measures" %}</p> <table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth">
</header>
<div class="card-content">
{% if risk.controls.exists %}
<table class="table is-striped is-hoverable is-fullwidth">
<thead> <thead>
<tr> <tr class="has-background-prosoft">
<th>{% trans "Title" %}</th> <th class="has-text-centered">{% trans "Title" %}</th>
<th>{% trans "Status" %}</th> <th class="has-text-centered">{% trans "Status" %}</th>
<th>{% trans "Deadline" %}</th> <th class="has-text-centered">{% trans "Deadline" %}</th>
<th>{% trans "Responsible" %}</th> <th class="has-text-centered">{% trans "Responsible" %}</th>
<th>{% trans "Link" %}</th> <th class="has-text-centered">{% trans "Link" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for control in risk.controls.all %} {% for control in risk.controls.all %}
<tr onclick="window.location.href='/risks/controls/{{ control.id }}';" style="cursor:pointer;"> <tr onclick="window.location.href='/risks/controls/{{ control.id }}';" style="cursor:pointer;">
<td>{{ control.title }}</td> <td>{{ control.title }}</td>
<td>{{ control.get_status_display }}</td> <td class="has-text-centered">{{ control.get_status_display }}</td>
<td> <td class="has-text-centered">{{ control.due_date|date:"d.m.Y"|default:"" }}</td>
{% if control.due_date %} <td class="has-text-centered">{{ control.responsible.get_full_name|default:control.responsible.username|default:"" }}</td>
{{ control.due_date|date:"d.m.Y" }} <td class="has-text-centered">{% if control.wiki_link %}<a href="{{ control.wiki_link }}" target="_blank">🔗</a>{% else %}{% endif %}</td>
{% else %}
{% endif %}
</td>
<td>
{% if control.responsible %}
{{ control.responsible.get_full_name|default:control.responsible.username }}
{% else %}
{% endif %}
</td>
<td>
{% if control.wiki_link %}
<a href="{{ control.wiki_link }}" target="_blank">🔗</a>
{% else %}
{% endif %}
</td>
</tr> </tr>
{% empty %}
<tr><td colspan="5" class="has-text-grey has-text-centered">{% trans "No measures recorded." %}</td></tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% else %}
<p class="has-text-grey">{% trans "No measures recorded." %}</p>
{% endif %}
</div> </div>
</div> </div><!-- Measures Tab End -->
<!-- Ende Maßnahmen -->
<!-- Vorfälle --> <!-- Tab: Incidents -->
<div class="card"> <div class="tab-panel is-hidden" data-tab="incidents">
<header class="card-header"> <div class="table-container">
<p class="card-header-title">{% trans "Incidents" %}</p> <table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth">
</header>
<div class="card-content">
{% if risk.incidents.exists %}
<table class="table is-striped is-fullwidth">
<thead> <thead>
<tr> <tr class="has-background-prosoft">
<th>{% trans "Incident" %}</th> <th class="has-text-centered">{% trans "Incident" %}</th>
<th>{% trans "Status" %}</th> <th class="has-text-centered">{% trans "Status" %}</th>
<th>{% trans "Reported on" %}</th> <th class="has-text-centered">{% trans "Reported on" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for i in risk.incidents.all %} {% for i in risk.incidents.all %}
<tr onclick="window.location.href='/risks/incidents/{{ i.id }}';" style="cursor:pointer;"> <tr onclick="window.location.href='/risks/incidents/{{ i.id }}';" style="cursor:pointer;">
<td>{{ i.title }}</td> <td>{{ i.title }}</td>
<td>{{ i.status }}</td> <td class="has-text-centered">{{ i.get_status_display }}</td>
<td>{{ i.created_at|date:"d.m.Y H:i" }}</td> <td class="has-text-centered">{{ i.created_at|date:"d.m.Y H:i" }}</td>
</tr> </tr>
{% empty %}
<tr><td colspan="3" class="has-text-grey has-text-centered">{% trans "No incidents recorded." %}</td></tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% else %}
<p class="has-text-grey">{% trans "No incidents recorded." %}</p>
{% endif %}
</div> </div>
</div> <!-- Ende Vorfälle --> </div><!-- Incidents Tab End -->
<!-- Historie --> <!-- Tab: History -->
<div class="card"> <div class="tab-panel is-hidden" data-tab="history">
<header class="card-header"> <div class="table-container">
<p class="card-header-title">{% trans "History" %}</p> <table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth">
</header>
<div class="card-content">
{% if logs %}
<table class="table is-striped is-fullwidth">
<thead> <thead>
<tr> <tr class="has-background-prosoft">
<th>{% trans "Time" %}</th> <th class="has-text-centered">{% trans "Time" %}</th>
<th>{% trans "User" %}</th> <th class="has-text-centered">{% trans "User" %}</th>
<th>{% trans "Action" %}</th> <th class="has-text-centered">{% trans "Action" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for log in logs %} {% for log in logs %}
<tr> <tr>
<td>{{ log.action_time|date:"d.m.Y H:i" }}</td> <td class="has-text-centered">{{ log.action_time|date:"d.m.Y H:i" }}</td>
<td>{{ log.user.get_full_name|default:log.user.username }}</td> <td class="has-text-centered">{{ log.user.get_full_name|default:log.user.username }}</td>
<td>{{ log.get_change_message }}</td> <td>{{ log.get_change_message }}</td>
</tr> </tr>
{% empty %}
<tr><td colspan="3" class="has-text-grey has-text-centered">{% trans "No history found." %}</td></tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% else %}
<p class="has-text-grey">{% trans "No History found." %}</p>
{% endif %}
</div> </div>
</div> <!-- Ende Historie --> </div><!-- History Tab End -->
<br><br> <!-- Tab switching script -->
<script>
</div> document.addEventListener('DOMContentLoaded', () => {
const tabs = document.querySelectorAll('.erp-tabs a[data-tab]');
const panels = document.querySelectorAll('.tab-panel');
tabs.forEach(tab => {
tab.addEventListener('click', e => {
e.preventDefault();
tabs.forEach(x => x.classList.remove('is-active'));
panels.forEach(p => p.classList.add('is-hidden'));
tab.classList.add('is-active');
const target = tab.getAttribute('data-tab');
const activePanel = document.querySelector(`.tab-panel[data-tab="${target}"]`);
if (activePanel) activePanel.classList.remove('is-hidden');
});
});
});
</script>
{% endblock %} {% endblock %}