refactor: tables (#25226)

This commit is contained in:
Jason Rasmussen 2026-01-14 07:56:09 -05:00 committed by GitHub
parent c7254a0c30
commit 91d4cd6824
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 184 additions and 189 deletions

View file

@ -1,9 +1,21 @@
<script lang="ts"> <script lang="ts">
import StatsCard from '$lib/components/server-statistics/ServerStatisticsCard.svelte'; import StatsCard from '$lib/components/server-statistics/ServerStatisticsCard.svelte';
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import { getByteUnitString, getBytesWithUnit } from '$lib/utils/byte-units'; import { getBytesWithUnit } from '$lib/utils/byte-units';
import type { ServerStatsResponseDto } from '@immich/sdk'; import type { ServerStatsResponseDto } from '@immich/sdk';
import { Code, Icon, Text } from '@immich/ui'; import {
Code,
FormatBytes,
Heading,
Icon,
Table,
TableBody,
TableCell,
TableHeader,
TableHeading,
TableRow,
Text,
} from '@immich/ui';
import { mdiCameraIris, mdiChartPie, mdiPlayCircle } from '@mdi/js'; import { mdiCameraIris, mdiChartPie, mdiPlayCircle } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -75,34 +87,28 @@
</div> </div>
<div> <div>
<Text class="mt-6 mb-2 font-medium">{$t('user_usage_detail')}</Text> <Heading size="tiny" class="mb-2">{$t('user_usage_detail')}</Heading>
<table class="mt-5 w-full text-start"> <Table class="mt-5" striped size="small">
<thead <TableHeader>
class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray" <TableHeading class="w-1/4">{$t('user')}</TableHeading>
> <TableHeading class="w-1/4">{$t('photos')}</TableHeading>
<tr class="flex w-full place-items-center"> <TableHeading class="w-1/4">{$t('videos')}</TableHeading>
<th class="w-1/4 text-center text-sm font-medium">{$t('user')}</th> <TableHeading class="w-1/4">{$t('usage')}</TableHeading>
<th class="w-1/4 text-center text-sm font-medium">{$t('photos')}</th> </TableHeader>
<th class="w-1/4 text-center text-sm font-medium">{$t('videos')}</th> <TableBody class="block max-h-80 overflow-y-auto">
<th class="w-1/4 text-center text-sm font-medium">{$t('usage')}</th>
</tr>
</thead>
<tbody
class="block max-h-80 w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray dark:text-immich-dark-fg"
>
{#each stats.usageByUser as user (user.userId)} {#each stats.usageByUser as user (user.userId)}
<tr class="flex h-12.5 w-full place-items-center text-center even:bg-subtle/20 odd:bg-subtle/80"> <TableRow>
<td class="w-1/4 text-ellipsis px-2 text-sm">{user.userName}</td> <TableCell class="w-1/4">{user.userName}</TableCell>
<td class="w-1/4 text-ellipsis px-2 text-sm" <TableCell class="w-1/4">
>{user.photos.toLocaleString($locale)} ({getByteUnitString(user.usagePhotos, $locale, 0)})</td {user.photos.toLocaleString($locale)} (<FormatBytes bytes={user.usagePhotos} />)</TableCell
> >
<td class="w-1/4 text-ellipsis px-2 text-sm" <TableCell class="w-1/4">
>{user.videos.toLocaleString($locale)} ({getByteUnitString(user.usageVideos, $locale, 0)})</td {user.videos.toLocaleString($locale)} (<FormatBytes bytes={user.usageVideos} precision={0} />)</TableCell
> >
<td class="w-1/4 text-ellipsis px-2 text-sm"> <TableCell class="w-1/4">
{getByteUnitString(user.usage, $locale, 0)} <FormatBytes bytes={user.usage} precision={0} />
{#if user.quotaSizeInBytes !== null} {#if user.quotaSizeInBytes !== null}
/ {getByteUnitString(user.quotaSizeInBytes, $locale, 0)} / <FormatBytes bytes={user.quotaSizeInBytes} precision={0} />
{/if} {/if}
<span class="text-primary"> <span class="text-primary">
{#if user.quotaSizeInBytes !== null && user.quotaSizeInBytes >= 0} {#if user.quotaSizeInBytes !== null && user.quotaSizeInBytes >= 0}
@ -114,10 +120,10 @@
({$t('unlimited')}) ({$t('unlimited')})
{/if} {/if}
</span> </span>
</td> </TableCell>
</tr> </TableRow>
{/each} {/each}
</tbody> </TableBody>
</table> </Table>
</div> </div>
</div> </div>

View file

@ -60,7 +60,7 @@
{disabled} {disabled}
onCheckedChange={() => handleCheckboxChange(option.value)} onCheckedChange={() => handleCheckboxChange(option.value)}
/> />
<Label label={option.text} for="{option.value}-checkbox" /> <Label label={option.text} for="{option.value}-checkbox" size="small" />
</div> </div>
{/each} {/each}
</div> </div>

View file

@ -31,7 +31,7 @@
<div> <div>
<div class="flex h-6.5 place-items-center gap-1"> <div class="flex h-6.5 place-items-center gap-1">
<Label>{title}</Label> <Label size="small">{title}</Label>
{#if isEdited} {#if isEdited}
<div <div
transition:fly={{ x: 10, duration: 200, easing: quintOut }} transition:fly={{ x: 10, duration: 200, easing: quintOut }}

View file

@ -5,7 +5,7 @@
import { getApiKeyActions, getApiKeysActions } from '$lib/services/api-key.service'; import { getApiKeyActions, getApiKeysActions } from '$lib/services/api-key.service';
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import { getApiKeys, type ApiKeyResponseDto } from '@immich/sdk'; import { getApiKeys, type ApiKeyResponseDto } from '@immich/sdk';
import { Button } from '@immich/ui'; import { Button, Table, TableBody, TableCell, TableHeader, TableHeading, TableRow, Text } from '@immich/ui';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
@ -20,15 +20,11 @@
}; };
const onApiKeyUpdate = (update: ApiKeyResponseDto) => { const onApiKeyUpdate = (update: ApiKeyResponseDto) => {
for (const key of keys) { keys = keys.map((key) => (key.id === update.id ? update : key));
if (key.id === update.id) {
Object.assign(key, update);
}
}
}; };
const onApiKeyDelete = ({ id }: ApiKeyResponseDto) => { const onApiKeyDelete = ({ id }: ApiKeyResponseDto) => {
keys = keys.filter((apiKey) => apiKey.id !== id); keys = keys.filter((key) => key.id !== id);
}; };
const { Create } = $derived(getApiKeysActions($t)); const { Create } = $derived(getApiKeysActions($t));
@ -39,45 +35,41 @@
<section class="my-4"> <section class="my-4">
<div class="flex flex-col gap-2" in:fade={{ duration: 500 }}> <div class="flex flex-col gap-2" in:fade={{ duration: 500 }}>
<div class="mb-2 flex justify-end"> <div class="mb-2 flex justify-end">
<Button leadingIcon={Create.icon} shape="round" size="small" onclick={() => Create.onAction(Create)} <Button leadingIcon={Create.icon} shape="round" size="small" onclick={() => Create.onAction(Create)}>
>{Create.title}</Button {Create.title}
> </Button>
</div> </div>
{#if keys.length > 0} {#if keys.length > 0}
<table class="w-full text-start"> <Table class="mt-4" striped spacing="small">
<thead <TableHeader>
class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray" <TableHeading>{$t('name')}</TableHeading>
> <TableHeading>{$t('permission')}</TableHeading>
<tr class="flex w-full place-items-center"> <TableHeading>{$t('created')}</TableHeading>
<th class="w-1/4 text-center text-sm font-medium">{$t('name')}</th> <TableHeading>{$t('action')}</TableHeading>
<th class="w-1/4 text-center text-sm font-medium">{$t('permission')}</th> </TableHeader>
<th class="w-1/4 text-center text-sm font-medium">{$t('created')}</th>
<th class="w-1/4 text-center text-sm font-medium">{$t('action')}</th> <TableBody>
</tr>
</thead>
<tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray">
{#each keys as key (key.id)} {#each keys as key (key.id)}
{@const { Update, Delete } = getApiKeyActions($t, key)} {@const { Update, Delete } = getApiKeyActions($t, key)}
<tr <TableRow>
class="flex h-20 w-full place-items-center text-center dark:text-immich-dark-fg even:bg-subtle/20 odd:bg-subtle/80" <TableCell>{key.name}</TableCell>
> <TableCell>
<td class="w-1/4 text-ellipsis px-4 text-sm overflow-hidden">{key.name}</td> <Text
<td class="font-mono overflow-hidden line-clamp-3"
class="w-1/4 text-ellipsis px-4 text-xs overflow-hidden line-clamp-3 break-all font-immich-mono" size="small"
title={JSON.stringify(key.permissions, undefined, 2)}>{key.permissions}</td title={JSON.stringify(key.permissions, null, 2)}>{key.permissions}</Text
> >
<td class="w-1/4 text-ellipsis px-4 text-sm overflow-hidden" </TableCell>
>{new Date(key.createdAt).toLocaleDateString($locale, dateFormats.settings)} <TableCell>{new Date(key.createdAt).toLocaleDateString($locale, dateFormats.settings)}</TableCell>
</td> <TableCell class="flex flex-row flex-wrap justify-center gap-x-2 gap-y-1">
<td class="flex flex-row flex-wrap justify-center gap-x-2 gap-y-1 w-1/4">
<TableButton action={Update} size="small" /> <TableButton action={Update} size="small" />
<TableButton action={Delete} size="small" /> <TableButton action={Delete} size="small" />
</td> </TableCell>
</tr> </TableRow>
{/each} {/each}
</tbody> </TableBody>
</table> </Table>
{/if} {/if}
</div> </div>
</section> </section>

View file

@ -7,6 +7,7 @@
type AlbumStatisticsResponseDto, type AlbumStatisticsResponseDto,
type AssetStatsResponseDto, type AssetStatsResponseDto,
} from '@immich/sdk'; } from '@immich/sdk';
import { Heading, Table, TableBody, TableCell, TableHeader, TableHeading, TableRow } from '@immich/ui';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -56,56 +57,42 @@
</script> </script>
{#snippet row(viewName: string, stats: AssetStatsResponseDto)} {#snippet row(viewName: string, stats: AssetStatsResponseDto)}
<tr <TableRow>
class="flex h-14 w-full place-items-center text-center dark:text-immich-dark-fg even:bg-subtle/20 odd:bg-subtle/80" <TableCell class="w-1/4">{viewName}</TableCell>
> <TableCell class="w-1/4">{stats.images.toLocaleString($locale)}</TableCell>
<td class="w-1/4 px-4 text-sm">{viewName}</td> <TableCell class="w-1/4">{stats.videos.toLocaleString($locale)}</TableCell>
<td class="w-1/4 px-4 text-sm">{stats.images.toLocaleString($locale)}</td> <TableCell class="w-1/4">{stats.total.toLocaleString($locale)}</TableCell>
<td class="w-1/4 px-4 text-sm">{stats.videos.toLocaleString($locale)}</td> </TableRow>
<td class="w-1/4 px-4">{stats.total.toLocaleString($locale)}</td>
</tr>
{/snippet} {/snippet}
<section class="my-6"> <section class="my-6 w-full">
<p class="text-xs dark:text-white uppercase">{$t('photos_and_videos')}</p> <Heading size="tiny">{$t('photos_and_videos')}</Heading>
<div class="overflow-x-auto"> <Table striped spacing="medium" class="mt-4">
<table class="w-full text-start mt-4"> <TableHeader>
<thead <TableHeading class="w-1/4">{$t('view_name')}</TableHeading>
class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray" <TableHeading class="w-1/4">{$t('photos')}</TableHeading>
> <TableHeading class="w-1/4">{$t('videos')}</TableHeading>
<tr class="flex w-full place-items-center text-sm font-medium text-center"> <TableHeading class="w-1/4">{$t('total')}</TableHeading>
<th class="w-1/4">{$t('view_name')}</th> </TableHeader>
<th class="w-1/4">{$t('photos')}</th> <TableBody>
<th class="w-1/4">{$t('videos')}</th> {@render row($t('timeline'), timelineStats)}
<th class="w-1/4">{$t('total')}</th> {@render row($t('favorites'), favoriteStats)}
</tr> {@render row($t('archive'), archiveStats)}
</thead> {@render row($t('trash'), trashStats)}
<tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray"> </TableBody>
{@render row($t('timeline'), timelineStats)} </Table>
{@render row($t('favorites'), favoriteStats)}
{@render row($t('archive'), archiveStats)}
{@render row($t('trash'), trashStats)}
</tbody>
</table>
</div>
<div class="mt-6"> <Heading size="tiny" class="mt-8">{$t('albums')}</Heading>
<p class="text-xs dark:text-white uppercase">{$t('albums')}</p> <Table striped spacing="medium" class="mt-4">
</div> <TableHeader>
<div class="overflow-x-auto"> <TableHeading class="w-1/2">{$t('owned')}</TableHeading>
<table class="w-full text-start mt-4"> <TableHeading class="w-1/2">{$t('shared')}</TableHeading>
<thead class="mb-4 flex h-12 w-full rounded-md border text-primary dark:border-immich-dark-gray bg-subtle"> </TableHeader>
<tr class="flex w-full place-items-center text-sm font-medium text-center"> <TableBody>
<th class="w-1/2">{$t('owned')}</th> <TableRow>
<th class="w-1/2">{$t('shared')}</th> <TableCell class="w-1/2">{albumStats.owned.toLocaleString($locale)}</TableCell>
</tr> <TableCell class="w-1/2">{albumStats.shared.toLocaleString($locale)}</TableCell>
</thead> </TableRow>
<tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray"> </TableBody>
<tr class="flex h-14 w-full place-items-center text-center dark:text-immich-dark-fg bg-subtle/20"> </Table>
<td class="w-1/2 px-4 text-sm">{albumStats.owned.toLocaleString($locale)}</td>
<td class="w-1/2 px-4 text-sm">{albumStats.shared.toLocaleString($locale)}</td>
</tr>
</tbody>
</table>
</div>
</section> </section>

View file

@ -8,7 +8,17 @@
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import { getBytesWithUnit } from '$lib/utils/byte-units'; import { getBytesWithUnit } from '$lib/utils/byte-units';
import { getLibrary, getLibraryStatistics, type LibraryResponseDto } from '@immich/sdk'; import { getLibrary, getLibraryStatistics, type LibraryResponseDto } from '@immich/sdk';
import { Button, CommandPaletteDefaultProvider } from '@immich/ui'; import {
Button,
CommandPaletteDefaultProvider,
Container,
Table,
TableBody,
TableCell,
TableHeader,
TableHeading,
TableRow,
} from '@immich/ui';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
@ -47,6 +57,15 @@
}; };
const { Create, ScanAll } = $derived(getLibrariesActions($t, libraries)); const { Create, ScanAll } = $derived(getLibrariesActions($t, libraries));
const classes = {
column1: 'w-4/12',
column2: 'w-4/12',
column3: 'w-2/12',
column4: 'w-2/12',
column5: 'w-2/12',
column6: 'w-2/12',
};
</script> </script>
<OnEvents {onLibraryCreate} {onLibraryUpdate} {onLibraryDelete} /> <OnEvents {onLibraryCreate} {onLibraryUpdate} {onLibraryDelete} />
@ -54,51 +73,35 @@
<CommandPaletteDefaultProvider name={$t('library')} actions={[Create, ScanAll]} /> <CommandPaletteDefaultProvider name={$t('library')} actions={[Create, ScanAll]} />
<AdminPageLayout breadcrumbs={[{ title: data.meta.title }]} actions={[ScanAll, Create]}> <AdminPageLayout breadcrumbs={[{ title: data.meta.title }]} actions={[ScanAll, Create]}>
<section class="my-4"> <Container size="large" center class="my-4">
<div class="flex flex-col items-center gap-2" in:fade={{ duration: 500 }}> <div class="flex flex-col items-center gap-2" in:fade={{ duration: 500 }}>
{#if libraries.length > 0} {#if libraries.length > 0}
<table class="text-start"> <Table striped>
<thead <TableHeader>
class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray" <TableHeading class={classes.column1}>{$t('name')}</TableHeading>
> <TableHeading class={classes.column2}>{$t('owner')}</TableHeading>
<tr class="grid grid-cols-6 w-full place-items-center"> <TableHeading class={classes.column3}>{$t('photos')}</TableHeading>
<th class="text-center text-sm font-medium">{$t('name')}</th> <TableHeading class={classes.column4}>{$t('videos')}</TableHeading>
<th class="text-center text-sm font-medium">{$t('owner')}</th> <TableHeading class={classes.column5}>{$t('size')}</TableHeading>
<th class="text-center text-sm font-medium">{$t('photos')}</th> <TableHeading class={classes.column6}></TableHeading>
<th class="text-center text-sm font-medium">{$t('videos')}</th> </TableHeader>
<th class="text-center text-sm font-medium">{$t('size')}</th> <TableBody>
<th class="text-center text-sm font-medium"></th>
</tr>
</thead>
<tbody class="block overflow-y-auto rounded-md border dark:border-immich-dark-gray">
{#each libraries as library (library.id + library.name)} {#each libraries as library (library.id + library.name)}
{@const { photos, usage, videos } = statistics[library.id]} {@const { photos, usage, videos } = statistics[library.id]}
{@const [diskUsage, diskUsageUnit] = getBytesWithUnit(usage, 0)} {@const [diskUsage, diskUsageUnit] = getBytesWithUnit(usage, 0)}
<tr <TableRow>
class="grid grid-cols-6 h-20 w-full place-items-center text-center dark:text-immich-dark-fg even:bg-subtle/20 odd:bg-subtle/80" <TableCell class={classes.column1}>{library.name}</TableCell>
> <TableCell class={classes.column2}>{owners[library.id].name}</TableCell>
<td class="text-ellipsis px-4 text-sm">{library.name}</td> <TableCell class={classes.column3}>{photos.toLocaleString($locale)}</TableCell>
<td class="text-ellipsis px-4 text-sm"> <TableCell class={classes.column4}>{videos.toLocaleString($locale)}</TableCell>
{owners[library.id].name} <TableCell class={classes.column5}>{diskUsage} {diskUsageUnit}</TableCell>
</td> <TableCell class={classes.column6}>
<td class="text-ellipsis px-4 text-sm">
{photos.toLocaleString($locale)}
</td>
<td class="text-ellipsis px-4 text-sm">
{videos.toLocaleString($locale)}
</td>
<td class="text-ellipsis px-4 text-sm">
{diskUsage}
{diskUsageUnit}
</td>
<td class="flex gap-2 text-ellipsis px-4 text-sm">
<Button size="small" onclick={() => handleViewLibrary(library)}>{$t('view')}</Button> <Button size="small" onclick={() => handleViewLibrary(library)}>{$t('view')}</Button>
</td> </TableCell>
</tr> </TableRow>
{/each} {/each}
</tbody> </TableBody>
</table> </Table>
{:else} {:else}
<EmptyPlaceholder <EmptyPlaceholder
text={$t('no_libraries_message')} text={$t('no_libraries_message')}
@ -109,5 +112,5 @@
{@render children?.()} {@render children?.()}
</div> </div>
</section> </Container>
</AdminPageLayout> </AdminPageLayout>

View file

@ -5,7 +5,18 @@
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 { Button, CommandPaletteDefaultProvider, Container, Icon } from '@immich/ui'; import {
Button,
CommandPaletteDefaultProvider,
Container,
Icon,
Table,
TableBody,
TableCell,
TableHeader,
TableHeading,
TableRow,
} from '@immich/ui';
import { mdiInfinity } from '@mdi/js'; import { mdiInfinity } from '@mdi/js';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -34,6 +45,13 @@
}; };
const { Create } = $derived(getUserAdminsActions($t)); const { Create } = $derived(getUserAdminsActions($t));
const classes = {
column1: 'w-8/12 sm:w-5/12 lg:w-6/12 xl:w-4/12 2xl:w-5/12',
column2: 'hidden sm:block w-3/12',
column3: 'hidden xl:block w-3/12 2xl:w-2/12',
column4: 'w-4/12 lg:w-3/12 xl:w-2/12',
};
</script> </script>
<OnEvents <OnEvents
@ -48,28 +66,19 @@
<AdminPageLayout breadcrumbs={[{ title: data.meta.title }]} actions={[Create]}> <AdminPageLayout breadcrumbs={[{ title: data.meta.title }]} actions={[Create]}>
<Container center size="large"> <Container center size="large">
<table class="my-5 w-full text-start"> <Table class="mt-4" striped spacing="large">
<thead <TableHeader>
class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray" <TableHeading class={classes.column1}>{$t('email')}</TableHeading>
> <TableHeading class={classes.column2}>{$t('name')}</TableHeading>
<tr class="flex w-full place-items-center"> <TableHeading class={classes.column3}>{$t('has_quota')}</TableHeading>
<th class="w-8/12 sm:w-5/12 lg:w-6/12 xl:w-4/12 2xl:w-5/12 text-center text-sm font-medium">{$t('email')}</th> </TableHeader>
<th class="hidden sm:block w-3/12 text-center text-sm font-medium">{$t('name')}</th>
<th class="hidden xl:block w-3/12 2xl:w-2/12 text-center text-sm font-medium">{$t('has_quota')}</th> <TableBody>
</tr>
</thead>
<tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray">
{#each users as user (user.id)} {#each users as user (user.id)}
<tr <TableRow color={user.deletedAt ? 'danger' : undefined}>
class="flex h-20 overflow-hidden w-full place-items-center text-center dark:text-immich-dark-fg {user.deletedAt <TableCell class={classes.column1}>{user.email}</TableCell>
? 'bg-red-300 dark:bg-red-900' <TableCell class={classes.column2}>{user.name}</TableCell>
: 'even:bg-subtle/20 odd:bg-subtle/80'}" <TableCell class={classes.column3}>
>
<td class="w-8/12 sm:w-5/12 lg:w-6/12 xl:w-4/12 2xl:w-5/12 text-ellipsis break-all px-2 text-sm">
{user.email}
</td>
<td class="hidden sm:block w-3/12 text-ellipsis break-all px-2 text-sm">{user.name}</td>
<td class="hidden xl:block w-3/12 2xl:w-2/12 text-ellipsis break-all px-2 text-sm">
<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}
{getByteUnitString(user.quotaSizeInBytes, $locale)} {getByteUnitString(user.quotaSizeInBytes, $locale)}
@ -77,16 +86,14 @@
<Icon icon={mdiInfinity} size="16" /> <Icon icon={mdiInfinity} size="16" />
{/if} {/if}
</div> </div>
</td> </TableCell>
<td <TableCell class={classes.column4}>
class="flex flex-row flex-wrap justify-center gap-x-2 gap-y-1 w-4/12 lg:w-3/12 xl:w-2/12 text-ellipsis break-all text-sm"
>
<Button onclick={() => handleNavigateUserAdmin(user)}>{$t('view')}</Button> <Button onclick={() => handleNavigateUserAdmin(user)}>{$t('view')}</Button>
</td> </TableCell>
</tr> </TableRow>
{/each} {/each}
</tbody> </TableBody>
</table> </Table>
{@render children?.()} {@render children?.()}
</Container> </Container>