chore: use context menu for user table (#25428)

* chore: use context menu for user table

* chore: reorder columns

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
This commit is contained in:
Alex 2026-01-22 06:44:08 -06:00 committed by GitHub
parent c320146538
commit 7cbfc12e0d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 31 additions and 14 deletions

View file

@ -23,6 +23,7 @@ import {
import { modalManager, toastManager, type ActionItem } from '@immich/ui'; import { modalManager, toastManager, type ActionItem } from '@immich/ui';
import { import {
mdiDeleteRestore, mdiDeleteRestore,
mdiInformationOutline,
mdiLockReset, mdiLockReset,
mdiLockSmart, mdiLockSmart,
mdiPencilOutline, mdiPencilOutline,
@ -46,6 +47,12 @@ export const getUserAdminsActions = ($t: MessageFormatter) => {
}; };
export const getUserAdminActions = ($t: MessageFormatter, user: UserAdminResponseDto) => { export const getUserAdminActions = ($t: MessageFormatter, user: UserAdminResponseDto) => {
const Detail: ActionItem = {
icon: mdiInformationOutline,
title: $t('details'),
onAction: () => goto(Route.viewUser(user)),
};
const Update: ActionItem = { const Update: ActionItem = {
icon: mdiPencilOutline, icon: mdiPencilOutline,
title: $t('edit'), title: $t('edit'),
@ -92,7 +99,7 @@ export const getUserAdminActions = ($t: MessageFormatter, user: UserAdminRespons
onAction: () => handleResetPinCodeUserAdmin(user), onAction: () => handleResetPinCodeUserAdmin(user),
}; };
return { Update, Delete, Restore, ResetPassword, ResetPinCode }; return { Detail, Update, Delete, Restore, ResetPassword, ResetPinCode };
}; };
export const handleCreateUserAdmin = async (dto: UserAdminCreateDto) => { export const handleCreateUserAdmin = async (dto: UserAdminCreateDto) => {

View file

@ -1,15 +1,18 @@
<script lang="ts"> <script lang="ts">
import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte'; import AdminPageLayout from '$lib/components/layouts/AdminPageLayout.svelte';
import OnEvents from '$lib/components/OnEvents.svelte'; import OnEvents from '$lib/components/OnEvents.svelte';
import { getUserAdminsActions, handleNavigateUserAdmin } from '$lib/services/user-admin.service'; import { Route } from '$lib/route';
import { getUserAdminActions, getUserAdminsActions } from '$lib/services/user-admin.service';
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import { getByteUnitString } from '$lib/utils/byte-units'; import { getByteUnitString } from '$lib/utils/byte-units';
import { searchUsersAdmin, type UserAdminResponseDto } from '@immich/sdk'; import { searchUsersAdmin, type UserAdminResponseDto } from '@immich/sdk';
import { import {
Button,
CommandPaletteDefaultProvider, CommandPaletteDefaultProvider,
Container, Container,
ContextMenuButton,
Icon, Icon,
Link,
MenuItemType,
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
@ -46,11 +49,16 @@
const { Create } = $derived(getUserAdminsActions($t)); const { Create } = $derived(getUserAdminsActions($t));
const getActionsForUser = (user: UserAdminResponseDto) => {
const { Detail, Update, Delete, ResetPassword, ResetPinCode } = getUserAdminActions($t, user);
return [Detail, Update, ResetPassword, ResetPinCode, MenuItemType.Divider, Delete];
};
const classes = { const classes = {
column1: 'w-8/12 sm:w-5/12 lg:w-6/12 xl:w-4/12 2xl:w-5/12', column1: 'w-8/12 md:w-5/12 lg:w-4/12',
column2: 'hidden sm:block w-3/12', column2: 'hidden md:block md:w-5/12 lg:w-4/12',
column3: 'hidden xl:block w-3/12 2xl:w-2/12', column3: 'hidden lg:block lg:w-2/12',
column4: 'w-4/12 lg:w-3/12 xl:w-2/12', column4: 'w-4/12 md:w-2/12 flex justify-end',
}; };
</script> </script>
@ -68,16 +76,18 @@
<Container center size="large"> <Container center size="large">
<Table class="mt-4" striped spacing="small" size="small"> <Table class="mt-4" striped spacing="small" size="small">
<TableHeader> <TableHeader>
<TableHeading class={classes.column1}>{$t('email')}</TableHeading> <TableHeading class={classes.column1}>{$t('name')}</TableHeading>
<TableHeading class={classes.column2}>{$t('name')}</TableHeading> <TableHeading class={classes.column2}>{$t('email')}</TableHeading>
<TableHeading class={classes.column3}>{$t('has_quota')}</TableHeading> <TableHeading class={classes.column3}>{$t('has_quota')}</TableHeading>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{#each users as user (user.id)} {#each users as user (user.id)}
<TableRow color={user.deletedAt ? 'danger' : undefined}> <TableRow color={user.deletedAt ? 'danger' : undefined}>
<TableCell class={classes.column1}>{user.email}</TableCell> <TableCell class={classes.column1}>
<TableCell class={classes.column2}>{user.name}</TableCell> <Link href={Route.viewUser(user)}>{user.name}</Link>
</TableCell>
<TableCell class={classes.column2}>{user.email}</TableCell>
<TableCell class={classes.column3}> <TableCell class={classes.column3}>
<div class="container mx-auto flex flex-wrap justify-center"> <div class="container mx-auto flex flex-wrap justify-center">
{#if user.quotaSizeInBytes !== null && user.quotaSizeInBytes >= 0} {#if user.quotaSizeInBytes !== null && user.quotaSizeInBytes >= 0}
@ -88,7 +98,7 @@
</div> </div>
</TableCell> </TableCell>
<TableCell class={classes.column4}> <TableCell class={classes.column4}>
<Button onclick={() => handleNavigateUserAdmin(user)}>{$t('view')}</Button> <ContextMenuButton color="primary" aria-label={$t('open')} items={getActionsForUser(user)} />
</TableCell> </TableCell>
</TableRow> </TableRow>
{/each} {/each}

View file

@ -198,8 +198,8 @@
})} })}
> >
<p class="font-medium text-immich-dark-gray dark:text-white mb-2">{$t('storage')}</p> <p class="font-medium text-immich-dark-gray dark:text-white mb-2">{$t('storage')}</p>
<div class="mt-4 h-[7px] w-full rounded-full bg-gray-200 dark:bg-gray-700"> <div class="mt-4 h-1.75 w-full rounded-full bg-gray-200 dark:bg-gray-700">
<div class="h-[7px] rounded-full {getUsageClass()}" style="width: {usedPercentage}%"></div> <div class="h-1.75 rounded-full {getUsageClass()}" style="width: {usedPercentage}%"></div>
</div> </div>
</div> </div>
{/if} {/if}