Quelltext des aktuellen Standes
<?php
// ── Kosten-Zusammenfassung berechnen ────────────────────────────────────────
$totalMonthlyGross = 0.0;
$totalMonthlyNet = 0.0;
$hasTax = false;
$activeCount = 0;
$expiringCount = 0;
$expiredCount = 0;
$cancelledCount = 0;
$futureCount = 0;
foreach ($contracts as $c) {
switch ($c['status']) {
case 'active': $activeCount++; break;
case 'expiring': $expiringCount++; break;
case 'expired': $expiredCount++; break;
case 'cancelled': $cancelledCount++; break;
case 'future': $futureCount++; break;
}
if (!in_array($c['status'], ['active', 'expiring'])) continue;
if (empty($c['cost_amount']) || (float)$c['cost_amount'] <= 0) continue;
$amount = (float)$c['cost_amount'];
if (($c['cost_interval'] ?? 'monthly') === 'one_time') continue;
$monthly = match($c['cost_interval'] ?? 'monthly') {
'yearly' => $amount / 12,
'quarterly' => $amount / 3,
default => $amount,
};
$totalMonthlyGross += $monthly;
$rate = !empty($c['tax_rate']) ? (float)$c['tax_rate'] : 0.0;
if ($rate > 0) {
$hasTax = true;
$totalMonthlyNet += $monthly / (1 + $rate / 100);
} else {
$totalMonthlyNet += $monthly;
}
}
$totalYearlyGross = $totalMonthlyGross * 12;
$totalYearlyNet = $totalMonthlyNet * 12;
$this->layout('layouts/main', [
'page_title' => $company['name'] . ' – Verträge',
'current_route' => 'contracts',
'full_width' => true,
'breadcrumbs' => [],
]);
require_once dirname(__DIR__) . '/partials/_attachment_helpers.php';
?>
<div class="cc-fullwidth">
<!-- Content Header -->
<div class="content-header">
<div>
<h1><?= $this->e($company['name']) ?></h1>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
<li class="breadcrumb-item"><a href="/contracts"><?= $this->te('Verträge') ?></a></li>
<li class="breadcrumb-item active"><?= $this->e($company['name']) ?></li>
</ol>
</div>
<div class="content-header-actions">
<a href="/contacts/companies/<?= $company['id'] ?>" class="btn btn-default"><?= $this->te('Im Adressbuch') ?></a>
<?php if ($can_manage): ?>
<a href="/contracts/create?company_id=<?= $company['id'] ?>" class="btn btn-primary"><?= $this->te('Neuer Vertrag') ?></a>
<?php endif; ?>
</div>
</div>
<!-- ── Body ─────────────────────────────────────────────────────── -->
<div class="cc-body">
<!-- KPI Small Boxes -->
<?php if ($totalMonthlyGross > 0): ?>
<div class="kpi-row">
<div class="kpi-col">
<div class="small-box bg-info">
<div class="inner">
<h3><?= $this->money($totalMonthlyGross, 2) ?></h3>
<p><?= $this->te('Monatlich (brutto)') ?></p>
</div>
<div class="icon"><i class="fas fa-euro-sign"></i></div>
</div>
</div>
<div class="kpi-col">
<div class="small-box bg-primary">
<div class="inner">
<h3><?= $this->money($totalYearlyGross, 2) ?></h3>
<p><?= $this->te('Jährlich (brutto)') ?></p>
</div>
<div class="icon"><i class="fas fa-calendar-alt"></i></div>
</div>
</div>
<div class="kpi-col">
<div class="small-box bg-success">
<div class="inner">
<h3><?= $activeCount + $expiringCount ?></h3>
<p><?= $this->te('Aktive Verträge') ?></p>
</div>
<div class="icon"><i class="fas fa-file-contract"></i></div>
</div>
</div>
<?php if ($expiringCount > 0): ?>
<div class="kpi-col">
<div class="small-box bg-warning">
<div class="inner">
<h3><?= $expiringCount ?></h3>
<p><?= $this->te('Kündigung / Läuft aus') ?></p>
</div>
<div class="icon"><i class="fas fa-exclamation-triangle"></i></div>
</div>
</div>
<?php elseif ($expiredCount + $cancelledCount > 0): ?>
<div class="kpi-col">
<div class="small-box bg-secondary">
<div class="inner">
<h3><?= $expiredCount + $cancelledCount ?></h3>
<p><?= $this->te('Inaktiv / Gekündigt') ?></p>
</div>
<div class="icon"><i class="fas fa-times-circle"></i></div>
</div>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="cc-layout">
<!-- ── Hauptspalte ───────────────────────────────────────── -->
<main class="cc-main">
<!-- Verträge -->
<div class="cc-card">
<div class="cc-card-head">
<h2 class="cc-section-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
<?= $this->te('Verträge') ?>
</h2>
<span class="cc-count-badge"><?= count($contracts) ?></span>
</div>
<?php if (empty($contracts)): ?>
<div class="cc-empty-state">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="44" height="44"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
<p><?= $this->te('Keine Verträge für diese Firma vorhanden.') ?></p>
<?php if ($can_manage): ?>
<a href="/contracts/create?company_id=<?= $company['id'] ?>" class="btn btn-primary btn-sm"><?= $this->te('Ersten Vertrag erstellen') ?></a>
<?php endif; ?>
</div>
<?php else: ?>
<div class="cc-contract-list">
<?php foreach ($contracts as $c):
$amount = !empty($c['cost_amount']) ? (float)$c['cost_amount'] : null;
$monthly = null;
if ($amount !== null && ($c['cost_interval'] ?? 'monthly') !== 'one_time') {
$monthly = match($c['cost_interval'] ?? 'monthly') {
'yearly' => $amount / 12,
'quarterly' => $amount / 3,
default => $amount,
};
}
$hasCancellation = !empty($c['cancellation_notice_date'])
&& strtotime($c['cancellation_notice_date']) > time();
?>
<a href="/contracts/<?= $c['id'] ?>" class="cc-contract-row cc-contract-row--<?= $this->e($c['status']) ?>">
<div class="cc-contract-row-left">
<?php $ccPill = ['active'=>'status-pill--success','expiring'=>'status-pill--warning','expired'=>'status-pill--danger','cancelled'=>'status-pill--neutral','future'=>'status-pill--purple']; ?>
<span class="status-pill <?= $ccPill[$c['status']] ?? 'status-pill--neutral' ?>" style="flex-shrink:0;margin-top:0.1rem;">
<?php
if ($c['status'] === 'expiring' && empty($c['auto_renew']) && empty($c['cancellation_notice_date'])) {
echo 'Läuft aus';
} else {
echo match($c['status']) {
'active' => 'Aktiv',
'expiring' => 'Kündigung beachten',
'expired' => 'Abgelaufen',
'cancelled' => 'Gekündigt',
'future' => 'Zukünftig',
default => $c['status']
};
}
?>
</span>
<div class="cc-contract-info">
<span class="cc-contract-title"><?= $this->e($c['title']) ?></span>
<span class="cc-contract-sub">
<?php if (!empty($c['contract_number'])): ?>
<?= $this->te('Nr. {nummer}', ['nummer' => $c['contract_number']]) ?>
<?php if (!empty($c['start_date']) || !empty($c['end_date'])): ?> · <?php endif; ?>
<?php endif; ?>
<?php if (!empty($c['start_date'])): ?>
<?= date('d.m.Y', strtotime($c['start_date'])) ?>
<?php endif; ?>
<?php if (!empty($c['start_date']) && !empty($c['end_date'])): ?> – <?php endif; ?>
<?php if (!empty($c['end_date'])): ?>
<?= date('d.m.Y', strtotime($c['end_date'])) ?>
<?php elseif (!empty($c['start_date'])): ?>
<?= $this->te('– unbefristet') ?>
<?php endif; ?>
</span>
<?php if ($hasCancellation): ?>
<span class="cc-contract-warn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="11" height="11"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
Kündigen bis <?= date('d.m.Y', strtotime($c['cancellation_notice_date'])) ?>
</span>
<?php endif; ?>
</div>
</div>
<div class="cc-contract-row-right">
<?php if ($amount !== null && ($c['cost_interval'] ?? 'monthly') === 'one_time'): ?>
<div class="cc-cost-display">
<span class="cc-cost-monthly"><?= $this->money($amount, 2) ?><span class="cc-cost-period"> <?= $this->te('einmalig') ?></span></span>
</div>
<?php elseif ($monthly !== null): ?>
<div class="cc-cost-display">
<span class="cc-cost-monthly"><?= $this->money($monthly, 2) ?><span class="cc-cost-period"><?= $this->te('/Mo.') ?></span></span>
<span class="cc-cost-yearly"><?= $this->te('{betrag}/Jahr', ['betrag' => $this->money($monthly * 12, 2)]) ?></span>
</div>
<?php else: ?>
<span class="cc-cost-none">–</span>
<?php endif; ?>
<?php if ((int)$c['file_count'] > 0): ?>
<span class="cc-file-badge" title="<?= $this->te((int)$c['file_count'] === 1 ? '{anzahl} Dokument' : '{anzahl} Dokumente', ['anzahl' => (int)$c['file_count']]) ?>">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="12" height="12"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
<?= $c['file_count'] ?>
</span>
<?php endif; ?>
<svg class="cc-row-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><polyline points="9 18 15 12 9 6"/></svg>
</div>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<!-- Firmendokumente + Angebote untereinander -->
<!-- Firmendokumente -->
<div class="cc-card">
<div class="cc-card-head">
<h2 class="cc-section-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
<?= $this->te('Firmendokumente') ?>
</h2>
<span class="cc-count-badge"><?= count($company_files) ?></span>
</div>
<?php if ($can_manage): ?>
<form action="/contracts/company/<?= $company['id'] ?>/files" method="POST" enctype="multipart/form-data" class="cc-upload-form">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<input type="hidden" name="file_type" value="document">
<label class="cc-upload-label" for="ccFileInput">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
<span id="ccFileName"><?= $this->te('Datei auswählen…') ?></span>
</label>
<input type="file" name="file" id="ccFileInput" class="cc-upload-hidden" accept=".pdf,.doc,.docx,.xls,.xlsx,.odt,.ods,.png,.jpg,.jpeg,.gif,.webp,.tif,.tiff,.txt,.csv,.eml,.msg,.zip" required>
<button type="submit" class="btn btn-primary btn-sm"><?= $this->te('Hochladen') ?></button>
</form>
<script>
document.getElementById('ccFileInput').addEventListener('change', function () {
var el = document.getElementById('ccFileName');
el.textContent = this.files[0] ? this.files[0].name : 'Datei auswählen\u2026';
el.style.color = this.files[0] ? '#111827' : '';
el.style.fontWeight = this.files[0] ? '500' : '';
});
</script>
<?php endif; ?>
<?php if (empty($company_files)): ?>
<p class="cc-muted"><?= $this->te('Keine Firmendokumente vorhanden.') ?></p>
<?php else: ?>
<div class="cc-file-list">
<?php foreach ($company_files as $f):
$ext = strtolower(pathinfo($f['original_filename'], PATHINFO_EXTENSION));
$extColors = ['pdf'=>['#dc2626','#fee2e2'],'doc'=>['#2563eb','#dbeafe'],'docx'=>['#2563eb','#dbeafe'],'xls'=>['#16a34a','#dcfce7'],'xlsx'=>['#16a34a','#dcfce7'],'zip'=>['#d97706','#fef3c7'],'rar'=>['#d97706','#fef3c7'],'png'=>['#7c3aed','#f3e8ff'],'jpg'=>['#7c3aed','#f3e8ff'],'jpeg'=>['#7c3aed','#f3e8ff'],'gif'=>['#7c3aed','#f3e8ff']];
[$extColor, $extBg] = $extColors[$ext] ?? ['#6b7280', '#f3f4f6'];
?>
<div class="cc-file-row">
<div class="cc-file-icon" style="background:<?= $extBg ?>;color:<?= $extColor ?>">
<?= strtoupper($ext ?: '?') ?>
</div>
<div class="cc-file-info">
<span class="cc-file-name"><?= $this->e($f['original_filename']) ?></span>
<span class="cc-file-meta"><?= $this->formatFileSize((int)$f['file_size']) ?> · <?= $this->relativeTime($f['created_at']) ?></span>
</div>
<div class="cc-file-actions">
<?= att_view_link('/contracts/company-files/' . $f['id'] . '/view', $f['original_filename'], $f['mime_type'] ?? '', 'cc-icon-btn') ?>
<a href="/contracts/company-files/<?= $f['id'] ?>/download" class="cc-icon-btn" title="<?= $this->te('Herunterladen') ?>">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
</a>
<?php if ($can_manage): ?>
<form action="/contracts/company-files/<?= $f['id'] ?>" method="POST" onsubmit="return confirm('Datei löschen?');" style="display:inline">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="cc-icon-btn cc-icon-btn-danger" title="<?= $this->te('Löschen') ?>">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>
</button>
</form>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<!-- Angebote -->
<?php $companyQuotes = $company_quotes ?? []; ?>
<div class="cc-card">
<div class="cc-card-head">
<h2 class="cc-section-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
<?= $this->te('Angebote') ?>
</h2>
<span class="cc-count-badge"><?= count($companyQuotes) ?></span>
</div>
<?php if ($can_manage): ?>
<form action="/contracts/company/<?= $company['id'] ?>/files" method="POST" enctype="multipart/form-data" class="cc-upload-form">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<input type="hidden" name="file_type" value="quote">
<label class="cc-upload-label" for="ccQuoteInput">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
<span id="ccQuoteName"><?= $this->te('Datei auswählen…') ?></span>
</label>
<input type="file" name="file" id="ccQuoteInput" class="cc-upload-hidden" accept=".pdf,.doc,.docx,.xls,.xlsx,.odt,.ods,.png,.jpg,.jpeg,.gif,.webp,.tif,.tiff,.txt,.csv,.eml,.msg,.zip" required>
<button type="submit" class="btn btn-primary btn-sm"><?= $this->te('Hochladen') ?></button>
</form>
<script>
document.getElementById('ccQuoteInput').addEventListener('change', function () {
var el = document.getElementById('ccQuoteName');
el.textContent = this.files[0] ? this.files[0].name : 'Datei auswählen\u2026';
el.style.color = this.files[0] ? '#111827' : '';
el.style.fontWeight = this.files[0] ? '500' : '';
});
</script>
<?php endif; ?>
<?php if (empty($companyQuotes)): ?>
<p class="cc-muted"><?= $this->te('Keine Angebote vorhanden.') ?></p>
<?php else: ?>
<div class="cc-file-list">
<?php foreach ($companyQuotes as $f):
$ext = strtolower(pathinfo($f['original_filename'], PATHINFO_EXTENSION));
$extColors = ['pdf'=>['#dc2626','#fee2e2'],'doc'=>['#2563eb','#dbeafe'],'docx'=>['#2563eb','#dbeafe'],'xls'=>['#16a34a','#dcfce7'],'xlsx'=>['#16a34a','#dcfce7'],'zip'=>['#d97706','#fef3c7'],'rar'=>['#d97706','#fef3c7'],'png'=>['#7c3aed','#f3e8ff'],'jpg'=>['#7c3aed','#f3e8ff'],'jpeg'=>['#7c3aed','#f3e8ff'],'gif'=>['#7c3aed','#f3e8ff']];
[$extColor, $extBg] = $extColors[$ext] ?? ['#6b7280', '#f3f4f6'];
?>
<div class="cc-file-row">
<div class="cc-file-icon" style="background:<?= $extBg ?>;color:<?= $extColor ?>">
<?= strtoupper($ext ?: '?') ?>
</div>
<div class="cc-file-info">
<span class="cc-file-name"><?= $this->e($f['original_filename']) ?></span>
<span class="cc-file-meta"><?= $this->formatFileSize((int)$f['file_size']) ?> · <?= $this->relativeTime($f['created_at']) ?></span>
</div>
<div class="cc-file-actions">
<?= att_view_link('/contracts/company-files/' . $f['id'] . '/view', $f['original_filename'], $f['mime_type'] ?? '', 'cc-icon-btn') ?>
<a href="/contracts/company-files/<?= $f['id'] ?>/download" class="cc-icon-btn" title="<?= $this->te('Herunterladen') ?>">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
</a>
<?php if ($can_manage): ?>
<form action="/contracts/company-files/<?= $f['id'] ?>" method="POST" onsubmit="return confirm('Angebot löschen?');" style="display:inline">
<input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="cc-icon-btn cc-icon-btn-danger" title="<?= $this->te('Löschen') ?>">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>
</button>
</form>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</main>
<!-- ── Sidebar ────────────────────────────────────────────── -->
<aside class="cc-sidebar">
<!-- Kosten-Übersicht (nur wenn Verträge mit Kosten vorhanden) -->
<?php if ($totalMonthlyGross > 0): ?>
<div class="cc-sidebar-card cc-sidebar-card-costs">
<h3 class="cc-sidebar-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
<?= $this->te('Gesamtkosten (aktiv)') ?>
</h3>
<div class="cc-cost-tiles">
<div class="cc-cost-tile cc-cost-tile-blue">
<span class="cc-tile-label"><?= $this->te('Monatlich brutto') ?></span>
<span class="cc-tile-value"><?= $this->money($totalMonthlyGross, 2) ?></span>
</div>
<div class="cc-cost-tile cc-cost-tile-indigo">
<span class="cc-tile-label"><?= $this->te('Jährlich brutto') ?></span>
<span class="cc-tile-value"><?= $this->money($totalYearlyGross, 2) ?></span>
</div>
<?php if ($hasTax): ?>
<div class="cc-cost-tile cc-cost-tile-green">
<span class="cc-tile-label"><?= $this->te('Monatlich netto') ?></span>
<span class="cc-tile-value"><?= $this->money($totalMonthlyNet, 2) ?></span>
</div>
<div class="cc-cost-tile cc-cost-tile-teal">
<span class="cc-tile-label"><?= $this->te('Jährlich netto') ?></span>
<span class="cc-tile-value"><?= $this->money($totalYearlyNet, 2) ?></span>
</div>
<?php endif; ?>
</div>
<p class="cc-cost-note">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="11" height="11"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
<?= $this->te('Nur aktive und ablaufende Verträge') ?>
</p>
</div>
<?php endif; ?>
<!-- Firmenkontaktdaten -->
<div class="cc-sidebar-card">
<h3 class="cc-sidebar-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15"><path d="M3 21h18"/><path d="M5 21V7l8-4v18"/><path d="M19 21V11l-6-4"/></svg>
<?= $this->te('Firmendaten') ?>
</h3>
<dl class="cc-detail-list">
<?php if (!empty($company['phone'])): ?>
<dt><?= $this->te('Telefon') ?></dt>
<dd><a href="tel:<?= $this->e($company['phone']) ?>" class="cc-link"><?= $this->e($company['phone']) ?></a></dd>
<?php endif; ?>
<?php if (!empty($company['email'])): ?>
<dt><?= $this->t('E-Mail') ?></dt>
<dd><a href="mailto:<?= $this->e($company['email']) ?>" class="cc-link"><?= $this->e($company['email']) ?></a></dd>
<?php endif; ?>
<?php if (!empty($company['website'])): ?>
<dt><?= $this->te('Website') ?></dt>
<dd>
<a href="<?= $this->e($company['website']) ?>" target="_blank" rel="noopener" class="cc-link">
<?= $this->e(preg_replace('#^https?://#', '', rtrim($company['website'], '/'))) ?>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="10" height="10"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
</a>
</dd>
<?php endif; ?>
<?php if (!empty($company['address'])): ?>
<dt><?= $this->te('Adresse') ?></dt>
<dd class="cc-address"><?= nl2br($this->e($company['address'])) ?></dd>
<?php endif; ?>
<?php if (empty($company['phone']) && empty($company['email']) && empty($company['website']) && empty($company['address'])): ?>
<dd class="cc-muted" style="grid-column:1/-1"><?= $this->te('Keine Kontaktdaten hinterlegt.') ?></dd>
<?php endif; ?>
</dl>
<a href="/contacts/companies/<?= $company['id'] ?>" class="cc-sidebar-link">
<?= $this->te('Im Adressbuch öffnen') ?>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="11" height="11"><polyline points="9 18 15 12 9 6"/></svg>
</a>
</div>
<!-- Ansprechpartner -->
<?php if (!empty($contacts)): ?>
<div class="cc-sidebar-card">
<h3 class="cc-sidebar-title">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="15" height="15"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<?= $this->te('Ansprechpartner') ?>
<span class="cc-count-badge cc-count-badge-inline"><?= count($contacts) ?></span>
</h3>
<div class="cc-contact-list">
<?php foreach ($contacts as $ct):
$ctName = trim($ct['first_name'] . ' ' . $ct['last_name']);
?>
<div class="cc-contact-item">
<a href="/contacts/companies/<?= $company['id'] ?>" class="cc-contact-link-wrap">
<div class="cc-contact-avatar">
<?php if (!empty($ct['image_path'])): ?>
<img src="<?= $this->e($ct['image_path']) ?>" alt="">
<?php else: ?>
<span><?= mb_strtoupper(mb_substr($ctName, 0, 1)) ?></span>
<?php endif; ?>
</div>
<div>
<div class="cc-contact-name"><?= $this->e($ctName) ?></div>
<?php if (!empty($ct['position'])): ?>
<div class="cc-contact-role"><?= $this->e($ct['position']) ?></div>
<?php endif; ?>
</div>
</a>
<?php if (!empty($ct['phone']) || !empty($ct['mobile']) || !empty($ct['email'])): ?>
<div class="cc-contact-chips">
<?php if (!empty($ct['phone'])): ?>
<a href="tel:<?= $this->e($ct['phone']) ?>" class="cc-chip">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="11" height="11"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 12a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.18 1h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 8.15a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
<?= $this->e($ct['phone']) ?>
</a>
<?php endif; ?>
<?php if (!empty($ct['mobile'])): ?>
<a href="tel:<?= $this->e($ct['mobile']) ?>" class="cc-chip">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="11" height="11"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
<?= $this->e($ct['mobile']) ?>
</a>
<?php endif; ?>
<?php if (!empty($ct['email'])): ?>
<a href="mailto:<?= $this->e($ct['email']) ?>" class="cc-chip">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="11" height="11"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
<?= $this->e($ct['email']) ?>
</a>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</aside>
</div>
</div>
</div>
<style>
/* ── Layout ─────────────────────────────────────────────────────────────── */
/* ── Body ─────────────────────────────────────────────────────────────────── */
.cc-body { padding: 1.5rem 1rem 3rem; background: var(--slate-50); min-height: calc(100vh - 320px); }
.cc-layout { display: grid; grid-template-columns: 1fr 300px; gap: 1.75rem; }
.cc-main { display: flex; flex-direction: column; gap: 1.25rem; min-width: 0; }
/* ── Cards ────────────────────────────────────────────────────────────────── */
.cc-card { background: var(--color-bg); border-radius: 0.25rem; padding: 1.5rem; box-shadow: 0 1px 3px rgba(0,0,0,0.06); }
.cc-card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.25rem; }
.cc-section-title { margin: 0; font-size: 1rem; font-weight: 600; color: var(--gray-700); display: flex; align-items: center; gap: 0.5rem; }
.cc-section-title svg { color: var(--gray-500); flex-shrink: 0; }
.cc-count-badge {
display: inline-flex; align-items: center; justify-content: center;
min-width: 1.5rem; height: 1.5rem; padding: 0 0.375rem;
background: var(--gray-100); border-radius: 9999px; font-size: 0.72rem; font-weight: 600; color: var(--gray-500);
}
.cc-count-badge-inline { margin-left: auto; }
.cc-muted { color: var(--gray-400); font-size: 0.85rem; margin: 0; }
/* ── Empty State ─────────────────────────────────────────────────────────── */
.cc-empty-state {
text-align: center; padding: 2.5rem 1rem;
display: flex; flex-direction: column; align-items: center; gap: 0.75rem;
color: var(--gray-400);
}
.cc-empty-state p { margin: 0; font-size: 0.875rem; }
/* ── Contract List ────────────────────────────────────────────────────────── */
.cc-contract-list { display: flex; flex-direction: column; gap: 0; }
.cc-contract-row {
display: flex; justify-content: space-between; align-items: center;
padding: 0.875rem 1rem; gap: 1rem;
border-bottom: 1px solid var(--gray-100); border-radius: 0.25rem;
text-decoration: none; color: inherit; transition: background 0.12s;
}
.cc-contract-list .cc-contract-row:last-child { border-bottom: none; }
.cc-contract-row:hover { background: var(--sky-50); text-decoration: none; }
.cc-contract-row--expiring:hover { background: var(--amber-50); }
.cc-contract-row--expired, .cc-contract-row--cancelled { opacity: 0.65; }
.cc-contract-row--expired:hover, .cc-contract-row--cancelled:hover { background: var(--gray-50); opacity: 1; }
.cc-contract-row-left { display: flex; align-items: flex-start; gap: 0.75rem; min-width: 0; flex: 1; }
.cc-contract-info { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.cc-contract-title { font-size: 0.9rem; font-weight: 600; color: var(--gray-900); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cc-contract-sub { font-size: 0.75rem; color: var(--gray-400); }
.cc-contract-warn { display: inline-flex; align-items: center; gap: 0.25rem; font-size: 0.72rem; color: var(--amber-700); font-weight: 500; }
.cc-contract-warn svg { flex-shrink: 0; }
.cc-contract-row-right { display: flex; align-items: center; gap: 0.75rem; flex-shrink: 0; }
.cc-cost-display { display: flex; flex-direction: column; align-items: flex-end; gap: 0.1rem; }
.cc-cost-monthly { font-size: 0.9rem; font-weight: 700; color: var(--gray-900); }
.cc-cost-period { font-size: 0.72rem; font-weight: 400; color: var(--gray-500); }
.cc-cost-yearly { font-size: 0.7rem; color: var(--gray-400); }
.cc-cost-none { color: var(--gray-300); font-size: 0.85rem; }
.cc-file-badge {
display: inline-flex; align-items: center; gap: 0.3rem;
padding: 0.2rem 0.5rem; background: var(--gray-100); border-radius: 9999px;
font-size: 0.72rem; color: var(--gray-500);
}
.cc-row-arrow { color: var(--gray-300); transition: transform 0.15s, color 0.15s; flex-shrink: 0; }
.cc-contract-row:hover .cc-row-arrow { color: var(--sky-500); transform: translateX(2px); }
/* ── File List ────────────────────────────────────────────────────────────── */
.cc-upload-hidden { display: none; }
.cc-upload-form { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem; }
.cc-upload-label {
display: flex; align-items: center; gap: 0.5rem; flex: 1; min-width: 0;
padding: 0.375rem 0.75rem; background: var(--gray-50); border: 1px solid var(--gray-200);
border-radius: 0.375rem; cursor: pointer; font-size: 0.82rem; color: var(--gray-400);
transition: border-color 0.15s, background 0.15s;
}
.cc-upload-label svg { flex-shrink: 0; color: var(--gray-400); }
.cc-upload-label:hover { border-color: var(--sky-500); background: var(--sky-50); }
.cc-file-list { display: flex; flex-direction: column; gap: 0.5rem; }
.cc-file-row {
display: flex; align-items: center; gap: 0.75rem;
padding: 0.625rem 0.75rem; background: var(--gray-50);
border-radius: 0.25rem; border: 1px solid var(--gray-100);
}
.cc-file-icon {
flex-shrink: 0; width: 36px; height: 36px; border-radius: 0.375rem;
display: flex; align-items: center; justify-content: center;
font-size: 0.6rem; font-weight: 700; letter-spacing: 0.02em;
}
.cc-file-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.1rem; }
.cc-file-name { font-size: 0.82rem; font-weight: 500; color: var(--blue-600); text-decoration: none; word-break: break-all; }
.cc-file-name:hover { text-decoration: underline; }
.cc-file-meta { font-size: 0.7rem; color: var(--gray-400); }
.cc-file-actions { display: flex; align-items: center; gap: 0.25rem; flex-shrink: 0; }
.cc-icon-btn {
background: transparent; border: none; border-radius: 0.375rem;
padding: 0.3rem; cursor: pointer; color: var(--gray-400);
display: inline-flex; align-items: center; transition: background 0.12s, color 0.12s;
text-decoration: none;
}
.cc-icon-btn:hover { background: var(--gray-200); color: var(--gray-700); text-decoration: none; }
.cc-icon-btn-danger:hover { background: var(--red-100); color: var(--red-600); }
/* ── Sidebar ──────────────────────────────────────────────────────────────── */
.cc-sidebar { display: flex; flex-direction: column; gap: 1rem; }
.cc-sidebar-card {
background: var(--color-bg); border-radius: 0.25rem; padding: 1.25rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
}
.cc-sidebar-title {
margin: 0 0 1rem; font-size: 0.875rem; font-weight: 600; color: var(--gray-700);
display: flex; align-items: center; gap: 0.45rem;
}
.cc-sidebar-title svg { color: var(--gray-500); flex-shrink: 0; }
/* Kosten-Tiles */
.cc-sidebar-card-costs { border-top: 3px solid var(--sky-500); }
.cc-cost-tiles { display: grid; grid-template-columns: 1fr 1fr; gap: 0.625rem; margin-bottom: 0.75rem; }
.cc-cost-tile {
border-radius: 0.25rem; padding: 0.75rem 0.875rem;
display: flex; flex-direction: column; gap: 0.2rem;
}
.cc-cost-tile-blue { background: var(--blue-50); }
.cc-cost-tile-indigo { background: var(--indigo-50); }
.cc-cost-tile-green { background: var(--green-50); }
.cc-cost-tile-teal { background: var(--teal-50); }
.cc-tile-label { font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--gray-500); font-weight: 600; }
.cc-tile-value { font-size: 0.95rem; font-weight: 700; color: var(--gray-900); }
.cc-cost-note {
display: flex; align-items: center; gap: 0.35rem;
font-size: 0.72rem; color: var(--gray-400); margin: 0;
}
.cc-cost-note svg { flex-shrink: 0; }
/* Detail list */
.cc-detail-list {
margin: 0 0 1rem; display: grid;
grid-template-columns: auto 1fr; gap: 0.125rem 0.75rem;
}
.cc-detail-list dt { font-size: 0.7rem; color: var(--gray-400); text-transform: uppercase; letter-spacing: 0.04em; align-self: start; padding-top: 0.1rem; }
.cc-detail-list dd { margin: 0; font-size: 0.85rem; color: var(--gray-900); }
.cc-detail-list dt + dd { margin-top: 0.5rem; }
.cc-detail-list dt:first-child, .cc-detail-list dt:first-child + dd { margin-top: 0; }
.cc-address { white-space: pre-line; line-height: 1.5; }
.cc-link { color: var(--blue-600); text-decoration: none; }
.cc-link:hover { text-decoration: underline; }
.cc-sidebar-link {
display: inline-flex; align-items: center; gap: 0.3rem;
font-size: 0.8rem; color: var(--sky-500); text-decoration: none; font-weight: 500;
}
.cc-sidebar-link:hover { text-decoration: underline; }
/* Contacts */
.cc-contact-list { display: flex; flex-direction: column; gap: 0.875rem; }
.cc-contact-item { }
.cc-contact-link-wrap {
display: flex; align-items: center; gap: 0.625rem;
text-decoration: none; color: inherit; padding: 0.375rem;
border-radius: 0.25rem; transition: background 0.12s; margin: 0 -0.375rem 0.25rem;
}
.cc-contact-link-wrap:hover { background: var(--gray-100); text-decoration: none; }
.cc-contact-avatar {
width: 36px; height: 36px; border-radius: 50%; flex-shrink: 0;
background: linear-gradient(135deg, var(--sky-500), var(--color-primary-strong));
display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.cc-contact-avatar img { width: 100%; height: 100%; object-fit: cover; }
.cc-contact-avatar span { font-size: 0.95rem; font-weight: 700; color: var(--color-on-accent); }
.cc-contact-name { font-size: 0.875rem; font-weight: 600; color: var(--gray-900); }
.cc-contact-role { font-size: 0.75rem; color: var(--gray-500); }
.cc-contact-chips { display: flex; flex-wrap: wrap; gap: 0.35rem; padding-left: 0; }
.cc-chip {
display: inline-flex; align-items: center; gap: 0.3rem;
padding: 0.25rem 0.625rem; background: var(--gray-100); border: 1px solid var(--gray-200);
border-radius: 9999px; font-size: 0.72rem; font-weight: 500; color: var(--gray-700);
text-decoration: none; transition: all 0.12s; white-space: nowrap;
}
.cc-chip:hover { background: var(--sky-100); border-color: var(--sky-200); color: var(--color-primary-strong); transform: translateY(-1px); text-decoration: none; }
.cc-chip svg { flex-shrink: 0; color: var(--gray-500); }
/* ── Responsive ─────────────────────────────────────────────────────────── */
@media (max-width: 1100px) {
.cc-layout { grid-template-columns: 1fr; }
.cc-sidebar { order: -1; display: grid; grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) {
.cc-body { padding: 1rem; }
.cc-layout { gap: 1rem; }
.cc-sidebar { grid-template-columns: 1fr; }
.cc-card { padding: 1rem; }
.cc-cost-tiles { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 480px) {
.cc-body { padding: 0.75rem; }
.cc-card { padding: 0.875rem; }
.cc-contract-row { flex-direction: column; align-items: flex-start; gap: 0.5rem; }
.cc-contract-row-right { width: 100%; justify-content: space-between; }
.cc-cost-tiles { grid-template-columns: 1fr; }
}
</style>