Add SettingsSidebar and SettingsSearch components to AdminSettings

- Introduced SettingsSidebar for improved navigation and organization.
- Added SettingsSearch and its mobile variant for enhanced search functionality.
- Updated AdminSettings to utilize new components, streamlining the layout and improving user experience.
This commit is contained in:
PEDZEO
2026-01-21 04:31:07 +03:00
parent 82d38b829a
commit 8d4c00d176
3 changed files with 28 additions and 129 deletions

View File

@@ -4,10 +4,10 @@ import { SearchIcon, CloseIcon } from './icons'
interface SettingsSearchProps { interface SettingsSearchProps {
searchQuery: string searchQuery: string
setSearchQuery: (query: string) => void setSearchQuery: (query: string) => void
resultsCount: number resultsCount?: number
} }
export function SettingsSearch({ searchQuery, setSearchQuery, resultsCount }: SettingsSearchProps) { export function SettingsSearch({ searchQuery, setSearchQuery }: SettingsSearchProps) {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (

View File

@@ -7,6 +7,8 @@ export * from './BrandingTab'
export * from './ThemeTab' export * from './ThemeTab'
export * from './FavoritesTab' export * from './FavoritesTab'
export * from './SettingsTab' export * from './SettingsTab'
export * from './SettingsSidebar'
export * from './SettingsSearch'
// Constants and utils // Constants and utils
export * from './constants' export * from './constants'

View File

@@ -1,23 +1,21 @@
import { useState, useMemo } from 'react' import { useState, useMemo } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import { adminSettingsApi, SettingDefinition } from '../api/adminSettings' import { adminSettingsApi, SettingDefinition } from '../api/adminSettings'
import { themeColorsApi } from '../api/themeColors' import { themeColorsApi } from '../api/themeColors'
import { useFavoriteSettings } from '../hooks/useFavoriteSettings' import { useFavoriteSettings } from '../hooks/useFavoriteSettings'
import { import {
BackIcon,
SearchIcon,
StarIcon,
CloseIcon,
MenuIcon, MenuIcon,
MENU_SECTIONS, MENU_SECTIONS,
MenuItem MenuItem,
formatSettingKey
} from '../components/admin' } from '../components/admin'
import { BrandingTab } from '../components/admin/BrandingTab' import { BrandingTab } from '../components/admin/BrandingTab'
import { ThemeTab } from '../components/admin/ThemeTab' import { ThemeTab } from '../components/admin/ThemeTab'
import { FavoritesTab } from '../components/admin/FavoritesTab' import { FavoritesTab } from '../components/admin/FavoritesTab'
import { SettingsTab } from '../components/admin/SettingsTab' import { SettingsTab } from '../components/admin/SettingsTab'
import { SettingsSidebar } from '../components/admin/SettingsSidebar'
import { SettingsSearch, SettingsSearchMobile, SettingsSearchResults } from '../components/admin/SettingsSearch'
export default function AdminSettings() { export default function AdminSettings() {
const { t } = useTranslation() const { t } = useTranslation()
@@ -72,15 +70,6 @@ export default function AdminSettings() {
})) }))
}, [currentMenuItem, allSettings, t]) }, [currentMenuItem, allSettings, t])
// Helper to format setting key for translation lookup
const formatSettingKey = (name: string): string => {
return name
.replace(/_/g, ' ')
.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ')
}
// Filter settings for search - GLOBAL search across all settings // Filter settings for search - GLOBAL search across all settings
const filteredSettings = useMemo(() => { const filteredSettings = useMemo(() => {
if (!allSettings || !Array.isArray(allSettings) || !searchQuery) return [] if (!allSettings || !Array.isArray(allSettings) || !searchQuery) return []
@@ -172,63 +161,13 @@ export default function AdminSettings() {
)} )}
{/* Sidebar */} {/* Sidebar */}
<aside className={` <SettingsSidebar
fixed lg:sticky lg:top-0 inset-y-0 left-0 z-50 activeSection={activeSection}
w-64 h-screen bg-dark-900 border-r border-dark-700/50 flex-shrink-0 setActiveSection={setActiveSection}
transform transition-transform duration-200 ease-in-out mobileMenuOpen={mobileMenuOpen}
${mobileMenuOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'} setMobileMenuOpen={setMobileMenuOpen}
`}> favoritesCount={favorites.length}
{/* Header */} />
<div className="p-4 border-b border-dark-700/50">
<div className="flex items-center gap-3">
<Link to="/admin" className="p-2 rounded-xl bg-dark-800 hover:bg-dark-700 transition-colors">
<BackIcon />
</Link>
<h1 className="text-lg font-bold text-dark-100">{t('admin.settings.title')}</h1>
<button
onClick={() => setMobileMenuOpen(false)}
className="ml-auto p-2 rounded-xl bg-dark-800 hover:bg-dark-700 transition-colors lg:hidden"
>
<CloseIcon />
</button>
</div>
</div>
{/* Menu */}
<nav className="p-2 space-y-1 overflow-y-auto max-h-[calc(100vh-80px)]">
{MENU_SECTIONS.map((section, sectionIdx) => (
<div key={section.id}>
{sectionIdx > 0 && <div className="my-3 border-t border-dark-700/50" />}
{section.items.map((item) => {
const isActive = activeSection === item.id
const hasIcon = item.iconType === 'star'
return (
<button
key={item.id}
onClick={() => {
setActiveSection(item.id)
setMobileMenuOpen(false)
}}
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all ${
isActive
? 'bg-accent-500/10 text-accent-400'
: 'text-dark-400 hover:text-dark-200 hover:bg-dark-800/50'
}`}
>
{hasIcon && <StarIcon filled={isActive && item.id === 'favorites'} />}
<span className="font-medium">{t(`admin.settings.${item.id}`)}</span>
{item.id === 'favorites' && favorites.length > 0 && (
<span className="ml-auto px-2 py-0.5 text-xs rounded-full bg-warning-500/20 text-warning-400">
{favorites.length}
</span>
)}
</button>
)
})}
</div>
))}
</nav>
</aside>
{/* Main content */} {/* Main content */}
<main className="flex-1 min-w-0"> <main className="flex-1 min-w-0">
@@ -248,64 +187,22 @@ export default function AdminSettings() {
<div className="flex-1" /> <div className="flex-1" />
{/* Search - desktop */} <SettingsSearch
<div className="relative hidden sm:block"> searchQuery={searchQuery}
<input setSearchQuery={setSearchQuery}
type="text" resultsCount={filteredSettings.length}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder={t('admin.settings.searchPlaceholder')}
className="w-48 lg:w-64 pl-10 pr-10 py-2 rounded-xl bg-dark-800 border border-dark-700 text-dark-100 placeholder-dark-500 focus:outline-none focus:border-accent-500 text-sm"
/>
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-dark-500">
<SearchIcon />
</div>
{searchQuery && (
<button
onClick={() => setSearchQuery('')}
className="absolute right-3 top-1/2 -translate-y-1/2 text-dark-500 hover:text-dark-300 transition-colors"
>
<CloseIcon />
</button>
)}
</div>
</div>
{/* Search - mobile */}
<div className="relative mt-3 sm:hidden">
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder={t('admin.settings.searchPlaceholder')}
className="w-full pl-10 pr-10 py-2 rounded-xl bg-dark-800 border border-dark-700 text-dark-100 placeholder-dark-500 focus:outline-none focus:border-accent-500 text-sm"
/> />
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-dark-500">
<SearchIcon />
</div>
{searchQuery && (
<button
onClick={() => setSearchQuery('')}
className="absolute right-3 top-1/2 -translate-y-1/2 text-dark-500 hover:text-dark-300 transition-colors"
>
<CloseIcon />
</button>
)}
</div> </div>
{/* Search results count */} <SettingsSearchMobile
{searchQuery.trim() && ( searchQuery={searchQuery}
<div className="mt-3 flex items-center gap-2 text-sm"> setSearchQuery={setSearchQuery}
<span className="text-dark-400"> />
{filteredSettings.length > 0
? `Найдено: ${filteredSettings.length}` <SettingsSearchResults
: 'Ничего не найдено'} searchQuery={searchQuery}
</span> resultsCount={filteredSettings.length}
{filteredSettings.length > 0 && ( />
<span className="text-dark-500">по запросу «{searchQuery}»</span>
)}
</div>
)}
</div> </div>
{/* Content */} {/* Content */}