feat(task-35): admin People section — vets, sponsors, mentors, volunteers, users
Full CRUD for vets, sponsors, mentors, volunteer applications, and users. Includes approve/deny flows for pending users, role management, and password reset. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
<script lang="ts">
|
||||
let { data } = $props();
|
||||
const vetList = $derived(data.vets);
|
||||
const inputClass = 'w-full px-3 py-2.5 rounded-lg border border-gray-soft bg-white text-gray-brand placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-teal-brand focus:border-teal-brand text-sm';
|
||||
</script>
|
||||
|
||||
<svelte:head><title>Vets — Admin</title></svelte:head>
|
||||
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900">Vet Management</h1>
|
||||
<a href="/admin/vets/new" class="px-4 py-2.5 text-sm font-medium rounded-lg bg-teal-brand text-white hover:bg-teal-dark transition-colors">+ Add Vet</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-soft p-4 mb-6">
|
||||
<form method="GET" class="flex gap-3">
|
||||
<input type="text" name="q" value={data.q} placeholder="Search by name or clinic..." class={inputClass} />
|
||||
<button type="submit" class="px-5 py-2.5 text-sm font-medium rounded-lg bg-teal-brand text-white hover:bg-teal-dark transition-colors whitespace-nowrap cursor-pointer">Search</button>
|
||||
{#if data.q}
|
||||
<a href="/admin/vets" class="px-5 py-2.5 text-sm font-medium rounded-lg border border-gray-soft text-gray-brand hover:bg-gray-100 transition-colors whitespace-nowrap">Clear</a>
|
||||
{/if}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{#if vetList.length === 0}
|
||||
<div class="text-center py-12 text-gray-400">
|
||||
<svg class="mx-auto h-12 w-12 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
<p class="text-sm font-medium">{data.q ? 'No vets match your search' : 'No vets yet'}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-soft overflow-hidden">
|
||||
<table class="w-full text-sm text-left">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-soft bg-gray-50">
|
||||
<th class="px-6 py-3 font-semibold text-gray-600">Name</th>
|
||||
<th class="px-6 py-3 font-semibold text-gray-600">Clinic</th>
|
||||
<th class="px-6 py-3 font-semibold text-gray-600">Phone</th>
|
||||
<th class="px-6 py-3 font-semibold text-gray-600">Email</th>
|
||||
<th class="px-6 py-3 font-semibold text-gray-600 text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each vetList as vet}
|
||||
<tr class="border-b border-gray-soft last:border-0 hover:bg-gray-50 transition-colors">
|
||||
<td class="px-6 py-4"><a href="/admin/vets/{vet.id}" class="font-medium text-teal-brand hover:text-teal-dark">{vet.name}</a></td>
|
||||
<td class="px-6 py-4 text-gray-600">{vet.clinic ?? '—'}</td>
|
||||
<td class="px-6 py-4 text-gray-600">{#if vet.phone}<a href="tel:{vet.phone}" class="hover:text-teal-brand">{vet.phone}</a>{:else}—{/if}</td>
|
||||
<td class="px-6 py-4 text-gray-600">{#if vet.email}<a href="mailto:{vet.email}" class="hover:text-teal-brand">{vet.email}</a>{:else}—{/if}</td>
|
||||
<td class="px-6 py-4 text-right"><a href="/admin/vets/{vet.id}" class="text-sm font-medium text-teal-brand hover:text-teal-dark">Edit</a></td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-gray-400">{vetList.length} vet{vetList.length !== 1 ? 's' : ''} total</p>
|
||||
{/if}
|
||||
</div>
|
||||
Reference in New Issue
Block a user