mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
Improve admin settings UI components
- Add EditIcon component to icons.tsx - Enhance SettingInput with textarea for long/JSON values - Improve SettingRow styling and long value handling - Fix CORE category translation (Ядро → Бот) - Add auto-resize for textarea and Ctrl+Enter save shortcut
This commit is contained in:
@@ -41,7 +41,7 @@ export function FavoritesTab({ settings, isFavorite, toggleFavorite }: Favorites
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
{settings.map((setting) => (
|
{settings.map((setting) => (
|
||||||
<SettingRow
|
<SettingRow
|
||||||
key={setting.key}
|
key={setting.key}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react'
|
import { useState, useRef, useEffect } from 'react'
|
||||||
import { SettingDefinition } from '../../api/adminSettings'
|
import { SettingDefinition } from '../../api/adminSettings'
|
||||||
import { CheckIcon, CloseIcon } from './icons'
|
import { CheckIcon, CloseIcon, EditIcon } from './icons'
|
||||||
|
|
||||||
interface SettingInputProps {
|
interface SettingInputProps {
|
||||||
setting: SettingDefinition
|
setting: SettingDefinition
|
||||||
@@ -8,12 +8,48 @@ interface SettingInputProps {
|
|||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if value is likely JSON or multi-line
|
||||||
|
function isLongValue(value: string | null | undefined): boolean {
|
||||||
|
if (!value) return false
|
||||||
|
const str = String(value)
|
||||||
|
return str.length > 50 || str.includes('\n') || str.startsWith('[') || str.startsWith('{')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if key suggests it's a list or JSON config
|
||||||
|
function isListOrJsonKey(key: string): boolean {
|
||||||
|
const lowerKey = key.toLowerCase()
|
||||||
|
return (
|
||||||
|
lowerKey.includes('_items') ||
|
||||||
|
lowerKey.includes('_config') ||
|
||||||
|
lowerKey.includes('_keywords') ||
|
||||||
|
lowerKey.includes('_list') ||
|
||||||
|
lowerKey.includes('_json') ||
|
||||||
|
lowerKey.includes('_template') ||
|
||||||
|
lowerKey.includes('_periods') ||
|
||||||
|
lowerKey.includes('_discounts') ||
|
||||||
|
lowerKey.includes('_packages')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function SettingInput({ setting, onUpdate, disabled }: SettingInputProps) {
|
export function SettingInput({ setting, onUpdate, disabled }: SettingInputProps) {
|
||||||
const [isEditing, setIsEditing] = useState(false)
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
const [value, setValue] = useState('')
|
const [value, setValue] = useState('')
|
||||||
|
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
|
const currentValue = String(setting.current ?? '')
|
||||||
|
const needsTextarea = isLongValue(currentValue) || isListOrJsonKey(setting.key)
|
||||||
|
|
||||||
|
// Auto-resize textarea
|
||||||
|
useEffect(() => {
|
||||||
|
if (textareaRef.current && isEditing) {
|
||||||
|
textareaRef.current.style.height = 'auto'
|
||||||
|
textareaRef.current.style.height = Math.min(textareaRef.current.scrollHeight, 300) + 'px'
|
||||||
|
}
|
||||||
|
}, [value, isEditing])
|
||||||
|
|
||||||
const handleStart = () => {
|
const handleStart = () => {
|
||||||
setValue(String(setting.current ?? ''))
|
setValue(currentValue)
|
||||||
setIsEditing(true)
|
setIsEditing(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,10 +67,10 @@ export function SettingInput({ setting, onUpdate, disabled }: SettingInputProps)
|
|||||||
if (setting.choices && setting.choices.length > 0) {
|
if (setting.choices && setting.choices.length > 0) {
|
||||||
return (
|
return (
|
||||||
<select
|
<select
|
||||||
value={String(setting.current ?? '')}
|
value={currentValue}
|
||||||
onChange={(e) => onUpdate(e.target.value)}
|
onChange={(e) => onUpdate(e.target.value)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="bg-dark-700 border border-dark-600 rounded-lg px-3 py-1.5 text-sm text-dark-100 focus:outline-none focus:border-accent-500 disabled:opacity-50"
|
className="bg-dark-700 border border-dark-600 rounded-lg px-3 py-2 text-sm text-dark-100 focus:outline-none focus:border-accent-500 focus:ring-1 focus:ring-accent-500/30 disabled:opacity-50 min-w-[140px] cursor-pointer"
|
||||||
>
|
>
|
||||||
{setting.choices.map((choice, idx) => (
|
{setting.choices.map((choice, idx) => (
|
||||||
<option key={idx} value={String(choice.value)}>
|
<option key={idx} value={String(choice.value)}>
|
||||||
@@ -45,11 +81,51 @@ export function SettingInput({ setting, onUpdate, disabled }: SettingInputProps)
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Editing mode
|
// Editing mode - Textarea for long values
|
||||||
|
if (isEditing && needsTextarea) {
|
||||||
|
return (
|
||||||
|
<div className="w-full space-y-2">
|
||||||
|
<textarea
|
||||||
|
ref={textareaRef}
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => setValue(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Escape') handleCancel()
|
||||||
|
// Ctrl+Enter to save
|
||||||
|
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) handleSave()
|
||||||
|
}}
|
||||||
|
autoFocus
|
||||||
|
placeholder="Введите значение..."
|
||||||
|
className="w-full bg-dark-700 border border-accent-500 rounded-xl px-4 py-3 text-sm text-dark-100 focus:outline-none focus:ring-2 focus:ring-accent-500/30 font-mono resize-none min-h-[100px]"
|
||||||
|
/>
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<span className="text-xs text-dark-500">Ctrl+Enter для сохранения</span>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
onClick={handleCancel}
|
||||||
|
className="px-3 py-1.5 rounded-lg bg-dark-600 text-dark-300 hover:bg-dark-500 transition-colors text-sm"
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleSave}
|
||||||
|
className="px-3 py-1.5 rounded-lg bg-accent-500 text-white hover:bg-accent-600 transition-colors text-sm flex items-center gap-1.5"
|
||||||
|
>
|
||||||
|
<CheckIcon />
|
||||||
|
Сохранить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Editing mode - Regular input
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
|
ref={inputRef}
|
||||||
type={setting.type === 'int' || setting.type === 'float' ? 'number' : 'text'}
|
type={setting.type === 'int' || setting.type === 'float' ? 'number' : 'text'}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={(e) => setValue(e.target.value)}
|
||||||
@@ -58,17 +134,20 @@ export function SettingInput({ setting, onUpdate, disabled }: SettingInputProps)
|
|||||||
if (e.key === 'Escape') handleCancel()
|
if (e.key === 'Escape') handleCancel()
|
||||||
}}
|
}}
|
||||||
autoFocus
|
autoFocus
|
||||||
className="bg-dark-700 border border-accent-500 rounded-lg px-3 py-1.5 text-sm text-dark-100 focus:outline-none w-32"
|
placeholder="Введите значение..."
|
||||||
|
className="bg-dark-700 border border-accent-500 rounded-lg px-3 py-2 text-sm text-dark-100 focus:outline-none focus:ring-2 focus:ring-accent-500/30 w-48 sm:w-56"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
className="p-1.5 rounded-lg bg-accent-500 text-white hover:bg-accent-600 transition-colors"
|
className="p-2 rounded-lg bg-accent-500 text-white hover:bg-accent-600 transition-colors"
|
||||||
|
title="Сохранить (Enter)"
|
||||||
>
|
>
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
className="p-1.5 rounded-lg bg-dark-600 text-dark-300 hover:bg-dark-500 transition-colors"
|
className="p-2 rounded-lg bg-dark-600 text-dark-300 hover:bg-dark-500 transition-colors"
|
||||||
|
title="Отмена (Esc)"
|
||||||
>
|
>
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</button>
|
</button>
|
||||||
@@ -76,14 +155,38 @@ export function SettingInput({ setting, onUpdate, disabled }: SettingInputProps)
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display mode
|
// Display mode - Long value preview
|
||||||
|
if (needsTextarea) {
|
||||||
|
const displayValue = currentValue || '-'
|
||||||
|
const previewValue = displayValue.length > 60 ? displayValue.slice(0, 60) + '...' : displayValue
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={handleStart}
|
onClick={handleStart}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="bg-dark-700 border border-dark-600 rounded-lg px-3 py-1.5 text-sm text-dark-200 hover:border-dark-500 transition-colors disabled:opacity-50 min-w-[80px] text-left font-mono truncate max-w-[150px]"
|
className="w-full bg-dark-700/50 border border-dark-600 rounded-xl px-4 py-3 text-sm text-dark-200 hover:border-dark-500 hover:bg-dark-700 transition-colors disabled:opacity-50 text-left font-mono group"
|
||||||
>
|
>
|
||||||
{String(setting.current ?? '-')}
|
<div className="flex items-start justify-between gap-2">
|
||||||
|
<span className="break-all line-clamp-2 flex-1">{previewValue}</span>
|
||||||
|
<span className="text-dark-500 group-hover:text-accent-400 transition-colors flex-shrink-0">
|
||||||
|
<EditIcon />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display mode - Short value
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={handleStart}
|
||||||
|
disabled={disabled}
|
||||||
|
className="bg-dark-700 border border-dark-600 rounded-lg px-3 py-2 text-sm text-dark-200 hover:border-dark-500 hover:bg-dark-600 transition-colors disabled:opacity-50 min-w-[100px] text-left font-mono truncate max-w-[200px] flex items-center gap-2 group"
|
||||||
|
>
|
||||||
|
<span className="truncate flex-1">{currentValue || '-'}</span>
|
||||||
|
<span className="text-dark-500 group-hover:text-accent-400 transition-colors opacity-0 group-hover:opacity-100">
|
||||||
|
<EditIcon />
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,68 +30,93 @@ export function SettingRow({
|
|||||||
const displayName = t(`admin.settings.settingNames.${formattedKey}`, formattedKey)
|
const displayName = t(`admin.settings.settingNames.${formattedKey}`, formattedKey)
|
||||||
const description = setting.hint?.description ? stripHtml(setting.hint.description) : null
|
const description = setting.hint?.description ? stripHtml(setting.hint.description) : null
|
||||||
|
|
||||||
|
// Check if this is a long/complex value
|
||||||
|
const isLongValue = (() => {
|
||||||
|
const val = String(setting.current ?? '')
|
||||||
|
const key = setting.key.toLowerCase()
|
||||||
return (
|
return (
|
||||||
<div className="group p-4 rounded-xl bg-dark-800/30 border border-dark-700/30 hover:border-dark-600/50 transition-all">
|
val.length > 50 ||
|
||||||
{/* Top row - name and badge */}
|
val.includes('\n') ||
|
||||||
<div className="flex items-start justify-between gap-3 mb-2">
|
val.startsWith('[') ||
|
||||||
|
val.startsWith('{') ||
|
||||||
|
key.includes('_items') ||
|
||||||
|
key.includes('_config') ||
|
||||||
|
key.includes('_keywords') ||
|
||||||
|
key.includes('_template') ||
|
||||||
|
key.includes('_packages')
|
||||||
|
)
|
||||||
|
})()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="group p-4 sm:p-5 rounded-2xl bg-dark-800/40 border border-dark-700/40 hover:border-dark-600/60 hover:bg-dark-800/60 transition-all">
|
||||||
|
{/* Header row - name, badges, favorite */}
|
||||||
|
<div className="flex items-start justify-between gap-3 mb-3">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
<span className="font-medium text-dark-100">{displayName}</span>
|
<h3 className="font-semibold text-dark-100 text-base">{displayName}</h3>
|
||||||
{setting.has_override && (
|
{setting.has_override && (
|
||||||
<span className="px-1.5 py-0.5 text-xs rounded bg-warning-500/20 text-warning-400">
|
<span className="px-2 py-0.5 text-xs rounded-full bg-warning-500/20 text-warning-400 font-medium">
|
||||||
{t('admin.settings.modified')}
|
{t('admin.settings.modified')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{setting.read_only && (
|
||||||
|
<span className="px-2 py-0.5 text-xs rounded-full bg-dark-600/50 text-dark-400 font-medium flex items-center gap-1">
|
||||||
|
<LockIcon />
|
||||||
|
{t('admin.settings.readOnly')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{description && (
|
{description && (
|
||||||
<p className="text-sm text-dark-400 mt-1 line-clamp-2">{description}</p>
|
<p className="text-sm text-dark-400 mt-1.5 leading-relaxed">{description}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Favorite button */}
|
{/* Favorite button */}
|
||||||
<button
|
<button
|
||||||
onClick={onToggleFavorite}
|
onClick={onToggleFavorite}
|
||||||
className={`p-1.5 rounded-lg transition-all flex-shrink-0 ${
|
className={`p-2 rounded-xl transition-all flex-shrink-0 ${
|
||||||
isFavorite
|
isFavorite
|
||||||
? 'text-warning-400 bg-warning-500/10'
|
? 'text-warning-400 bg-warning-500/15 hover:bg-warning-500/25'
|
||||||
: 'text-dark-500 hover:text-dark-300 opacity-0 group-hover:opacity-100'
|
: 'text-dark-500 hover:text-warning-400 hover:bg-dark-700/50 opacity-0 group-hover:opacity-100'
|
||||||
}`}
|
}`}
|
||||||
|
title={isFavorite ? 'Убрать из избранного' : 'В избранное'}
|
||||||
>
|
>
|
||||||
<StarIcon filled={isFavorite} />
|
<StarIcon filled={isFavorite} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bottom row - control */}
|
{/* Setting key (muted) */}
|
||||||
<div className="flex items-center justify-between gap-3 mt-3 pt-3 border-t border-dark-700/30">
|
<div className="mb-3">
|
||||||
<span className="text-xs text-dark-500 font-mono truncate max-w-[200px]">{setting.key}</span>
|
<code className="text-xs text-dark-500 font-mono bg-dark-900/50 px-2 py-1 rounded">
|
||||||
|
{setting.key}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 flex-shrink-0">
|
{/* Control section */}
|
||||||
{/* Setting control */}
|
<div className={`${isLongValue ? '' : 'flex items-center justify-between gap-3'} pt-3 border-t border-dark-700/30`}>
|
||||||
{setting.read_only ? (
|
{setting.read_only ? (
|
||||||
<div className="flex items-center gap-2 text-dark-400">
|
// Read-only display
|
||||||
<LockIcon />
|
<div className="flex items-center gap-2 text-dark-300 bg-dark-700/30 rounded-lg px-4 py-2.5">
|
||||||
<span className="font-mono text-sm max-w-[150px] truncate">{String(setting.current ?? '-')}</span>
|
<span className="font-mono text-sm break-all">{String(setting.current ?? '-')}</span>
|
||||||
</div>
|
</div>
|
||||||
) : setting.type === 'bool' ? (
|
) : setting.type === 'bool' ? (
|
||||||
|
// Boolean toggle
|
||||||
|
<div className="flex items-center justify-between gap-3">
|
||||||
|
<span className="text-sm text-dark-400">
|
||||||
|
{setting.current === true || setting.current === 'true' ? 'Включено' : 'Выключено'}
|
||||||
|
</span>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
<Toggle
|
<Toggle
|
||||||
checked={setting.current === true || setting.current === 'true'}
|
checked={setting.current === true || setting.current === 'true'}
|
||||||
onChange={() => onUpdate(setting.current === true || setting.current === 'true' ? 'false' : 'true')}
|
onChange={() => onUpdate(setting.current === true || setting.current === 'true' ? 'false' : 'true')}
|
||||||
disabled={isUpdating}
|
disabled={isUpdating}
|
||||||
/>
|
/>
|
||||||
) : (
|
{/* Reset button for boolean */}
|
||||||
<SettingInput
|
{setting.has_override && (
|
||||||
setting={setting}
|
|
||||||
onUpdate={onUpdate}
|
|
||||||
disabled={isUpdating}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Reset button */}
|
|
||||||
{setting.has_override && !setting.read_only && (
|
|
||||||
<button
|
<button
|
||||||
onClick={onReset}
|
onClick={onReset}
|
||||||
disabled={isResetting}
|
disabled={isResetting}
|
||||||
className="p-1.5 rounded-lg text-dark-400 hover:text-dark-200 hover:bg-dark-700 transition-colors"
|
className="p-2 rounded-lg text-dark-400 hover:text-dark-200 hover:bg-dark-700 transition-colors disabled:opacity-50"
|
||||||
title={t('admin.settings.reset')}
|
title={t('admin.settings.reset')}
|
||||||
>
|
>
|
||||||
<RefreshIcon />
|
<RefreshIcon />
|
||||||
@@ -99,6 +124,43 @@ export function SettingRow({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
// Input field
|
||||||
|
<div className={`${isLongValue ? 'w-full' : 'flex items-center gap-2 flex-1 justify-end'}`}>
|
||||||
|
<SettingInput
|
||||||
|
setting={setting}
|
||||||
|
onUpdate={onUpdate}
|
||||||
|
disabled={isUpdating}
|
||||||
|
/>
|
||||||
|
{/* Reset button for non-long values */}
|
||||||
|
{!isLongValue && setting.has_override && (
|
||||||
|
<button
|
||||||
|
onClick={onReset}
|
||||||
|
disabled={isResetting}
|
||||||
|
className="p-2 rounded-lg text-dark-400 hover:text-dark-200 hover:bg-dark-700 transition-colors disabled:opacity-50 flex-shrink-0"
|
||||||
|
title={t('admin.settings.reset')}
|
||||||
|
>
|
||||||
|
<RefreshIcon />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Reset button for long values - shown below */}
|
||||||
|
{isLongValue && setting.has_override && !setting.read_only && setting.type !== 'bool' && (
|
||||||
|
<div className="mt-3 flex justify-end">
|
||||||
|
<button
|
||||||
|
onClick={onReset}
|
||||||
|
disabled={isResetting}
|
||||||
|
className="px-3 py-1.5 rounded-lg text-dark-400 hover:text-dark-200 hover:bg-dark-700 transition-colors disabled:opacity-50 text-sm flex items-center gap-1.5"
|
||||||
|
title={t('admin.settings.reset')}
|
||||||
|
>
|
||||||
|
<RefreshIcon />
|
||||||
|
<span>Сбросить</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export function SettingsTab({
|
|||||||
<p className="text-dark-400">{t('admin.settings.noSettings')}</p>
|
<p className="text-dark-400">{t('admin.settings.noSettings')}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
{filteredSettings.map((setting) => (
|
{filteredSettings.map((setting) => (
|
||||||
<SettingRow
|
<SettingRow
|
||||||
key={setting.key}
|
key={setting.key}
|
||||||
@@ -114,7 +114,7 @@ export function SettingsTab({
|
|||||||
{/* Accordion content */}
|
{/* Accordion content */}
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<div className="p-4 pt-0 border-t border-dark-700/30">
|
<div className="p-4 pt-0 border-t border-dark-700/30">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4 pt-4">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 pt-4">
|
||||||
{cat.settings.map((setting) => (
|
{cat.settings.map((setting) => (
|
||||||
<SettingRow
|
<SettingRow
|
||||||
key={setting.key}
|
key={setting.key}
|
||||||
|
|||||||
@@ -83,3 +83,9 @@ export const MenuIcon = () => (
|
|||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const EditIcon = () => (
|
||||||
|
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|||||||
@@ -1191,7 +1191,7 @@
|
|||||||
"POSTGRES": "PostgreSQL",
|
"POSTGRES": "PostgreSQL",
|
||||||
"SQLITE": "SQLite",
|
"SQLITE": "SQLite",
|
||||||
"REDIS": "Redis",
|
"REDIS": "Redis",
|
||||||
"CORE": "Ядро",
|
"CORE": "Бот",
|
||||||
"REMNAWAVE": "RemnaWave",
|
"REMNAWAVE": "RemnaWave",
|
||||||
"SERVER_STATUS": "Статус сервера",
|
"SERVER_STATUS": "Статус сервера",
|
||||||
"MONITORING": "Мониторинг",
|
"MONITORING": "Мониторинг",
|
||||||
|
|||||||
Reference in New Issue
Block a user