refactor: admin card (#24723)

This commit is contained in:
Jason Rasmussen 2025-12-19 12:47:04 -05:00 committed by GitHub
parent 3d2196b0f2
commit 1425b3da6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 164 additions and 207 deletions

View file

@ -0,0 +1,33 @@
<script lang="ts">
import HeaderActionButton from '$lib/components/HeaderActionButton.svelte';
import { Card, CardBody, CardHeader, CardTitle, Icon, type ActionItem, type IconLike } from '@immich/ui';
import type { Snippet } from 'svelte';
type Props = {
icon: IconLike;
title: string;
headerAction?: ActionItem;
children?: Snippet;
};
const { icon, title, headerAction, children }: Props = $props();
</script>
<Card color="secondary">
<CardHeader>
<div class="flex w-full justify-between items-center px-4 py-2">
<div class="flex gap-2 text-primary">
<Icon {icon} size="1.5rem" />
<CardTitle>{title}</CardTitle>
</div>
{#if headerAction}
<HeaderActionButton action={headerAction} />
{/if}
</div>
</CardHeader>
<CardBody>
<div class="px-4 pb-7">
{@render children?.()}
</div>
</CardBody>
</Card>

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import emptyFoldersUrl from '$lib/assets/empty-folders.svg'; import emptyFoldersUrl from '$lib/assets/empty-folders.svg';
import HeaderActionButton from '$lib/components/HeaderActionButton.svelte'; import AdminCard from '$lib/components/AdminCard.svelte';
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 ServerStatisticsCard from '$lib/components/server-statistics/ServerStatisticsCard.svelte'; import ServerStatisticsCard from '$lib/components/server-statistics/ServerStatisticsCard.svelte';
@ -15,18 +15,7 @@
getLibraryFolderActions, getLibraryFolderActions,
} from '$lib/services/library.service'; } from '$lib/services/library.service';
import { getBytesWithUnit } from '$lib/utils/byte-units'; import { getBytesWithUnit } from '$lib/utils/byte-units';
import { import { Code, CommandPaletteContext, Container, Heading, modalManager } from '@immich/ui';
Card,
CardBody,
CardHeader,
CardTitle,
Code,
CommandPaletteContext,
Container,
Heading,
Icon,
modalManager,
} from '@immich/ui';
import { mdiCameraIris, mdiChartPie, mdiFilterMinusOutline, mdiFolderOutline, mdiPlayCircle } from '@mdi/js'; import { mdiCameraIris, mdiChartPie, mdiFilterMinusOutline, mdiFolderOutline, mdiPlayCircle } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import type { PageData } from './$types'; import type { PageData } from './$types';
@ -64,18 +53,8 @@
<ServerStatisticsCard icon={mdiPlayCircle} title={$t('videos')} value={statistics.videos} /> <ServerStatisticsCard icon={mdiPlayCircle} title={$t('videos')} value={statistics.videos} />
<ServerStatisticsCard icon={mdiChartPie} title={$t('usage')} value={storageUsage} {unit} /> <ServerStatisticsCard icon={mdiChartPie} title={$t('usage')} value={storageUsage} {unit} />
</div> </div>
<Card color="secondary">
<CardHeader> <AdminCard icon={mdiFolderOutline} title={$t('folders')} headerAction={AddFolder}>
<div class="flex w-full justify-between items-center px-4 py-2">
<div class="flex gap-2 text-primary">
<Icon icon={mdiFolderOutline} size="1.5rem" />
<CardTitle>{$t('folders')}</CardTitle>
</div>
<HeaderActionButton action={AddFolder} />
</div>
</CardHeader>
<CardBody>
<div class="px-4 pb-7">
{#if library.importPaths.length === 0} {#if library.importPaths.length === 0}
<EmptyPlaceholder <EmptyPlaceholder
src={emptyFoldersUrl} src={emptyFoldersUrl}
@ -101,21 +80,9 @@
</tbody> </tbody>
</table> </table>
{/if} {/if}
</div> </AdminCard>
</CardBody>
</Card> <AdminCard icon={mdiFilterMinusOutline} title={$t('exclusion_pattern')} headerAction={AddExclusionPattern}>
<Card color="secondary">
<CardHeader>
<div class="flex w-full justify-between items-center px-4 py-2">
<div class="flex gap-2 text-primary">
<Icon icon={mdiFilterMinusOutline} size="1.5rem" />
<CardTitle>{$t('exclusion_pattern')}</CardTitle>
</div>
<HeaderActionButton action={AddExclusionPattern} />
</div>
</CardHeader>
<CardBody>
<div class="px-4 pb-7">
<table class="w-full"> <table class="w-full">
<tbody> <tbody>
{#each library.exclusionPatterns as exclusionPattern (exclusionPattern)} {#each library.exclusionPatterns as exclusionPattern (exclusionPattern)}
@ -132,9 +99,7 @@
{/each} {/each}
</tbody> </tbody>
</table> </table>
</div> </AdminCard>
</CardBody>
</Card>
</div> </div>
</Container> </Container>
</AdminPageLayout> </AdminPageLayout>

View file

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import AdminCard from '$lib/components/AdminCard.svelte';
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 ServerStatisticsCard from '$lib/components/server-statistics/ServerStatisticsCard.svelte'; import ServerStatisticsCard from '$lib/components/server-statistics/ServerStatisticsCard.svelte';
@ -15,10 +16,6 @@
import { import {
Alert, Alert,
Badge, Badge,
Card,
CardBody,
CardHeader,
CardTitle,
Code, Code,
CommandPaletteContext, CommandPaletteContext,
Container, Container,
@ -131,16 +128,8 @@
<ServerStatisticsCard icon={mdiChartPie} title={$t('storage')} value={statsUsage} unit={statsUsageUnit} /> <ServerStatisticsCard icon={mdiChartPie} title={$t('storage')} value={statsUsage} unit={statsUsageUnit} />
</div> </div>
</div> </div>
<div>
<Card color="secondary"> <AdminCard icon={mdiAccountOutline} title={$t('profile')}>
<CardHeader>
<div class="flex items-center gap-2 px-4 py-2 text-primary">
<Icon icon={mdiAccountOutline} size="1.5rem" />
<CardTitle>{$t('profile')}</CardTitle>
</div>
</CardHeader>
<CardBody>
<div class="px-4 pb-7">
<Stack gap={2}> <Stack gap={2}>
<div> <div>
<Heading tag="h3" size="tiny">{$t('name')}</Heading> <Heading tag="h3" size="tiny">{$t('name')}</Heading>
@ -163,19 +152,9 @@
<Code>{user.id}</Code> <Code>{user.id}</Code>
</div> </div>
</Stack> </Stack>
</div> </AdminCard>
</CardBody>
</Card> <AdminCard icon={mdiFeatureSearchOutline} title={$t('features')}>
</div>
<Card color="secondary">
<CardHeader>
<div class="flex items-center gap-2 px-4 py-2 text-primary">
<Icon icon={mdiFeatureSearchOutline} size="1.5rem" />
<CardTitle>{$t('features')}</CardTitle>
</div>
</CardHeader>
<CardBody>
<div class="px-4 pb-4">
<Stack gap={3}> <Stack gap={3}>
<FeatureSetting title={$t('email_notifications')} state={userPreferences.emailNotifications.enabled} /> <FeatureSetting title={$t('email_notifications')} state={userPreferences.emailNotifications.enabled} />
<FeatureSetting title={$t('folders')} state={userPreferences.folders.enabled} /> <FeatureSetting title={$t('folders')} state={userPreferences.folders.enabled} />
@ -187,18 +166,9 @@
<FeatureSetting title={$t('tags')} state={userPreferences.tags.enabled} /> <FeatureSetting title={$t('tags')} state={userPreferences.tags.enabled} />
<FeatureSetting title={$t('gcast_enabled')} state={userPreferences.cast.gCastEnabled} /> <FeatureSetting title={$t('gcast_enabled')} state={userPreferences.cast.gCastEnabled} />
</Stack> </Stack>
</div> </AdminCard>
</CardBody>
</Card> <AdminCard icon={mdiChartPieOutline} title={$t('storage_quota')}>
<Card color="secondary">
<CardHeader>
<div class="flex items-center gap-2 px-4 py-2 text-primary">
<Icon icon={mdiChartPieOutline} size="1.5rem" />
<CardTitle>{$t('storage_quota')}</CardTitle>
</div>
</CardHeader>
<CardBody>
<div class="px-4 pb-4">
{#if user.quotaSizeInBytes !== null && user.quotaSizeInBytes >= 0} {#if user.quotaSizeInBytes !== null && user.quotaSizeInBytes >= 0}
<Text> <Text>
{$t('storage_usage', { {$t('storage_usage', {
@ -214,7 +184,6 @@
{$t('unlimited')} {$t('unlimited')}
</Text> </Text>
{/if} {/if}
</div>
{#if user.quotaSizeInBytes !== null && user.quotaSizeInBytes >= 0} {#if user.quotaSizeInBytes !== null && user.quotaSizeInBytes >= 0}
<div <div
@ -232,17 +201,9 @@
</div> </div>
</div> </div>
{/if} {/if}
</CardBody> </AdminCard>
</Card>
<Card color="secondary"> <AdminCard icon={mdiDevices} title={$t('authorized_devices')}>
<CardHeader>
<div class="flex items-center gap-2 px-4 py-2 text-primary">
<Icon icon={mdiDevices} size="1.5rem" />
<CardTitle>{$t('authorized_devices')}</CardTitle>
</div>
</CardHeader>
<CardBody>
<div class="px-4 pb-7">
<Stack gap={3}> <Stack gap={3}>
{#each userSessions as session (session.id)} {#each userSessions as session (session.id)}
<DeviceCard {session} /> <DeviceCard {session} />
@ -250,9 +211,7 @@
<span class="text-dark">{$t('no_devices')}</span> <span class="text-dark">{$t('no_devices')}</span>
{/each} {/each}
</Stack> </Stack>
</div> </AdminCard>
</CardBody>
</Card>
</div> </div>
</Container> </Container>
</div> </div>