fix(ui): обозначить выбранные состояния и поиск трафика

This commit is contained in:
Denozordec committed 2026-09-28 17:02:16 +07:00
1 parent 460cf67313
commit 48b7a061c1
6 files changed
+44 -10

No files matched your search

+13 -8
View File
@@ -338,7 +338,7 @@ function StatChip({ icon, label, value }: { icon: ReactNode; label: string; valu
function ServerCard({ s, selected, onClick }: { s: ServerTraffic; selected: boolean; onClick: () => void }) {
return (
<button onClick={onClick} className={cn(
<button type="button" aria-pressed={selected} onClick={onClick} className={cn(
"text-left w-full rounded-lg border p-3 transition-colors hover:bg-muted/50",
selected ? "border-primary bg-primary/5" : "border-border bg-card",
s.status === "offline" && "opacity-60",
@@ -375,7 +375,7 @@ function ServerCard({ s, selected, onClick }: { s: ServerTraffic; selected: bool
function UserCard({ u, selected, onClick }: { u: UserTraffic; selected: boolean; onClick: () => void }) {
const hasClients = u.interfaces.length > 0
return (
<button onClick={onClick} className={cn(
<button type="button" aria-pressed={selected} onClick={onClick} className={cn(
"text-left w-full rounded-lg border p-3 transition-colors hover:bg-muted/50",
selected ? "border-primary bg-primary/5" : "border-border bg-card",
!hasClients && "opacity-60",
@@ -417,7 +417,7 @@ function UserCard({ u, selected, onClick }: { u: UserTraffic; selected: boolean;
function IfaceCard({ c, selected, onClick }: { c: BoundIfaceTraffic; selected: boolean; onClick: () => void }) {
return (
<button onClick={onClick} className={cn(
<button type="button" aria-pressed={selected} onClick={onClick} className={cn(
"text-left w-full rounded-lg border p-3 transition-colors hover:bg-muted/50",
selected ? "border-primary bg-primary/5" : "border-border bg-card",
c.status === "offline" && "opacity-60",
@@ -516,7 +516,7 @@ function DetailHeader({ range, setRange, children }: {
<div className="flex items-center gap-2 flex-wrap min-w-0">{children}</div>
<div className="flex flex-wrap gap-1">
{TRAFFIC_RANGE_KEYS.map((r) => (
<Button key={r} size="sm" variant={r === range ? "default" : "ghost"}
<Button key={r} type="button" aria-pressed={range === r} size="sm" variant={r === range ? "default" : "ghost"}
className="h-7 px-2 text-xs" onClick={() => setRange(r)}>
{TRAFFIC_RANGE_LABELS[r]}
</Button>
@@ -1178,7 +1178,7 @@ export default function TrafficPage() {
<div className="border-b bg-background shrink-0">
<div role="group" aria-label="Разделы трафика" className="flex items-center overflow-x-auto px-3 sm:px-6">
{GROUP_MODES.map(({ mode, icon, label }) => (
<button key={mode} onClick={() => handleModeChange(mode)}
<button type="button" aria-pressed={effectiveMode === mode} key={mode} onClick={() => handleModeChange(mode)}
className={cn(
"flex items-center gap-2 px-4 py-3 text-sm font-medium border-b-2 transition-colors -mb-px",
effectiveMode === mode
@@ -1224,6 +1224,7 @@ export default function TrafficPage() {
<div role="group" aria-label="Источник потоков" className="flex gap-1">
<button
type="button"
aria-pressed={flowScope === "servers"}
onClick={() => { setFlowScope("servers"); setFlowIface("__all__") }}
className={cn(
"text-[10px] px-2 py-0.5 rounded border transition-colors",
@@ -1236,6 +1237,7 @@ export default function TrafficPage() {
</button>
<button
type="button"
aria-pressed={flowScope === "users"}
onClick={() => { setFlowScope("users"); setFlowIface("__all__"); if (range === "30d") setRange("1h") }}
className={cn(
"text-[10px] px-2 py-0.5 rounded border transition-colors",
@@ -1253,7 +1255,7 @@ export default function TrafficPage() {
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Поиск…"
aria-label="Поиск потоков" placeholder="Поиск…"
className="w-full pl-8 pr-3 h-8 text-xs bg-muted/50 border border-border rounded-md outline-none focus:border-primary transition-colors"
/>
</div>
@@ -1341,7 +1343,7 @@ export default function TrafficPage() {
type="text"
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Поиск…"
aria-label="Поиск трафика" placeholder="Поиск…"
className="w-full pl-8 pr-3 h-8 text-xs bg-muted/50 border border-border rounded-md outline-none focus:border-primary transition-colors"
/>
</div>
@@ -1350,7 +1352,7 @@ export default function TrafficPage() {
<div className="flex gap-1 flex-wrap">
<span className="text-[10px] text-muted-foreground self-center mr-0.5">Сортировка:</span>
{visibleSortFields.map(({ field, label }) => (
<button key={field} onClick={() => toggleSort(field)}
<button type="button" aria-pressed={sortField === field} key={field} onClick={() => toggleSort(field)}
className={cn(
"text-[10px] px-2 py-0.5 rounded border transition-colors",
sortField === field
@@ -1391,6 +1393,7 @@ export default function TrafficPage() {
<div className="flex items-center gap-2 flex-wrap">
<span className="text-[11px] text-muted-foreground mr-1">Интерфейс:</span>
<button
type="button" aria-pressed={hideDisabledIfaces}
onClick={() => setHideDisabledIfaces((v) => !v)}
className={cn(
"inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium transition-all",
@@ -1402,6 +1405,7 @@ export default function TrafficPage() {
{hideDisabledIfaces ? "Показать отключенные" : "Скрыть отключенные"}
</button>
<button
type="button" aria-pressed={selectedIface === "__all__"}
onClick={() => setSelectedIface("__all__")}
className={cn(
"inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 text-[10px] font-medium transition-all",
@@ -1417,6 +1421,7 @@ export default function TrafficPage() {
return (
<button
key={`${iface.name}:${index}`}
type="button" aria-pressed={active}
onClick={() => setSelectedIface(iface.name)}
className={cn(
"inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-[10px] font-medium transition-all",