UI refresh: animation, cards, responsive tweaks
Large frontend modernization: add route fade transition and hoard-soft-enter keyframes with prefers-reduced-motion support; introduce smoother motion tokens and stronger shadow tokens. Update global CSS to use subtle surface gradients, unified transitions, hover lift effects, focus rings and improved button/card/table/overlay styles. Wrap <router-view> in a transition and adjust brand/logo sizing and interactions. Revamp several pages/components (Home, Login, Impressum, 404, Forbidden) with adjusted typography, animated entry for sections and improved card hover states. Admin/Dashboard pages enhanced: AdminUsers gains stats, mobile card list & computed counts; AdminUserDetail and Dashboard show compact summary cards and updated styles. Documentation updated (style.md, codexInfo.md) to reflect the new modernisation rules. No API or backend changes.
This commit is contained in:
@@ -13,6 +13,10 @@ const errorMessage = ref('')
|
||||
const users = ref<AdminUser[]>([])
|
||||
|
||||
const hasUsers = computed(() => users.value.length > 0)
|
||||
const activeUserCount = computed(() => users.value.filter((user) => user.isActive).length)
|
||||
const passwordChangeCount = computed(
|
||||
() => users.value.filter((user) => user.mustChangePassword).length,
|
||||
)
|
||||
|
||||
function formatRoles(roles: string[]): string {
|
||||
return roles.length > 0 ? roles.join(', ') : 'Keine Rolle'
|
||||
@@ -68,6 +72,21 @@ onMounted(() => {
|
||||
<p>Alle App-Konten mit Rollen, Status und Passwortwechselpflicht.</p>
|
||||
</header>
|
||||
|
||||
<section v-if="!isLoading && !errorMessage" class="admin-users-stats" aria-label="Benutzerübersicht">
|
||||
<article class="admin-users-stat">
|
||||
<p class="admin-users-stat-label">Konten</p>
|
||||
<p class="admin-users-stat-value">{{ users.length }}</p>
|
||||
</article>
|
||||
<article class="admin-users-stat">
|
||||
<p class="admin-users-stat-label">Aktiv</p>
|
||||
<p class="admin-users-stat-value">{{ activeUserCount }}</p>
|
||||
</article>
|
||||
<article class="admin-users-stat">
|
||||
<p class="admin-users-stat-label">Passwortwechsel</p>
|
||||
<p class="admin-users-stat-value">{{ passwordChangeCount }}</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<v-alert
|
||||
v-if="errorMessage"
|
||||
type="error"
|
||||
@@ -84,54 +103,112 @@ onMounted(() => {
|
||||
<p>Aktuell sind keine Konten vorhanden.</p>
|
||||
</section>
|
||||
|
||||
<div v-else class="admin-users-table-wrap">
|
||||
<v-table class="admin-users-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Benutzername</th>
|
||||
<th>Rollen</th>
|
||||
<th>Aktiv</th>
|
||||
<th>Passwortwechsel</th>
|
||||
<th class="admin-users-col-actions">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="user in users" :key="user.id">
|
||||
<td class="admin-users-cell-user">{{ user.userName || '(ohne Benutzername)' }}</td>
|
||||
<td>{{ formatRoles(user.roles) }}</td>
|
||||
<td>
|
||||
<span
|
||||
:class="[
|
||||
'hoard-status',
|
||||
user.isActive ? 'hoard-status--success' : 'hoard-status--danger',
|
||||
]"
|
||||
>
|
||||
{{ user.isActive ? 'Aktiv' : 'Inaktiv' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
:class="[
|
||||
'hoard-status',
|
||||
user.mustChangePassword ? 'hoard-status--warning' : 'hoard-status--info',
|
||||
]"
|
||||
>
|
||||
{{ user.mustChangePassword ? 'Erforderlich' : 'Nein' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="admin-users-col-actions">
|
||||
<v-btn
|
||||
size="small"
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-account-details-outline"
|
||||
@click="openUserDetail(user.id)"
|
||||
>
|
||||
Details
|
||||
</v-btn>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
<div v-else class="admin-users-list-region">
|
||||
<div class="admin-users-table-wrap">
|
||||
<v-table class="admin-users-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Benutzername</th>
|
||||
<th>Rollen</th>
|
||||
<th>Aktiv</th>
|
||||
<th>Passwortwechsel</th>
|
||||
<th class="admin-users-col-actions">Aktion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="user in users" :key="user.id">
|
||||
<td class="admin-users-cell-user">{{ user.userName || '(ohne Benutzername)' }}</td>
|
||||
<td>{{ formatRoles(user.roles) }}</td>
|
||||
<td>
|
||||
<span
|
||||
:class="[
|
||||
'hoard-status',
|
||||
user.isActive ? 'hoard-status--success' : 'hoard-status--danger',
|
||||
]"
|
||||
>
|
||||
{{ user.isActive ? 'Aktiv' : 'Inaktiv' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
:class="[
|
||||
'hoard-status',
|
||||
user.mustChangePassword ? 'hoard-status--warning' : 'hoard-status--info',
|
||||
]"
|
||||
>
|
||||
{{ user.mustChangePassword ? 'Erforderlich' : 'Nein' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="admin-users-col-actions">
|
||||
<v-btn
|
||||
size="small"
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-account-details-outline"
|
||||
@click="openUserDetail(user.id)"
|
||||
>
|
||||
Details
|
||||
</v-btn>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</div>
|
||||
|
||||
<div class="admin-users-mobile-list" aria-label="Benutzerliste">
|
||||
<article v-for="user in users" :key="user.id" class="admin-users-mobile-card">
|
||||
<header class="admin-users-mobile-head">
|
||||
<span class="admin-users-mobile-avatar">
|
||||
<v-icon icon="mdi-account-outline" size="20" />
|
||||
</span>
|
||||
<div>
|
||||
<p class="admin-users-mobile-label">Benutzer</p>
|
||||
<h2>{{ user.userName || '(ohne Benutzername)' }}</h2>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<dl class="admin-users-mobile-details">
|
||||
<div>
|
||||
<dt>Rollen</dt>
|
||||
<dd>{{ formatRoles(user.roles) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Aktiv</dt>
|
||||
<dd>
|
||||
<span
|
||||
:class="[
|
||||
'hoard-status',
|
||||
user.isActive ? 'hoard-status--success' : 'hoard-status--danger',
|
||||
]"
|
||||
>
|
||||
{{ user.isActive ? 'Aktiv' : 'Inaktiv' }}
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Passwortwechsel</dt>
|
||||
<dd>
|
||||
<span
|
||||
:class="[
|
||||
'hoard-status',
|
||||
user.mustChangePassword ? 'hoard-status--warning' : 'hoard-status--info',
|
||||
]"
|
||||
>
|
||||
{{ user.mustChangePassword ? 'Erforderlich' : 'Nein' }}
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<v-btn
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-account-details-outline"
|
||||
block
|
||||
@click="openUserDetail(user.id)"
|
||||
>
|
||||
Details
|
||||
</v-btn>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-users-actions hoard-action-row">
|
||||
@@ -179,8 +256,46 @@ onMounted(() => {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.admin-users-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.admin-users-stat {
|
||||
padding: var(--space-4);
|
||||
border: 1px solid color-mix(in srgb, var(--color-border) 82%, var(--color-surface) 18%);
|
||||
border-radius: var(--radius-md);
|
||||
background-color: color-mix(in srgb, var(--color-surface-alt) 58%, var(--color-surface) 42%);
|
||||
}
|
||||
|
||||
.admin-users-stat-label,
|
||||
.admin-users-stat-value {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.admin-users-stat-label {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.admin-users-stat-value {
|
||||
color: var(--color-text);
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.admin-users-table-wrap {
|
||||
overflow-x: auto;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.admin-users-mobile-list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.admin-users-table {
|
||||
@@ -201,9 +316,109 @@ onMounted(() => {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.admin-users-shell {
|
||||
animation: hoard-soft-enter 260ms both;
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
.admin-users-shell {
|
||||
padding: var(--space-4);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.admin-users-stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.admin-users-table-wrap {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.admin-users-mobile-list {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.admin-users-mobile-card {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
min-width: 0;
|
||||
padding: var(--space-4);
|
||||
border: 1px solid color-mix(in srgb, var(--color-border) 80%, var(--color-surface) 20%);
|
||||
border-radius: var(--radius-md);
|
||||
background-color: color-mix(in srgb, var(--color-surface-alt) 58%, var(--color-surface) 42%);
|
||||
}
|
||||
|
||||
.admin-users-mobile-head {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: var(--space-3);
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.admin-users-mobile-avatar {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-primary-700);
|
||||
background-color: color-mix(in srgb, var(--color-primary-100) 82%, var(--color-surface) 18%);
|
||||
}
|
||||
|
||||
.admin-users-mobile-label,
|
||||
.admin-users-mobile-head h2,
|
||||
.admin-users-mobile-details {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.admin-users-mobile-label {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.admin-users-mobile-head h2 {
|
||||
color: var(--color-text);
|
||||
font-size: 1.1rem;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.admin-users-mobile-details {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.admin-users-mobile-details div {
|
||||
display: grid;
|
||||
gap: var(--space-1);
|
||||
padding-bottom: var(--space-3);
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--color-border) 78%, var(--color-surface) 22%);
|
||||
}
|
||||
|
||||
.admin-users-mobile-details div:last-child {
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.admin-users-mobile-details dt {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.admin-users-mobile-details dd {
|
||||
margin: 0;
|
||||
color: var(--color-text);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.admin-users-actions {
|
||||
|
||||
Reference in New Issue
Block a user