summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul-Christian Volkmer2026-03-06 16:48:26 +0100
committerPaul-Christian Volkmer2026-03-06 16:48:26 +0100
commitb7b7fa3da199f2f910c27084a075f91063e46381 (patch)
tree4d63eb7eb830b1aea5e9b90f2a5c76d90d75a74d
parentbf6bfa904e127f51b79cfafb96e1280b50e9615a (diff)
feat: improve request list
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt6
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsRestController.kt3
-rw-r--r--src/main/resources/templates/fragments.html4
-rw-r--r--src/main/resources/templates/index.html93
-rw-r--r--src/web/charts.js2
-rw-r--r--src/web/style.css89
6 files changed, 146 insertions, 51 deletions
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt b/src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt
index ed0f264..9262c29 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt
@@ -45,7 +45,7 @@ class HomeController(
) {
@GetMapping
fun index(
- @PageableDefault(page = 0, size = 20, sort = ["processedAt"], direction = Sort.Direction.DESC)
+ @PageableDefault(page = 0, size = 10, sort = ["processedAt"], direction = Sort.Direction.DESC)
pageable: Pageable,
model: Model,
): String {
@@ -58,14 +58,14 @@ class HomeController(
@GetMapping(path = ["patient/{patientPseudonym}"])
fun byPatient(
@PathVariable patientPseudonym: PatientPseudonym,
- @PageableDefault(page = 0, size = 20, sort = ["processedAt"], direction = Sort.Direction.DESC)
+ @PageableDefault(page = 0, size = 10, sort = ["processedAt"], direction = Sort.Direction.DESC)
pageable: Pageable,
model: Model,
): String {
val requests = requestService.findRequestByPatientId(patientPseudonym, pageable)
model.addAttribute("patientPseudonym", patientPseudonym.value)
model.addAttribute("requests", requests)
-
+ model.addAttribute("postInitialSubmissionBlock", appConfigProperties.postInitialSubmissionBlock)
return "index"
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsRestController.kt b/src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsRestController.kt
index 27cc3dc..9d3f824 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsRestController.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/web/StatisticsRestController.kt
@@ -51,7 +51,8 @@ class StatisticsRestController(
RequestStatus.ERROR -> "#FF0000"
RequestStatus.WARNING -> "#FF8C00"
RequestStatus.SUCCESS -> "#008000"
- RequestStatus.NO_CONSENT -> "#004A9D"
+ RequestStatus.NO_CONSENT,
+ RequestStatus.BLOCKED_INITIAL -> "#004A9D"
else -> "#708090"
}
diff --git a/src/main/resources/templates/fragments.html b/src/main/resources/templates/fragments.html
index 7a8469b..403ad09 100644
--- a/src/main/resources/templates/fragments.html
+++ b/src/main/resources/templates/fragments.html
@@ -37,10 +37,10 @@
</div>
<th:block th:fragment="accept-initial" sec:authorize="hasRole('USER') or hasRole('ADMIN')">
<button class="btn btn-green" hx-swap="outerHTML" th:hx-delete="@{/submission/{requestId}/accepted(requestId=${request.uuid})}" th:if="${ request.submissionType.value == 'initial' and (request.status.value == 'success' or request.status.value == 'warning') and request.submissionAccepted == true}" title="Doch keine Meldebestätigung - jetzt blockieren">
- 🔓
+ 🔓 Initialmeldung
</button>
<button class="btn btn-yellow" hx-swap="outerHTML" th:hx-put="@{/submission/{requestId}/accepted(requestId=${request.uuid})}" th:if="${ request.submissionType.value == 'initial' and (request.status.value == 'success' or request.status.value == 'warning') and request.submissionAccepted == false}" title="Meldebestätigung vorhanden - jetzt nicht weiter blockieren">
- 🔒
+ 🔒 Initialmeldung
</button>
</th:block>
<footer th:fragment="footer">
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
index 16354c6..db85196 100644
--- a/src/main/resources/templates/index.html
+++ b/src/main/resources/templates/index.html
@@ -28,6 +28,55 @@
</div>
<div class="border" th:if="${requests.totalElements > 0}">
+ <div class="paged">
+ <div class="card" th:each="request : ${requests}">
+ <div th:if="${request.status.value.contains('success')}" class="card-header bg-green">Erfolgreiche Übertragung</div>
+ <div th:if="${request.status.value.contains('warning')}" class="card-header bg-yellow">Übertragung mit Warnungen</div>
+ <div th:if="${request.status.value.contains('error')}" class="card-header bg-red">Übertragung mit Fehlern</div>
+ <div th:if="${request.status.value == 'unknown' and not request.isPendingUnknown()}" class="card-header bg-gray">Unbekannter Status</div>
+ <div th:if="${request.status.value == 'unknown' and request.isPendingUnknown()}" class="card-header bg-gray">⏰ Unbekannter Status ⏰</div>
+ <div th:if="${request.status.value == 'duplication'}" class="card-header bg-gray">Gestoppt: Duplikation</div>
+ <div th:if="${request.status.value == 'no-consent'}" class="card-header bg-blue">Gestoppt: Kein Consent</div>
+ <div th:if="${request.status.value == 'blocked-initial'}" class="card-header bg-blue">Gestoppt: Noch keine Meldebestätigung für vorhergehende Meldung</div>
+ <div class="card-sub-header" th:classappend="${request.type.value == 'delete' ? 'delete' : ''}">
+ <div th:if="${request.type.value != 'delete'}">
+ <span th:if="${request.submissionType.value == 'initial'}"><span>📨 Übertragung vom </span><time th:datetime="${request.processedAt}">[[ ${request.processedAt} ]]</time></span>
+ <span th:if="${request.submissionType.value == 'addition'}"><span>🔄 Übertragung vom </span><time th:datetime="${request.processedAt}">[[ ${request.processedAt} ]]</time></span>
+ <span th:if="${request.submissionType.value == 'unknown'}"><span>❓ Übertragung vom </span><time th:datetime="${request.processedAt}">[[ ${request.processedAt} ]]</time></span>
+ </div>
+ <div th:if="${request.type.value == 'delete'}">
+ <span>🗑 Löschanfrage vom </span><time th:datetime="${request.processedAt}">[[ ${request.processedAt} ]]</time>
+ </div>
+ <div th:insert="~{fragments :: accept-initial}" sec:authorize="hasRole('USER') or hasRole('ADMIN')" th:if="${postInitialSubmissionBlock}"></div>
+ </div>
+ <div class="card-content">
+ <div>Request-ID</div>
+ <div th:if="not ${request.report}"><span>[[ ${request.uuid} ]]</span></div>
+ <div th:if="${request.report}">
+ <a th:href="@{/report/{id}(id=${request.uuid})}" sec:authorize="hasRole('USER') or hasRole('ADMIN')">[[ ${request.uuid} ]]</a>
+ <th:block sec:authorize="not (hasRole('USER') or hasRole('ADMIN'))">[[ ${request.uuid} ]]</th:block>
+ </div>
+ <div>Typ</div>
+ <div th:style="${request.type.value == 'delete'} ? 'color: red;'">
+ <span>
+ [[ ${request.type} ]]
+ <th:block th:if="${request.submissionType.value != 'unknown'}">([[ ${request.submissionType} ]])</th:block>
+ </span>
+ </div>
+ <div sec:authorize="hasRole('USER') or hasRole('ADMIN')">Patienten-Pseudonym</div>
+ <div class="patient-id" th:if="${patientPseudonym != null}" sec:authorize="hasRole('USER') or hasRole('ADMIN')">
+ [[ ${request.patientPseudonym} ]]
+ </div>
+ <div class="patient-id" th:if="${patientPseudonym == null}" sec:authorize="hasRole('USER') or hasRole('ADMIN')">
+ <a th:href="@{/patient/{pid}(pid=${request.patientPseudonym})}">[[ ${request.patientPseudonym} ]]</a>
+ </div>
+ <div sec:authorize="hasRole('USER') or hasRole('ADMIN')">TAN</div>
+ <div class="patient-id" sec:authorize="hasRole('USER') or hasRole('ADMIN')">
+ [[ ${request.tan} ]]
+ </div>
+ </div>
+ </div>
+ </div>
<div th:if="${patientPseudonym == null}" class="page-control">
<a id="first-page-link" th:href="@{/(page=${0})}" title="Zum Anfang: Taste W" th:if="${not requests.isFirst()}">&larrb;</a><a th:if="${requests.isFirst()}">&larrb;</a>
<a id="prev-page-link" th:href="@{/(page=${requests.getNumber() - 1})}" title="Seite zurück: Taste A" th:if="${not requests.isFirst()}">&larr;</a><a th:if="${requests.isFirst()}">&larr;</a>
@@ -42,50 +91,6 @@
<a id="next-page-link" th:href="@{/patient/{patientPseudonym}(patientPseudonym=${patientPseudonym},page=${requests.getNumber() + 1})}" title="Seite vor: Taste D" th:if="${not requests.isLast()}">&rarr;</a><a th:if="${requests.isLast()}">&rarr;</a>
<a id="last-page-link" th:href="@{/patient/{patientPseudonym}(patientPseudonym=${patientPseudonym},page=${requests.getTotalPages() - 1})}" title="Zum Ende: Taste S" th:if="${not requests.isLast()}">&rarrb;</a><a th:if="${requests.isLast()}">&rarrb;</a>
</div>
- <table class="paged">
- <thead>
- <tr>
- <th>Status</th>
- <th>Typ</th>
- <th sec:authorize="hasRole('USER') or hasRole('ADMIN')" th:if="${postInitialSubmissionBlock}">Aktion</th>
- <th>ID</th>
- <th>Datum</th>
- <th>Patienten-ID</th>
- </tr>
- </thead>
- <tbody>
- <tr th:each="request : ${requests}">
- <td th:if="${request.status.value.contains('success')}" class="bg-green"><small>[[ ${request.status} ]]</small></td>
- <td th:if="${request.status.value.contains('warning')}" class="bg-yellow"><small>[[ ${request.status} ]]</small></td>
- <td th:if="${request.status.value.contains('error')}" class="bg-red"><small>[[ ${request.status} ]]</small></td>
- <td th:if="${request.status.value == 'unknown' and not request.isPendingUnknown()}" class="bg-gray"><small>[[ ${request.status} ]]</small></td>
- <td th:if="${request.status.value == 'unknown' and request.isPendingUnknown()}" class="bg-yellow"><small>⏰ [[ ${request.status} ]] ⏰</small></td>
- <td th:if="${request.status.value == 'duplication'}" class="bg-gray"><small>[[ ${request.status} ]]</small></td>
- <td th:if="${request.status.value == 'no-consent'}" class="bg-blue"><small>[[ ${request.status} ]]</small></td>
- <td th:if="${request.status.value == 'blocked-initial'}" class="bg-gray"><small>[[ ${request.status} ]]</small></td>
- <td th:style="${request.type.value == 'delete'} ? 'color: red;'">
- <small>
- [[ ${request.type} ]]
- <th:block th:if="${request.submissionType.value != 'unknown'}">([[ ${request.submissionType} ]])</th:block>
- </small>
- </td>
- <td th:insert="~{fragments :: accept-initial}" sec:authorize="hasRole('USER') or hasRole('ADMIN')" th:if="${postInitialSubmissionBlock}"></td>
- <td th:if="not ${request.report}">[[ ${request.uuid} ]]</td>
- <td th:if="${request.report}">
- <a th:href="@{/report/{id}(id=${request.uuid})}" sec:authorize="hasRole('USER') or hasRole('ADMIN')">[[ ${request.uuid} ]]</a>
- <th:block sec:authorize="not (hasRole('USER') or hasRole('ADMIN'))">[[ ${request.uuid} ]]</th:block>
- </td>
- <td><time th:datetime="${request.processedAt}">[[ ${request.processedAt} ]]</time></td>
- <td class="patient-id" th:if="${patientPseudonym != null}" sec:authorize="hasRole('USER') or hasRole('ADMIN')">
- [[ ${request.patientPseudonym} ]]
- </td>
- <td class="patient-id" th:if="${patientPseudonym == null}" sec:authorize="hasRole('USER') or hasRole('ADMIN')">
- <a th:href="@{/patient/{pid}(pid=${request.patientPseudonym})}">[[ ${request.patientPseudonym} ]]</a>
- </td>
- <td class="patient-id" sec:authorize="not (hasRole('USER') or hasRole('ADMIN'))">***</td>
- </tr>
- </tbody>
- </table>
</div>
</main>
diff --git a/src/web/charts.js b/src/web/charts.js
index 866e60d..fe9dda7 100644
--- a/src/web/charts.js
+++ b/src/web/charts.js
@@ -203,7 +203,7 @@ export function drawBarChart(url, elemId, title, data) {
'#008000',
'#004A9D',
'#708090',
- '#708090',
+ '#004A9D',
'#708090'
],
animationDuration: 250,
diff --git a/src/web/style.css b/src/web/style.css
index 3631750..f5396be 100644
--- a/src/web/style.css
+++ b/src/web/style.css
@@ -430,6 +430,95 @@ table.config-table td > button:last-of-type {
min-height: 100vh;
}
+.card {
+ margin: .5rem 0;
+ min-height: 5rem;
+ border-left: 0.5rem solid var(--bg-gray);
+ border-radius: .2rem;
+ background: white;
+ overflow: hidden;
+ box-shadow: 0 .1rem .2rem var(--table-border);
+}
+
+.card > .card-header {
+ content: "";
+ padding: 0.2rem;
+ color: white;
+ background: var(--bg-gray);
+ font-family: monospace;
+ font-size: 0.8rem;
+}
+
+.card > .card-header.bg-red {
+ background: var(--bg-red);
+}
+
+.card:has(> .card-header.bg-red) {
+ border-left: 0.5rem solid var(--bg-red);
+}
+
+.card > .card-header.bg-yellow {
+ background: var(--bg-yellow);
+}
+
+.card:has(> .card-header.bg-yellow) {
+ border-left: 0.5rem solid var(--bg-yellow);
+}
+
+.card > .card-header.bg-green {
+ background: var(--bg-green);
+}
+
+.card:has(> .card-header.bg-green) {
+ border-left: 0.5rem solid var(--bg-green);
+}
+
+.card > .card-header.bg-blue {
+ background: var(--bg-blue);
+}
+
+.card:has(> .card-header.bg-blue) {
+ border-left: 0.5rem solid var(--bg-blue);
+}
+
+.card > .card-sub-header {
+ display: flex;
+ padding: 0 .5rem;
+ border-bottom: 1px solid var(--table-border);
+ font-size: 1rem;
+ font-weight: bold;
+ background: #eeeeee;
+ align-items: center;
+ height: 3rem;
+}
+
+.card > .card-sub-header.delete {
+ background: #ffdddd;
+}
+
+.card > .card-sub-header > *:nth-child(2) {
+ margin: 0 0 0 auto;
+}
+
+.card > .card-content {
+ padding: .5rem;
+ display: grid;
+ grid-template-columns: 1fr 5fr;
+}
+
+.card > .card-content > div:nth-child(even),
+.card > .card-content > div:nth-child(even) * {
+ font-family: monospace;
+}
+
+.card > .card-content > *:nth-child(odd) {
+ font-weight: bold;
+}
+
+.card > .card-content > div {
+ padding: 0.1rem 0;
+}
+
table.samples {
max-width: 100%;
overflow-x: scroll;