Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7b34ce6ae |
No files matched your search
@@ -1,7 +1,4 @@
|
|||||||
@import '@fontsource-variable/geist';
|
/* Плотность портала. Палитра, шрифты и таблицы — нативные токены ReUI из @authportal/ui/globals.css. */
|
||||||
@import '@fontsource-variable/geist-mono';
|
|
||||||
|
|
||||||
/* Плотность и контраст макета. Атрибуты ставит PreviewProvider и снимает при выходе. */
|
|
||||||
html[data-density='compact'] [data-slot='data-grid-table'] :is(th, td) {
|
html[data-density='compact'] [data-slot='data-grid-table'] :is(th, td) {
|
||||||
padding-block: 0.25rem;
|
padding-block: 0.25rem;
|
||||||
}
|
}
|
||||||
@@ -10,112 +7,6 @@ html[data-density='compact'] [data-slot='sidebar-inset'] > div:last-child {
|
|||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Палитра MikrotikManager v2 (доски Auth Portal): только пока смонтирован макет. */
|
|
||||||
html:not(.dark) {
|
|
||||||
--background: oklch(0.973 0.004 247.858);
|
|
||||||
--foreground: oklch(0.205 0.01 264);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.205 0.01 264);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.205 0.01 264);
|
|
||||||
--secondary: oklch(0.955 0.005 264);
|
|
||||||
--secondary-foreground: oklch(0.205 0 0);
|
|
||||||
--muted: oklch(0.95 0.006 264);
|
|
||||||
--muted-foreground: oklch(0.44 0.02 264);
|
|
||||||
--accent: oklch(0.93 0.01 264);
|
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
|
||||||
--border: oklch(0.88 0.008 264);
|
|
||||||
--input: oklch(0.86 0.008 264);
|
|
||||||
--ring: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar: oklch(0.985 0.003 264);
|
|
||||||
--sidebar-foreground: oklch(0.205 0.01 264);
|
|
||||||
--sidebar-accent: oklch(0.93 0.01 264);
|
|
||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
||||||
--sidebar-border: oklch(0.88 0.008 264);
|
|
||||||
--invert: oklch(0.9 0.01 264);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--destructive-foreground: oklch(0.45 0.2 27);
|
|
||||||
--success: oklch(0.6 0.15 163);
|
|
||||||
--success-foreground: oklch(0.4 0.1 163);
|
|
||||||
--warning: oklch(0.75 0.16 80);
|
|
||||||
--warning-foreground: oklch(0.45 0.1 70);
|
|
||||||
--info: oklch(0.55 0.23 293);
|
|
||||||
--info-foreground: oklch(0.42 0.2 293);
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark {
|
|
||||||
--background: oklch(0.133 0.011 264.376);
|
|
||||||
--foreground: oklch(0.985 0 0);
|
|
||||||
--card: oklch(0.191 0.011 264.376);
|
|
||||||
--card-foreground: oklch(0.985 0 0);
|
|
||||||
--popover: oklch(0.191 0.011 264.376);
|
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
|
||||||
--secondary: oklch(0.252 0.01 264.376);
|
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
|
||||||
--muted: oklch(0.252 0.01 264.376);
|
|
||||||
--muted-foreground: oklch(0.648 0.008 264.376);
|
|
||||||
--accent: oklch(0.285 0.022 264.376);
|
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar: oklch(0.191 0.011 264.376);
|
|
||||||
--sidebar-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.262 0.014 264.376);
|
|
||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--invert: oklch(0.37 0.013 285.805);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--destructive-foreground: oklch(0.577 0.245 27.325);
|
|
||||||
--success: oklch(0.696 0.17 162.48);
|
|
||||||
--success-foreground: oklch(0.596 0.145 163.225);
|
|
||||||
--warning: oklch(0.795 0.184 86.047);
|
|
||||||
--warning-foreground: oklch(0.681 0.162 75.834);
|
|
||||||
--info: oklch(0.606 0.25 292.717);
|
|
||||||
--info-foreground: oklch(0.541 0.281 293.009);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-contrast='high'] {
|
|
||||||
--border: color-mix(in oklch, var(--foreground) 55%, transparent);
|
|
||||||
--input: color-mix(in oklch, var(--foreground) 60%, transparent);
|
|
||||||
--muted-foreground: color-mix(in oklch, var(--foreground) 88%, transparent);
|
|
||||||
--ring: oklch(0.35 0.2 264);
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark[data-contrast='high'] {
|
|
||||||
--background: oklch(0 0 0);
|
|
||||||
--primary: oklch(1 0 0);
|
|
||||||
--primary-foreground: oklch(0 0 0);
|
|
||||||
--sidebar: oklch(0.1 0 0);
|
|
||||||
--foreground: oklch(1 0 0);
|
|
||||||
--success: oklch(0.82 0.19 150);
|
|
||||||
--success-foreground: oklch(0.9 0.15 150);
|
|
||||||
--warning: oklch(0.9 0.17 95);
|
|
||||||
--warning-foreground: oklch(0.95 0.12 95);
|
|
||||||
--destructive: oklch(0.78 0.2 25);
|
|
||||||
--destructive-foreground: oklch(0.88 0.12 25);
|
|
||||||
--info: oklch(0.8 0.15 300);
|
|
||||||
--info-foreground: oklch(0.9 0.1 300);
|
|
||||||
--card: oklch(0.12 0 0);
|
|
||||||
--popover: oklch(0.1 0 0);
|
|
||||||
--muted: oklch(0.22 0 0);
|
|
||||||
--muted-foreground: oklch(0.86 0 0);
|
|
||||||
--border: oklch(1 0 0 / 55%);
|
|
||||||
--input: oklch(1 0 0 / 60%);
|
|
||||||
--ring: oklch(0.85 0.17 95);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Шрифты дизайн-системы (Geist / Geist Mono) — только пока смонтирован макет. */
|
|
||||||
html {
|
|
||||||
--font-sans: 'Geist Variable', ui-sans-serif, system-ui, sans-serif;
|
|
||||||
--font-mono: 'Geist Mono Variable', ui-monospace, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
html body {
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Видимый фокус и reduced motion — как в макете MikrotikManager v2. */
|
/* Видимый фокус и reduced motion — как в макете MikrotikManager v2. */
|
||||||
html [data-slot='sidebar-wrapper'] :focus-visible,
|
html [data-slot='sidebar-wrapper'] :focus-visible,
|
||||||
html #main-content :focus-visible {
|
html #main-content :focus-visible {
|
||||||
@@ -130,16 +21,3 @@ html #main-content :focus-visible {
|
|||||||
transition: none !important;
|
transition: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Шапка таблицы как на досках: 12px, лёгкая подложка. */
|
|
||||||
html [data-slot='data-grid-table'] thead th,
|
|
||||||
html [data-slot='data-grid-table'] thead th :is(span, button, div) {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
background: color-mix(in oklch, var(--muted) 30%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Затемнение под панелями без размытия — как на досках. */
|
|
||||||
html :is([data-slot='sheet-overlay'], [data-slot='dialog-overlay'], [data-slot='alert-dialog-overlay']) {
|
|
||||||
background: oklch(0 0 0 / 55%);
|
|
||||||
backdrop-filter: none;
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,11 @@ import { useCallback, useState } from 'react'
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
import { ShieldCheckIcon, UserCheckIcon, UserXIcon, UsersIcon } from 'lucide-react'
|
||||||
import type { AdminUser, CreateUserRequest } from '@authportal/shared'
|
import type { AdminUser, CreateUserRequest } from '@authportal/shared'
|
||||||
|
import { PageHead } from '@/components/portal/bits'
|
||||||
|
import { kpi } from '@/components/portal/kpi'
|
||||||
|
import { KpiStrip } from '@/components/portal/kpi-strip'
|
||||||
import { AdminUsersGrid } from '@/components/reui-kit/admin-users-grid'
|
import { AdminUsersGrid } from '@/components/reui-kit/admin-users-grid'
|
||||||
import { CreateUserSheet } from '@/components/reui-kit/create-user-sheet'
|
import { CreateUserSheet } from '@/components/reui-kit/create-user-sheet'
|
||||||
import { UserAccessSheet } from '@/components/reui-kit/user-access-sheet'
|
import { UserAccessSheet } from '@/components/reui-kit/user-access-sheet'
|
||||||
@@ -173,8 +177,21 @@ function AdminUsersPage() {
|
|||||||
[invalidateUsers],
|
[invalidateUsers],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const admins = users.filter((u) => u.is_admin).length
|
||||||
|
const disabled = users.filter((u) => u.disabled).length
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<PageHead title="Пользователи" sub="Учётные записи портала · роли, доступ к приложениям, passkeys" />
|
||||||
|
<KpiStrip
|
||||||
|
aria-label="Показатели"
|
||||||
|
items={[
|
||||||
|
kpi('all', 'Пользователей', String(users.length), { icon: UsersIcon, badge: 'учётных записей' }),
|
||||||
|
kpi('adm', 'Администраторов', String(admins), { icon: ShieldCheckIcon, tone: 'ok', badge: 'is_admin' }),
|
||||||
|
kpi('act', 'Активных', String(users.length - disabled), { icon: UserCheckIcon, badge: 'могут войти' }),
|
||||||
|
kpi('off', 'Отключённых', String(disabled), { icon: UserXIcon, tone: disabled ? 'wn' : 'neutral', badge: 'disabled' }),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
{error ? (
|
{error ? (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<p className="text-destructive text-sm">
|
<p className="text-destructive text-sm">
|
||||||
|
|||||||
Reference in new issue
Block a user