diff --git a/src/app/layouts/admin-layout/admin-layout.css b/src/app/layouts/admin-layout/admin-layout.css index 3ede811..9b63491 100644 --- a/src/app/layouts/admin-layout/admin-layout.css +++ b/src/app/layouts/admin-layout/admin-layout.css @@ -18,43 +18,89 @@ min-height: 0; /* unico contenitore scrollabile della console admin: header + sidebar + contenuto scorrono insieme */ + gap: 14px; + padding: 16px clamp(14px, 2.5vw, 32px) 24px; + background: #f8fafc; overflow-y: auto; overflow-x: hidden; } .admin-shell__header { display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 16px; - padding: 6px 4px 10px; + align-items: center; + gap: 12px; + padding: 14px 16px; + border-bottom: 1px solid #e2e8f0; + background: #fff; +} + +.admin-shell__brand-mark { + display: grid; + flex: 0 0 auto; + width: 36px; + height: 36px; + place-items: center; + border-radius: 10px; + background: #eff6ff; + color: #2563eb; +} + +.admin-shell__brand-mark mat-icon { + width: 20px; + height: 20px; + font-size: 20px; +} + +.admin-shell__header-copy { + min-width: 0; + flex: 1; } .admin-shell__eyebrow { - color: #2563eb; - font-size: 12px; + color: #64748b; + font-size: 10px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; } .admin-shell__title { - margin: 4px 0 0; + margin: 2px 0 0; color: #0f172a; - font-size: 26px; - font-weight: 800; + font-size: 22px; + font-weight: 700; } .admin-shell__subtitle { - margin: 6px 0 0; + margin: 3px 0 0; color: #64748b; - font-size: 14px; + font-size: 13px; +} + +.admin-shell__header-status { + display: inline-flex; + align-items: center; + gap: 6px; + flex: 0 0 auto; + padding: 6px 9px; + border: 1px solid #dbeafe; + border-radius: 999px; + background: rgba(255, 255, 255, 0.76); + color: #1d4ed8; + font-size: 12px; + font-weight: 700; +} + +.admin-shell__header-status mat-icon { + width: 16px; + height: 16px; + font-size: 16px; } .admin-shell__body { display: grid; - grid-template-columns: 240px minmax(0, 1fr); - gap: 18px; + grid-template-columns: 224px minmax(0, 1fr); + gap: 14px; flex: 1 0 auto; min-height: 0; } @@ -62,23 +108,26 @@ .admin-shell__sidebar { display: flex; flex-direction: column; - gap: 14px; - padding: 18px 14px; - border: 1px solid rgba(148, 163, 184, 0.2); - border-radius: 22px; - background: linear-gradient(180deg, #f8fbff 0%, #eef5ff 100%); - box-shadow: 0 12px 28px rgba(15, 23, 42, 0.06); + gap: 12px; + padding: 12px 10px; + border: 1px solid #e2e8f0; + border-radius: 14px; + background: #fff; + position: sticky; + top: 0; + align-self: start; } .admin-shell__menu-group { display: flex; flex-direction: column; - gap: 10px; + gap: 8px; } .admin-shell__sidebar-title { - color: #0f172a; - font-size: 12px; + padding: 0 8px; + color: #64748b; + font-size: 10px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; @@ -94,14 +143,14 @@ display: flex; align-items: center; gap: 10px; - padding: 12px 14px; - border-radius: 16px; - border: 1px solid rgba(148, 163, 184, 0.24); + padding: 9px 10px; + border-radius: 9px; + border: 1px solid #e2e8f0; color: #0f172a; font-size: 14px; font-weight: 700; text-decoration: none; - background: rgba(255, 255, 255, 0.78); + background: #fff; transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease; } @@ -115,24 +164,34 @@ display: flex; align-items: center; gap: 10px; - padding: 12px 14px; - border-radius: 16px; + padding: 9px 10px; + border-radius: 9px; color: #334155; - font-size: 14px; - font-weight: 700; + font-size: 13px; + font-weight: 600; text-decoration: none; - transition: background-color 120ms ease, color 120ms ease, transform 120ms ease; + position: relative; + transition: background-color 120ms ease, color 120ms ease, transform 120ms ease, box-shadow 120ms ease; } .admin-shell__nav-link:hover { - background: rgba(37, 99, 235, 0.08); + background: #eff6ff; color: #1d4ed8; } .admin-shell__nav-link--active { + background: #eff6ff; + color: #1d4ed8; +} + +.admin-shell__nav-link--active::before { + position: absolute; + left: 0; + width: 3px; + height: 20px; + border-radius: 999px; background: #2563eb; - color: #fff; - box-shadow: 0 12px 24px rgba(37, 99, 235, 0.2); + content: ''; } .admin-shell__content { @@ -147,10 +206,15 @@ .admin-shell__sidebar { padding: 14px; + position: static; } .admin-shell__nav { flex-direction: row; flex-wrap: wrap; } + + .admin-shell__header-status { + display: none; + } } diff --git a/src/app/layouts/admin-layout/admin-layout.html b/src/app/layouts/admin-layout/admin-layout.html index 8484fe5..ebb776e 100644 --- a/src/app/layouts/admin-layout/admin-layout.html +++ b/src/app/layouts/admin-layout/admin-layout.html @@ -6,10 +6,17 @@
-
-
Administration
+ +
+
Administration workspace

Admin Console

-

Manage users and admin-only operations from a dedicated workspace.

+

Users, access and service health in one focused workspace.

+
+
+ + Restricted access
diff --git a/src/app/pages/admin/admin-activity/admin-activity.css b/src/app/pages/admin/admin-activity/admin-activity.css index a4ce316..edab6ec 100644 --- a/src/app/pages/admin/admin-activity/admin-activity.css +++ b/src/app/pages/admin/admin-activity/admin-activity.css @@ -7,34 +7,37 @@ .admin-activity-page { display: flex; flex-direction: column; - gap: 20px; - padding-block: 8px; + gap: 14px; + padding: 8px 4px 16px; } .admin-activity-page__header { display: flex; flex-wrap: wrap; - gap: 16px; + gap: 12px; align-items: flex-start; justify-content: space-between; } .admin-activity-page__eyebrow { - font-size: 12px; + color: #2563eb; + font-size: 10px; + font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; - opacity: 0.6; } .admin-activity-page__title { - margin: 4px 0 6px; - font-size: 22px; + margin: 2px 0 4px; + color: #0f172a; + font-size: 24px; + font-weight: 700; } .admin-activity-page__subtitle { margin: 0; max-width: 62ch; - opacity: 0.75; + color: #64748b; } .admin-activity-page__actions { @@ -53,10 +56,11 @@ display: flex; align-items: center; gap: 10px; - padding: 12px 16px; - border-radius: 10px; + padding: 9px 11px; + border-radius: 12px; border: 1px solid transparent; - font-weight: 500; + box-shadow: none; + font-weight: 600; } .admin-activity-verdict--clear { @@ -77,11 +81,14 @@ .admin-activity-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); - gap: 16px; + gap: 12px; } .admin-activity-card { - padding: 16px; + padding: 14px; + border: 1px solid rgba(203, 213, 225, 0.84); + border-radius: 14px; + box-shadow: none; } .admin-activity-card__title { @@ -94,10 +101,18 @@ } .admin-activity-card__count { - font-size: 24px; + color: #1d4ed8; + font-size: 20px; + font-weight: 700; font-variant-numeric: tabular-nums; } +:host ::ng-deep .mat-mdc-button-base { + min-height: 34px; + padding-inline: 10px; + font-size: 12px; +} + .admin-activity-card__hint { margin: 6px 0 12px; font-size: 12px; diff --git a/src/app/pages/admin/admin-create-user/admin-create-user.css b/src/app/pages/admin/admin-create-user/admin-create-user.css index b883e26..767a026 100644 --- a/src/app/pages/admin/admin-create-user/admin-create-user.css +++ b/src/app/pages/admin/admin-create-user/admin-create-user.css @@ -7,37 +7,36 @@ .admin-create-user-page { display: flex; flex-direction: column; - gap: 16px; + gap: 14px; min-height: 100%; padding: 8px 4px 16px; - background: linear-gradient(180deg, #f8fafc 0%, #eef6ff 100%); } .admin-create-user-page__eyebrow { color: #2563eb; - font-size: 12px; + font-size: 10px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; } .admin-create-user-page__title { - margin: 4px 0 0; + margin: 2px 0 0; color: #0f172a; - font-size: 28px; - font-weight: 800; + font-size: 24px; + font-weight: 700; } .admin-create-user-page__subtitle { - margin: 6px 0 0; + margin: 3px 0 0; color: #64748b; - font-size: 14px; + font-size: 13px; } .admin-create-user-page__success, .admin-create-user-card__error { - border-radius: 14px; - padding: 12px 14px; + border-radius: 10px; + padding: 9px 11px; font-size: 13px; font-weight: 600; } @@ -49,15 +48,17 @@ } .admin-create-user-card { - border-radius: 22px; - padding: 20px; + border-radius: 14px; + padding: 14px; max-width: 980px; + border: 1px solid #e2e8f0; + box-shadow: none; } .admin-create-user-card__title { margin-bottom: 14px; color: #0f172a; - font-size: 18px; + font-size: 16px; font-weight: 700; } @@ -87,6 +88,16 @@ display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 12px; + padding: 12px; + border: 1px solid #e2e8f0; + border-radius: 12px; + background: #f8fafc; +} + +:host ::ng-deep .mat-mdc-button-base { + min-height: 34px; + padding-inline: 10px; + font-size: 12px; } .admin-create-user-check { @@ -103,7 +114,9 @@ .admin-create-user-form__actions { display: flex; + align-items: center; justify-content: flex-end; + padding-top: 4px; } @media (max-width: 1100px) { diff --git a/src/app/pages/admin/admin-stats/admin-stats.css b/src/app/pages/admin/admin-stats/admin-stats.css index 9cd176e..7f64163 100644 --- a/src/app/pages/admin/admin-stats/admin-stats.css +++ b/src/app/pages/admin/admin-stats/admin-stats.css @@ -7,42 +7,43 @@ .admin-stats-page { display: flex; flex-direction: column; - gap: 16px; + gap: 14px; min-height: 100%; padding: 8px 4px 16px; - background: linear-gradient(180deg, #f8fafc 0%, #eef6ff 100%); } .admin-stats-page__eyebrow { color: #2563eb; - font-size: 12px; + font-size: 10px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; } .admin-stats-page__title { - margin: 4px 0 0; + margin: 2px 0 0; color: #0f172a; - font-size: 28px; - font-weight: 800; + font-size: 24px; + font-weight: 700; } .admin-stats-page__subtitle { - margin: 6px 0 0; + margin: 3px 0 0; color: #64748b; - font-size: 14px; + font-size: 13px; } .admin-stats-card { - border-radius: 22px; - padding: 20px; + border-radius: 14px; + padding: 14px; + border: 1px solid #e2e8f0; + box-shadow: none; } .admin-stats-card__title { margin-bottom: 14px; color: #0f172a; - font-size: 18px; + font-size: 16px; font-weight: 700; } @@ -69,7 +70,7 @@ .admin-stats-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 14px; + gap: 10px; } .admin-stats-users { @@ -84,20 +85,19 @@ flex-direction: column; gap: 3px; min-width: 180px; - padding: 12px 14px; + padding: 9px 11px; border: 1px solid #dbe7f5; - border-radius: 16px; - background: #f8fbff; + border-radius: 12px; + background: #fff; color: #334155; text-align: left; cursor: pointer; - transition: border-color 120ms ease, background-color 120ms ease, transform 120ms ease; + transition: border-color 120ms ease, background-color 120ms ease; } .admin-stats-user-chip:hover { border-color: #93c5fd; background: #eff6ff; - transform: translateY(-1px); } .admin-stats-user-chip--active { @@ -117,8 +117,11 @@ } .admin-stats-metric { - border-radius: 20px; - padding: 18px; + border-radius: 14px; + padding: 14px; + border: 1px solid #e2e8f0; + background: #fff; + box-shadow: none; } .admin-stats-metric__label { @@ -132,8 +135,14 @@ .admin-stats-metric__value { margin-top: 10px; color: #0f172a; - font-size: 34px; - font-weight: 800; + font-size: 28px; + font-weight: 700; +} + +:host ::ng-deep .mat-mdc-button-base { + min-height: 34px; + padding-inline: 10px; + font-size: 12px; } .admin-stats-metric__meta { diff --git a/src/app/pages/admin/admin-users-list/admin-users-list.css b/src/app/pages/admin/admin-users-list/admin-users-list.css index 206eaf2..88a8338 100644 --- a/src/app/pages/admin/admin-users-list/admin-users-list.css +++ b/src/app/pages/admin/admin-users-list/admin-users-list.css @@ -7,10 +7,9 @@ .admin-users-list-page { display: flex; flex-direction: column; - gap: 16px; + gap: 14px; min-height: 100%; padding: 8px 4px 16px; - background: linear-gradient(180deg, #f8fafc 0%, #eef6ff 100%); } .admin-users-list-page__header { @@ -22,30 +21,30 @@ .admin-users-list-page__eyebrow { color: #2563eb; - font-size: 12px; + font-size: 10px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; } .admin-users-list-page__title { - margin: 4px 0 0; + margin: 2px 0 0; color: #0f172a; - font-size: 28px; - font-weight: 800; + font-size: 24px; + font-weight: 700; } .admin-users-list-page__subtitle { - margin: 6px 0 0; + margin: 3px 0 0; color: #64748b; - font-size: 14px; + font-size: 13px; } .admin-users-list-page__success, .admin-users-list-card__error, .admin-users-list-row__error { - border-radius: 14px; - padding: 12px 14px; + border-radius: 10px; + padding: 9px 11px; font-size: 13px; font-weight: 600; } @@ -57,17 +56,38 @@ } .admin-users-list-card { - border-radius: 22px; - padding: 20px; + border-radius: 14px; + padding: 14px; + border: 1px solid #e2e8f0; + box-shadow: none; +} + +.admin-users-list-card__toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 14px; } .admin-users-list-card__title { - margin-bottom: 14px; color: #0f172a; - font-size: 18px; + font-size: 16px; font-weight: 700; } +.admin-users-list-card__summary { + margin-top: 4px; + color: #64748b; + font-size: 12px; + font-weight: 600; +} + +.admin-users-list-card__search { + width: min(100%, 340px); + margin: -10px 0; +} + .admin-users-list-card__error, .admin-users-list-row__error { border: 1px solid #fecaca; @@ -81,10 +101,21 @@ padding: 12px 4px; } +.admin-users-list-state--empty { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + min-height: 90px; + border: 1px dashed #cbd5e1; + border-radius: 16px; + background: #f8fafc; +} + .admin-users-list { display: flex; flex-direction: column; - gap: 12px; + gap: 8px; } .admin-users-list-row { @@ -93,32 +124,58 @@ gap: 16px; align-items: center; border: 1px solid #e2e8f0; - border-radius: 18px; + border-radius: 12px; + background: #fff; + padding: 12px; + transition: border-color 140ms ease, background-color 140ms ease; +} + +.admin-users-list-row:hover { + border-color: #bfdbfe; background: #f8fbff; - padding: 16px; } .admin-users-list-row__identity { + display: flex; + align-items: center; + min-width: 0; + gap: 11px; +} + +.admin-users-list-row__avatar { + display: grid; + flex: 0 0 auto; + width: 32px; + height: 32px; + place-items: center; + border-radius: 9px; + background: #eff6ff; + color: #1d4ed8; + font-size: 13px; + font-weight: 800; +} + +.admin-users-list-row__identity-copy { min-width: 0; } .admin-users-list-row__username { color: #0f172a; - font-size: 15px; + font-size: 14px; font-weight: 700; } .admin-users-list-row__email { color: #64748b; font-size: 13px; - margin-top: 4px; + margin-top: 2px; word-break: break-word; } .admin-users-list-row__role { display: flex; align-items: center; - gap: 10px; + gap: 8px; } .admin-users-list-row__role-field { @@ -133,6 +190,35 @@ flex-wrap: wrap; } +.admin-users-list-row__role-badge { + display: inline-flex; + align-items: center; + padding: 4px 7px; + border: 1px solid #cbd5e1; + border-radius: 999px; + background: #f8fafc; + color: #475569; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.06em; +} + +:host ::ng-deep .mat-mdc-button-base { + min-height: 34px; + padding-inline: 10px; + font-size: 12px; +} + +:host ::ng-deep .mat-mdc-button-base .mat-icon { + margin-right: 4px; +} + +.admin-users-list-row__role-badge--admin { + border-color: #c4b5fd; + background: #f5f3ff; + color: #6d28d9; +} + .admin-users-list-row__error { grid-column: 1 / -1; } @@ -145,4 +231,14 @@ .admin-users-list-row__actions { justify-content: flex-start; } + + .admin-users-list-card__toolbar { + align-items: stretch; + flex-direction: column; + } + + .admin-users-list-card__search { + width: 100%; + margin: 0; + } } diff --git a/src/app/pages/admin/admin-users-list/admin-users-list.html b/src/app/pages/admin/admin-users-list/admin-users-list.html index 31c113a..9a8ea74 100644 --- a/src/app/pages/admin/admin-users-list/admin-users-list.html +++ b/src/app/pages/admin/admin-users-list/admin-users-list.html @@ -11,9 +11,9 @@

Users

Review existing users, change roles, reset passwords and remove accounts.

-
@@ -22,7 +22,24 @@ } -
Users
+
+
+
Users
+
+ {{ filteredUsers().length }} of {{ users().length }} account{{ users().length === 1 ? '' : 's' }} shown +
+
+ + Find a user + + + @if (userSearch()) { + + } + +
@if (loading()) {
Loading users...
@@ -30,13 +47,22 @@
{{ pageError() }}
} @else if (!users().length) {
No users found.
+ } @else if (!filteredUsers().length) { +
+ + No users match “{{ userSearch() }}”. + +
} @else {
- @for (user of users(); track user.username) { + @for (user of filteredUsers(); track user.username) {
-
{{ user.username }}
- + +
+
{{ user.username }}
+ +
@@ -47,15 +73,23 @@ ADMIN -
- + + {{ user.role }} + +
diff --git a/src/app/pages/admin/admin-users-list/admin-users-list.ts b/src/app/pages/admin/admin-users-list/admin-users-list.ts index 5d84b15..db02edb 100644 --- a/src/app/pages/admin/admin-users-list/admin-users-list.ts +++ b/src/app/pages/admin/admin-users-list/admin-users-list.ts @@ -3,12 +3,13 @@ // Attribution term under AGPL-3.0 section 7(b): see LICENSE-ADDENDUM. import { CommonModule } from '@angular/common'; -import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { AdminUser, UserRole } from '@models/user'; import { Router } from '@angular/router'; @@ -27,6 +28,7 @@ import { scheduleSignalClear } from '@utilities/temporary-signal'; MatCardModule, MatFormFieldModule, MatIconModule, + MatInputModule, MatSelectModule, AdminResetPasswordDialogComponent ], @@ -40,6 +42,14 @@ export class AdminUsersListPage { private router = inject(Router); readonly users = signal([]); + readonly userSearch = signal(''); + readonly filteredUsers = computed(() => { + const query = this.userSearch().trim().toLowerCase(); + if (!query) return this.users(); + return this.users().filter((user) => + `${user.username} ${user.email ?? ''} ${user.role}`.toLowerCase().includes(query) + ); + }); readonly loading = signal(true); readonly pageError = signal(null); readonly roleSavingByUser = signal>({});