feat: teams redesign — cards, filter, user autocomplete

- Teams index: cards with animation, search filter
- Team detail: user autocomplete via AJAX (search on type)
- CSS: card grid styles, autocomplete dropdown
- Backend: searchUsers endpoint, filter support
This commit is contained in:
2026-06-09 18:11:20 -04:00
parent b1fc0e2cfe
commit 13f14416a4
5 changed files with 419 additions and 75 deletions

View File

@@ -1,4 +1,4 @@
<?php $teams = $teams ?? []; ?>
<?php $teams = $teams ?? []; $search = $search ?? ''; ?>
<div class="page-header">
<div class="page-header-left">
@@ -12,62 +12,79 @@
</div>
</div>
<div class="card">
<div class="card-body">
<?php if (empty($teams)): ?>
<div class="empty-state">
<i class="fas fa-users-cog"></i>
<p>No teams created yet.</p>
<a href="/teams/create" class="btn btn-primary">Create Your First Team</a>
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Members</th>
<th>Servers</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($teams as $team): ?>
<tr>
<td>
<a href="/teams/<?= $team['id'] ?>" class="fw-semibold">
<?= htmlspecialchars($team['name']) ?>
</a>
</td>
<td class="text-muted"><?= htmlspecialchars(mb_substr($team['description'] ?? '', 0, 80)) ?></td>
<td><span class="badge badge-info"><?= (int) ($team['member_count'] ?? 0) ?></span></td>
<td><span class="badge badge-primary"><?= (int) ($team['server_count'] ?? 0) ?></span></td>
<td>
<div class="btn-group">
<a href="/teams/<?= $team['id'] ?>" class="btn btn-xs" title="Manage">
<i class="fas fa-cog"></i>
</a>
<button class="btn btn-xs btn-danger" title="Delete"
onclick="deleteTeam(<?= $team['id'] ?>, '<?= htmlspecialchars(addslashes($team['name'])) ?>')">
<i class="fas fa-trash"></i>
</button>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="teams-toolbar">
<div class="teams-search">
<i class="fas fa-search teams-search-icon"></i>
<input type="text" id="teamSearch" class="form-input teams-search-input"
placeholder="Search teams..." value="<?= htmlspecialchars($search) ?>">
<?php if ($search !== ''): ?>
<button class="teams-search-clear" onclick="clearSearch()">&times;</button>
<?php endif; ?>
</div>
</div>
<?php if (empty($teams)): ?>
<div class="empty-state">
<i class="fas fa-users-cog"></i>
<p><?= $search !== '' ? 'No teams match your search.' : 'No teams created yet.' ?></p>
<?php if ($search !== ''): ?>
<a href="/teams" class="btn btn-secondary">Clear Search</a>
<?php else: ?>
<a href="/teams/create" class="btn btn-primary">Create Your First Team</a>
<?php endif; ?>
</div>
<?php else: ?>
<div class="teams-grid" id="teamsGrid">
<?php foreach ($teams as $team): ?>
<div class="team-card">
<div class="team-card-header">
<div class="team-card-icon">
<i class="fas fa-users"></i>
</div>
<h3 class="team-card-title">
<a href="/teams/<?= $team['id'] ?>"><?= htmlspecialchars($team['name']) ?></a>
</h3>
</div>
<p class="team-card-desc">
<?= htmlspecialchars(mb_substr($team['description'] ?? 'No description', 0, 100)) ?>
</p>
<div class="team-card-stats">
<span class="team-stat">
<i class="fas fa-user"></i> <?= (int) ($team['member_count'] ?? 0) ?> members
</span>
<span class="team-stat">
<i class="fas fa-server"></i> <?= (int) ($team['server_count'] ?? 0) ?> servers
</span>
</div>
<div class="team-card-actions">
<a href="/teams/<?= $team['id'] ?>" class="btn btn-sm btn-primary">
<i class="fas fa-cog"></i> Manage
</a>
<button class="btn btn-sm btn-danger" title="Delete"
onclick="deleteTeam(<?= $team['id'] ?>, '<?= htmlspecialchars(addslashes($team['name'])) ?>')">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<form id="deleteForm" method="POST" style="display:none">
<input type="hidden" name="_csrf_token" value="<?= $this->csrfToken() ?>">
</form>
<script>
document.getElementById('teamSearch')?.addEventListener('input', function() {
const val = this.value.trim();
const url = val ? '/teams?search=' + encodeURIComponent(val) : '/teams';
window.location.href = url;
});
function clearSearch() {
window.location.href = '/teams';
}
function deleteTeam(id, name) {
if (confirm('Delete team "' + name + '"?\nThis will remove access for all members.')) {
const form = document.getElementById('deleteForm');