clupilot/app/resources/views/livewire/pages/team/index.blade.php

142 lines
8.1 KiB
PHP

<?php
use App\Models\Invitation;
use App\Models\TeamMember;
use Livewire\Attributes\Layout;
use Livewire\Volt\Component;
new #[Layout('layouts.app')] class extends Component {
public function with(): array
{
$members = TeamMember::query()->with('user')->orderBy('role')->get();
$invitations = Invitation::query()
->with('invitedBy')
->whereNull('accepted_at')
->orderByDesc('sent_at')
->get();
return [
'members' => $members,
'invitations' => $invitations,
'totalMembers' => $members->count(),
'adminCount' => $members->whereIn('role', ['owner', 'admin'])->count(),
'pendingInvites' => $invitations->count(),
'twoFaCoverage' => $members->count() ? (int) round($members->where('two_fa_enabled')->count() / $members->count() * 100) : 0,
];
}
}; ?>
<div>
<div class="clu-topbar">
<x-clu.burger />
<div>
<h1 class="clu-page-title">Team</h1>
<div class="clu-page-sub">{{ $totalMembers }} mitglieder · {{ $pendingInvites }} einladungen offen</div>
</div>
<span class="clu-status-chip"><span class="clu-pulse-dot"></span> {{ $twoFaCoverage }}% mit 2FA</span>
<div class="spacer"></div>
<button class="clu-btn-primary" style="height:36px;padding:0 14px;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 5v14M5 12h14"/></svg>
Einladen
</button>
</div>
<div class="grid gap-[14px] mb-[18px]" style="grid-template-columns:repeat(auto-fit,minmax(220px,1fr));">
<div class="clu-metric">
<div class="clu-metric-row"><span class="clu-metric-label">Mitglieder</span>
<span class="clu-metric-ico"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="8" r="3.5"/><path d="M3 20v-1c0-3 3-5 6-5s6 2 6 5v1"/></svg></span>
</div>
<div class="clu-metric-value">{{ $totalMembers }}</div>
<div class="clu-metric-delta">aktiv</div>
</div>
<div class="clu-metric">
<div class="clu-metric-row"><span class="clu-metric-label">Admins</span>
<span class="clu-metric-ico"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l8 4v5c0 5-3.5 8-8 9-4.5-1-8-4-8-9V7l8-4z"/></svg></span>
</div>
<div class="clu-metric-value">{{ $adminCount }}</div>
<div class="clu-metric-delta">owner + admin</div>
</div>
<div class="clu-metric warning">
<div class="clu-metric-row"><span class="clu-metric-label">Einladungen offen</span>
<span class="clu-metric-ico"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 5h18v14H3z"/><path d="M3 5l9 9 9-9"/></svg></span>
</div>
<div class="clu-metric-value">{{ $pendingInvites }}</div>
<div class="clu-metric-delta warn">warten auf Annahme</div>
</div>
<div class="clu-metric success">
<div class="clu-metric-row"><span class="clu-metric-label">2FA Coverage</span>
<span class="clu-metric-ico"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M9 12l2 2 4-4"/></svg></span>
</div>
<div class="clu-metric-value">{{ $twoFaCoverage }}<span class="unit">%</span></div>
<div class="clu-metric-delta">aktiviert</div>
</div>
</div>
<div class="clu-card mb-[18px]">
<div class="clu-card-head"><h3>Mitglieder</h3></div>
<div class="clu-card-body tight" style="overflow-x:auto;">
<table class="clu-table">
<thead>
<tr><th>Name</th><th>Rolle</th><th>Scope</th><th>2FA</th><th>Letzte Aktivität</th><th></th></tr>
</thead>
<tbody>
@forelse ($members as $m)
<tr>
<td>
<div class="clu-site-cell">
<div class="clu-avatar" style="background:linear-gradient(135deg,#5a8bff,#8a63ff);width:28px;height:28px;border-radius:8px;font-size:11px;">{{ mb_strtoupper(mb_substr($m->user->name ?? '?', 0, 2)) }}</div>
<div class="clu-site-meta">
<span class="clu-site-domain">{{ $m->user->name ?? '—' }}</span>
<span class="clu-site-server">{{ $m->user->email ?? '—' }}</span>
</div>
</div>
</td>
<td><span class="clu-pill @if($m->role === 'owner') accent @elseif($m->role === 'admin') warning @else muted @endif">{{ strtoupper($m->role) }}</span></td>
<td class="font-mono text-[11.5px]">{{ $m->scope ?? '—' }}</td>
<td>
@if ($m->two_fa_enabled)
<span class="clu-health">aktiv</span>
@else
<span class="clu-health warn">fehlt</span>
@endif
</td>
<td class="font-mono">{{ $m->last_active_at?->diffForHumans() ?? '—' }}</td>
<td><div class="clu-row-actions">
<button title="Bearbeiten" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></svg></button>
</div></td>
</tr>
@empty
<tr><td colspan="6" style="text-align:center;padding:40px;color:var(--color-muted);">Noch keine Mitglieder.</td></tr>
@endforelse
</tbody>
</table>
</div>
</div>
<div class="clu-card">
<div class="clu-card-head"><h3>Offene Einladungen</h3><span class="meta">{{ $pendingInvites }} pending</span></div>
<div class="clu-card-body tight" style="overflow-x:auto;">
<table class="clu-table">
<thead><tr><th>Email</th><th>Rolle</th><th>Eingeladen von</th><th>Gesendet</th><th>Läuft ab</th><th></th></tr></thead>
<tbody>
@forelse ($invitations as $i)
<tr>
<td class="font-mono">{{ $i->email }}</td>
<td><span class="clu-pill muted">{{ strtoupper($i->role) }}</span></td>
<td>{{ $i->invitedBy->name ?? '—' }}</td>
<td class="font-mono">{{ $i->sent_at?->diffForHumans() ?? '—' }}</td>
<td class="font-mono">{{ $i->expires_at?->diffForHumans() ?? '—' }}</td>
<td><div class="clu-row-actions">
<button title="Erneut senden" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 9-9"/><path d="M3 4v5h5"/></svg></button>
<button title="Zurückziehen" type="button"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M15 9l-6 6M9 9l6 6"/></svg></button>
</div></td>
</tr>
@empty
<tr><td colspan="6" style="text-align:center;padding:40px;color:var(--color-muted);">Keine offenen Einladungen.</td></tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>