Quelltext des aktuellen Standes
<?php
$topic_ancestors = $topic_ancestors ?? [];
$this->layout('layouts/main', [
'page_title' => 'Verantwortlichkeiten: ' . $page['title'],
'breadcrumbs' => [],
'full_width' => true
]);
?>
<div class="resp-fullwidth">
<div class="content-header">
<div>
<h1><?= $this->te('Verantwortlichkeiten') ?></h1>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
<li class="breadcrumb-item"><a href="/topics"><?= $this->te('Themengebiete') ?></a></li>
<?php foreach ($topic_ancestors as $ancestor): ?>
<li class="breadcrumb-item"><a href="/topics/<?= $ancestor['id'] ?>"><?= $this->e($ancestor['name']) ?></a></li>
<?php endforeach; ?>
<?php if ($topic): ?>
<li class="breadcrumb-item"><a href="/topics/<?= $topic['id'] ?>"><?= $this->e($topic['name']) ?></a></li>
<?php endif; ?>
<li class="breadcrumb-item"><a href="/pages/<?= $page['id'] ?>"><?= $this->e($page['title']) ?></a></li>
<li class="breadcrumb-item active"><?= $this->te('Verantwortlichkeiten') ?></li>
</ol>
</div>
<div class="content-header-actions">
<a href="/pages/<?= $page['id'] ?>" class="btn btn-default btn-sm"><?= $this->te('← Zurück zur Seite') ?></a>
</div>
</div>
<div class="resp-body">
<div class="resp-layout">
<!-- Hauptinhalt -->
<main class="resp-main">
<!-- Aktuelle Zuständigkeiten -->
<div class="resp-card">
<div class="resp-card-header">
<h2><?= $this->te('👥 Aktuelle Zuständigkeiten') ?></h2>
</div>
<div class="resp-card-body">
<div class="owner-grid">
<div class="owner-card">
<div class="owner-type">
<span class="owner-type-icon" aria-hidden="true">🛠</span>
<?= $this->te('Technisch verantwortlich') ?>
</div>
<?php if ($responsibilities && !empty($responsibilities['technical_owner'])): ?>
<div class="owner-info">
<div class="owner-avatar" aria-hidden="true">
<?= $this->e(strtoupper(substr($responsibilities['technical_owner_name'] ?? 'T', 0, 1))) ?>
</div>
<div class="owner-details">
<span class="owner-name"><?= $this->e($responsibilities['technical_owner_name'] ?? '') ?></span>
<?php if (!empty($responsibilities['technical_owner_email'])): ?>
<span class="owner-email"><?= $this->e($responsibilities['technical_owner_email']) ?></span>
<?php endif; ?>
</div>
</div>
<?php else: ?>
<div class="not-assigned">
<span class="not-assigned-icon" aria-hidden="true">—</span>
<?= $this->te('Nicht zugewiesen') ?>
</div>
<?php endif; ?>
</div>
<div class="owner-card">
<div class="owner-type">
<span class="owner-type-icon" aria-hidden="true">💼</span>
<?= $this->te('Fachlich verantwortlich') ?>
</div>
<?php if ($responsibilities && !empty($responsibilities['business_owner'])): ?>
<div class="owner-info">
<div class="owner-avatar" aria-hidden="true">
<?= $this->e(strtoupper(substr($responsibilities['business_owner_name'] ?? 'F', 0, 1))) ?>
</div>
<div class="owner-details">
<span class="owner-name"><?= $this->e($responsibilities['business_owner_name'] ?? '') ?></span>
<?php if (!empty($responsibilities['business_owner_email'])): ?>
<span class="owner-email"><?= $this->e($responsibilities['business_owner_email']) ?></span>
<?php endif; ?>
</div>
</div>
<?php else: ?>
<div class="not-assigned">
<span class="not-assigned-icon" aria-hidden="true">—</span>
<?= $this->te('Nicht zugewiesen') ?>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- Review-Status -->
<div class="resp-card">
<div class="resp-card-header">
<h2><?= $this->te('📅 Review-Status') ?></h2>
</div>
<div class="resp-card-body">
<dl class="review-list">
<div class="review-row">
<dt><?= $this->te('Letzte Prüfung') ?></dt>
<dd>
<?php if ($responsibilities && !empty($responsibilities['last_review_date'])): ?>
<?= date('d.m.Y', strtotime($responsibilities['last_review_date'])) ?>
<span class="text-muted">von <?= $this->e($responsibilities['last_reviewed_by_name'] ?? 'Unbekannt') ?></span>
<?php else: ?>
<span class="text-muted"><?= $this->te('Noch nie geprüft') ?></span>
<?php endif; ?>
</dd>
</div>
<div class="review-row <?= ($responsibilities && !empty($responsibilities['next_review_date']) && strtotime($responsibilities['next_review_date']) < time()) ? 'overdue' : '' ?>">
<dt><?= $this->te('Nächste Prüfung') ?></dt>
<dd>
<?php if ($responsibilities && !empty($responsibilities['next_review_date'])): ?>
<?php if (strtotime($responsibilities['next_review_date']) < time()): ?>
<span class="status-pill status-pill--danger">
<span aria-hidden="true">⚠</span> Überfällig seit <?= date('d.m.Y', strtotime($responsibilities['next_review_date'])) ?>
</span>
<?php else: ?>
<?= date('d.m.Y', strtotime($responsibilities['next_review_date'])) ?>
<?php endif; ?>
<?php else: ?>
<span class="text-muted"><?= $this->te('Nicht festgelegt') ?></span>
<?php endif; ?>
</dd>
</div>
<div class="review-row">
<dt><?= $this->te('Review-Intervall') ?></dt>
<dd>
<?php if ($responsibilities && !empty($responsibilities['review_interval_days'])): ?>
Alle <?= (int)$responsibilities['review_interval_days'] ?> Tage
<?php else: ?>
<span class="text-muted"><?= $this->te('Standard (365 Tage)') ?></span>
<?php endif; ?>
</dd>
</div>
</dl>
<?php if ($can_manage ?? false): ?>
<div class="review-action">
<form action="/pages/<?= $page['id'] ?>/review" method="POST" class="inline-form">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<button type="submit" class="btn btn-success">
<span aria-hidden="true">✅</span> <?= $this->te('Als geprüft markieren') ?>
</button>
</form>
</div>
<?php endif; ?>
</div>
</div>
<!-- Verantwortlichkeiten bearbeiten -->
<?php if ($can_manage ?? false): ?>
<div class="resp-card">
<div class="resp-card-header">
<h2><?= $this->te('✎ Verantwortlichkeiten bearbeiten') ?></h2>
</div>
<div class="resp-card-body">
<form action="/pages/<?= $page['id'] ?>/responsibilities" method="POST">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<input type="hidden" name="_method" value="PUT">
<div class="form-grid-2col">
<div class="form-group">
<label for="technical_owner" class="form-label"><?= $this->te('Technisch verantwortlich') ?></label>
<select name="technical_owner" id="technical_owner" class="form-control">
<option value=""><?= $this->te('-- Nicht zugewiesen --') ?></option>
<?php foreach ($users as $user): ?>
<option value="<?= $user['id'] ?>"
<?= ($responsibilities && ($responsibilities['technical_owner'] ?? '') == $user['id']) ? 'selected' : '' ?>>
<?= $this->e($user['display_name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label for="business_owner" class="form-label"><?= $this->te('Fachlich verantwortlich') ?></label>
<select name="business_owner" id="business_owner" class="form-control">
<option value=""><?= $this->te('-- Nicht zugewiesen --') ?></option>
<?php foreach ($users as $user): ?>
<option value="<?= $user['id'] ?>"
<?= ($responsibilities && ($responsibilities['business_owner'] ?? '') == $user['id']) ? 'selected' : '' ?>>
<?= $this->e($user['display_name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="form-grid-2col">
<div class="form-group">
<label for="review_interval_days" class="form-label"><?= $this->te('Review-Intervall (Tage)') ?></label>
<input type="number" id="review_interval_days" name="review_interval_days"
class="form-control"
value="<?= $this->e((int)($responsibilities['review_interval_days'] ?? 365)) ?>"
min="1" max="1825">
</div>
<div class="form-group">
<label for="next_review_date" class="form-label"><?= $this->te('Nächste Prüfung') ?></label>
<input type="date" id="next_review_date" name="next_review_date"
class="form-control"
value="<?= $this->e($responsibilities['next_review_date'] ?? '') ?>">
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">
<span aria-hidden="true">✓</span> <?= $this->te('Speichern') ?>
</button>
</div>
</form>
</div>
</div>
<?php endif; ?>
</main>
<!-- Sidebar -->
<aside class="resp-sidebar">
<div class="sidebar-card">
<h3><?= $this->te('🛈 Informationen') ?></h3>
<dl class="info-list">
<dt><?= $this->te('Seite') ?></dt>
<dd><?= $this->e($page['title']) ?></dd>
<?php if ($topic): ?>
<dt><?= $this->te('Themengebiet') ?></dt>
<dd><?= $this->e($topic['name']) ?></dd>
<?php endif; ?>
<dt><?= $this->te('Version') ?></dt>
<dd>v<?= $this->e($page['version_number'] ?? 1) ?></dd>
</dl>
</div>
<div class="sidebar-card">
<h3><?= $this->te('⚙ Aktionen') ?></h3>
<div class="action-list">
<a href="/pages/<?= $page['id'] ?>" class="action-link">
<span class="action-icon" aria-hidden="true">📄</span>
<?= $this->te('Zurück zur Seite') ?>
</a>
<a href="/pages/<?= $page['id'] ?>/edit" class="action-link">
<span class="action-icon" aria-hidden="true">✎</span>
<?= $this->te('Seite bearbeiten') ?>
</a>
<a href="/pages/<?= $page['id'] ?>/versions" class="action-link">
<span class="action-icon" aria-hidden="true">📅</span>
<?= $this->te('Versionshistorie') ?>
</a>
</div>
</div>
</aside>
</div>
</div>
</div>
<style>
.resp-fullwidth {
}
/* Body */
.resp-body {
padding: 2rem;
background: var(--slate-50);
min-height: calc(100vh - 300px);
}
.resp-layout {
display: grid;
grid-template-columns: 1fr 320px;
gap: 2rem;
max-width: 1400px;
margin: 0 auto;
}
/* Main */
.resp-main {
min-width: 0;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
/* Cards */
.resp-card {
background: var(--color-bg);
border-radius: 0.75rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
overflow: hidden;
}
.resp-card-header {
padding: 1rem 1.5rem;
background: var(--gray-50);
border-bottom: 1px solid var(--gray-200);
}
.resp-card-header h2 {
margin: 0;
font-size: 1rem;
font-weight: 600;
color: var(--gray-900);
}
.resp-card-body {
padding: 1.5rem;
}
/* Owner Grid */
.owner-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.25rem;
}
.owner-card {
padding: 1.25rem;
background: var(--gray-50);
border-radius: 0.5rem;
}
.owner-type {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.8rem;
font-weight: 600;
color: var(--gray-500);
text-transform: uppercase;
letter-spacing: 0.03em;
margin-bottom: 1rem;
}
.owner-type-icon {
font-size: 1rem;
}
.owner-info {
display: flex;
align-items: center;
gap: 0.75rem;
}
.owner-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, var(--blue-600), var(--blue-500));
color: var(--color-on-accent);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 1rem;
flex-shrink: 0;
}
.owner-details {
display: flex;
flex-direction: column;
gap: 0.125rem;
}
.owner-name {
font-weight: 600;
color: var(--gray-900);
font-size: 0.95rem;
}
.owner-email {
font-size: 0.8rem;
color: var(--gray-500);
}
.not-assigned {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--gray-400);
font-size: 0.9rem;
font-style: italic;
}
.not-assigned-icon {
font-size: 1.25rem;
}
/* Review List */
.review-list {
margin: 0;
}
.review-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.875rem 0;
border-bottom: 1px solid var(--gray-100);
}
.review-row:last-of-type {
border-bottom: none;
}
.review-row dt {
font-size: 0.9rem;
font-weight: 500;
color: var(--gray-700);
margin: 0;
}
.review-row dd {
margin: 0;
font-size: 0.9rem;
color: var(--gray-900);
text-align: right;
}
.text-muted {
color: var(--gray-400);
font-size: 0.85rem;
}
.review-action {
margin-top: 1.25rem;
padding-top: 1.25rem;
border-top: 1px solid var(--gray-200);
}
.inline-form {
display: inline;
}
/* Form */
.form-grid-2col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.25rem;
margin-bottom: 1.25rem;
}
.form-group {
margin-bottom: 0;
}
.form-label {
display: block;
font-weight: 500;
color: var(--gray-700);
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.form-actions {
padding-top: 0.5rem;
}
/* Sidebar */
.resp-sidebar {
display: flex;
flex-direction: column;
gap: 1rem;
}
.sidebar-card {
background: var(--color-bg);
border-radius: 0.75rem;
padding: 1.25rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.sidebar-card h3 {
margin: 0 0 1rem 0;
font-size: 0.9rem;
font-weight: 600;
color: var(--gray-700);
}
.info-list {
margin: 0;
}
.info-list dt {
font-size: 0.75rem;
color: var(--gray-500);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-top: 0.75rem;
}
.info-list dt:first-child {
margin-top: 0;
}
.info-list dd {
margin: 0.25rem 0 0 0;
font-size: 0.9rem;
color: var(--gray-900);
}
.action-list {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.action-link {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.625rem 0.75rem;
border-radius: 0.375rem;
text-decoration: none;
color: var(--gray-700);
font-size: 0.9rem;
transition: all 0.15s;
}
.action-link:hover {
background: var(--color-primary-pale);
color: var(--color-primary);
text-decoration: none;
}
.action-icon {
font-size: 1.1rem;
}
/* Responsive */
@media (max-width: 1024px) {
.resp-layout {
grid-template-columns: 1fr;
}
.resp-sidebar {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
}
@media (max-width: 768px) {
.resp-body {
padding: 1rem;
}
.owner-grid {
grid-template-columns: 1fr;
}
.form-grid-2col {
grid-template-columns: 1fr;
}
.review-row {
flex-direction: column;
align-items: flex-start;
gap: 0.25rem;
}
.review-row dd {
text-align: left;
}
.resp-sidebar {
grid-template-columns: 1fr;
}
}
</style>