Split admin/+page.svelte (573 lines) into: - UsersTab.svelte: user table with delete action - TagsTab.svelte: tag list with inline rename and delete - GroupsTab.svelte: groups table with inline edit + create form - SystemTab.svelte: backfill buttons with own state Page drops from 573 → ~40 lines. Part of #75 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
121 lines
3.8 KiB
Svelte
121 lines
3.8 KiB
Svelte
<script lang="ts">
|
||
import { enhance } from '$app/forms';
|
||
import { m } from '$lib/paraglide/messages.js';
|
||
|
||
let {
|
||
users
|
||
}: {
|
||
users: {
|
||
id: string;
|
||
username: string;
|
||
firstName?: string;
|
||
lastName?: string;
|
||
groups?: { id: string; name: string }[];
|
||
}[];
|
||
} = $props();
|
||
</script>
|
||
|
||
<div class="overflow-hidden rounded-lg border border-line bg-surface shadow-sm">
|
||
<div class="flex items-center justify-between border-b border-line-2 p-6">
|
||
<h2 class="text-lg font-bold text-ink-2">{m.admin_section_users()}</h2>
|
||
<a
|
||
href="/admin/users/new"
|
||
class="inline-flex items-center gap-1 rounded-sm bg-primary px-4 py-2 font-sans text-xs font-bold tracking-widest text-white uppercase transition-opacity hover:opacity-80"
|
||
>
|
||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||
</svg>
|
||
{m.admin_btn_new_user()}
|
||
</a>
|
||
</div>
|
||
|
||
<table class="min-w-full divide-y divide-line">
|
||
<thead class="bg-muted">
|
||
<tr>
|
||
<th class="px-6 py-3 text-left text-xs font-bold tracking-wider text-ink-2 uppercase"
|
||
>{m.admin_col_login()}</th
|
||
>
|
||
<th class="px-6 py-3 text-left text-xs font-bold tracking-wider text-ink-2 uppercase"
|
||
>{m.admin_col_full_name()}</th
|
||
>
|
||
<th class="px-6 py-3 text-left text-xs font-bold tracking-wider text-ink-2 uppercase"
|
||
>{m.admin_col_groups()}</th
|
||
>
|
||
<th class="px-6 py-3 text-right text-xs font-bold tracking-wider text-ink-2 uppercase"
|
||
>{m.admin_col_actions()}</th
|
||
>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-line bg-surface">
|
||
{#each users as user (user.id)}
|
||
<tr class="group/row hover:bg-muted">
|
||
<td class="px-6 py-4 text-sm font-medium whitespace-nowrap text-ink">
|
||
{user.username}
|
||
</td>
|
||
<td class="px-6 py-4 text-sm whitespace-nowrap text-ink-2">
|
||
{#if user.firstName || user.lastName}
|
||
{user.firstName ?? ''} {user.lastName ?? ''}
|
||
{:else}
|
||
<span class="text-ink-3 italic">–</span>
|
||
{/if}
|
||
</td>
|
||
<td class="px-6 py-4 text-sm text-ink-2">
|
||
<div class="flex flex-wrap gap-1">
|
||
{#if user.groups && user.groups.length > 0}
|
||
{#each user.groups as group (group.id)}
|
||
<span
|
||
class="rounded-full border border-blue-100 bg-blue-50 px-2 py-0.5 text-[10px] font-bold text-blue-700 uppercase"
|
||
>
|
||
{group.name}
|
||
</span>
|
||
{/each}
|
||
{:else}
|
||
<span class="text-xs text-ink-3 italic">{m.admin_no_groups()}</span>
|
||
{/if}
|
||
</div>
|
||
</td>
|
||
<td class="px-6 py-4 text-right whitespace-nowrap">
|
||
<div class="flex items-center justify-end gap-4">
|
||
<a
|
||
href="/admin/users/{user.id}"
|
||
class="text-sm font-bold tracking-wide text-accent uppercase hover:text-ink"
|
||
>
|
||
{m.btn_edit()}
|
||
</a>
|
||
|
||
<form
|
||
method="POST"
|
||
action="?/deleteUser"
|
||
use:enhance={({ cancel }) => {
|
||
if (!confirm(m.admin_user_delete_confirm({ username: user.username }))) {
|
||
cancel();
|
||
}
|
||
return async ({ update }) => {
|
||
await update();
|
||
};
|
||
}}
|
||
class="flex items-center"
|
||
>
|
||
<input type="hidden" name="id" value={user.id} />
|
||
<button
|
||
class="p-1 text-ink-3 transition-colors hover:text-red-600"
|
||
title={m.admin_btn_delete_user_title()}
|
||
>
|
||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
stroke-width="2"
|
||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||
/>
|
||
</svg>
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
{/each}
|
||
</tbody>
|
||
</table>
|
||
</div>
|