Add helper functions for formatting settings and cleaning HTML in AdminSettings

- Introduced `formatSettingName` to convert setting names from Snake_Case/CamelCase to a readable format.
- Added `stripHtml` function to remove HTML tags from setting descriptions.
- Updated rendering logic in `renderSettingRow` to utilize the new helper functions for improved display of setting names and descriptions.
- Enhanced layout for settings display with better spacing and organization.
This commit is contained in:
PEDZEO
2026-01-21 03:21:45 +03:00
parent 710bc32111
commit 0679905c51

View File

@@ -104,6 +104,38 @@ const MenuIcon = () => (
</svg> </svg>
) )
// ============ HELPER FUNCTIONS ============
// Форматирование названия настройки (Snake_Case / CamelCase -> читаемый текст)
function formatSettingName(name: string): string {
if (!name) return ''
// Убираем префиксы типа "Miniapp ", "Happ " и т.д.
let formatted = name
// CamelCase -> пробелы
.replace(/([a-z])([A-Z])/g, '$1 $2')
// snake_case -> пробелы
.replace(/_/g, ' ')
// Убираем лишние пробелы
.replace(/\s+/g, ' ')
.trim()
// Делаем первую букву заглавной, остальные как есть
return formatted.charAt(0).toUpperCase() + formatted.slice(1)
}
// Очистка HTML тегов из описания
function stripHtml(html: string): string {
if (!html) return ''
return html
.replace(/<[^>]*>/g, '')
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.trim()
}
// ============ SIDEBAR MENU ITEMS ============ // ============ SIDEBAR MENU ITEMS ============
interface MenuItem { interface MenuItem {
id: string id: string
@@ -379,31 +411,34 @@ export default function AdminSettings() {
const renderSettingRow = (setting: SettingDefinition) => { const renderSettingRow = (setting: SettingDefinition) => {
const isFav = isFavorite(setting.key) const isFav = isFavorite(setting.key)
const displayName = formatSettingName(setting.name || setting.key)
const description = setting.hint?.description ? stripHtml(setting.hint.description) : null
return ( return (
<div <div
key={setting.key} key={setting.key}
className="group flex items-center justify-between p-4 rounded-xl bg-dark-800/30 border border-dark-700/30 hover:border-dark-600/50 transition-all" className="group p-4 rounded-xl bg-dark-800/30 border border-dark-700/30 hover:border-dark-600/50 transition-all"
> >
<div className="flex-1 min-w-0 mr-4"> {/* Top row - name and badge */}
<div className="flex items-center gap-2"> <div className="flex items-start justify-between gap-3 mb-2">
<span className="font-medium text-dark-100 truncate">{setting.name || setting.key}</span> <div className="flex-1 min-w-0">
{setting.has_override && ( <div className="flex items-center gap-2 flex-wrap">
<span className="px-1.5 py-0.5 text-xs rounded bg-warning-500/20 text-warning-400"> <span className="font-medium text-dark-100">{displayName}</span>
{t('admin.settings.modified')} {setting.has_override && (
</span> <span className="px-1.5 py-0.5 text-xs rounded bg-warning-500/20 text-warning-400">
{t('admin.settings.modified')}
</span>
)}
</div>
{description && (
<p className="text-sm text-dark-400 mt-1 line-clamp-2">{description}</p>
)} )}
</div> </div>
{setting.hint?.description && (
<p className="text-sm text-dark-400 truncate mt-0.5">{setting.hint.description}</p>
)}
</div>
<div className="flex items-center gap-2 flex-shrink-0">
{/* Favorite button */} {/* Favorite button */}
<button <button
onClick={() => toggleFavorite(setting.key)} onClick={() => toggleFavorite(setting.key)}
className={`p-1.5 rounded-lg transition-all ${ className={`p-1.5 rounded-lg transition-all flex-shrink-0 ${
isFav isFav
? 'text-warning-400 bg-warning-500/10' ? 'text-warning-400 bg-warning-500/10'
: 'text-dark-500 hover:text-dark-300 opacity-0 group-hover:opacity-100' : 'text-dark-500 hover:text-dark-300 opacity-0 group-hover:opacity-100'
@@ -411,41 +446,48 @@ export default function AdminSettings() {
> >
<StarIcon filled={isFav} /> <StarIcon filled={isFav} />
</button> </button>
</div>
{/* Setting control */} {/* Bottom row - control */}
{setting.read_only ? ( <div className="flex items-center justify-between gap-3 mt-3 pt-3 border-t border-dark-700/30">
<div className="flex items-center gap-2 text-dark-400"> <span className="text-xs text-dark-500 font-mono truncate max-w-[200px]">{setting.key}</span>
<LockIcon />
<span className="font-mono text-sm">{String(setting.current ?? '-')}</span>
</div>
) : setting.type === 'bool' ? (
renderToggle(
setting.current === true || setting.current === 'true',
() => updateSettingMutation.mutate({
key: setting.key,
value: setting.current === true || setting.current === 'true' ? 'false' : 'true'
}),
updateSettingMutation.isPending
)
) : (
<SettingInput
setting={setting}
onUpdate={(value) => updateSettingMutation.mutate({ key: setting.key, value })}
disabled={updateSettingMutation.isPending}
/>
)}
{/* Reset button */} <div className="flex items-center gap-2 flex-shrink-0">
{setting.has_override && !setting.read_only && ( {/* Setting control */}
<button {setting.read_only ? (
onClick={() => resetSettingMutation.mutate(setting.key)} <div className="flex items-center gap-2 text-dark-400">
disabled={resetSettingMutation.isPending} <LockIcon />
className="p-1.5 rounded-lg text-dark-400 hover:text-dark-200 hover:bg-dark-700 transition-colors" <span className="font-mono text-sm max-w-[150px] truncate">{String(setting.current ?? '-')}</span>
title={t('admin.settings.reset')} </div>
> ) : setting.type === 'bool' ? (
<RefreshIcon /> renderToggle(
</button> setting.current === true || setting.current === 'true',
)} () => updateSettingMutation.mutate({
key: setting.key,
value: setting.current === true || setting.current === 'true' ? 'false' : 'true'
}),
updateSettingMutation.isPending
)
) : (
<SettingInput
setting={setting}
onUpdate={(value) => updateSettingMutation.mutate({ key: setting.key, value })}
disabled={updateSettingMutation.isPending}
/>
)}
{/* Reset button */}
{setting.has_override && !setting.read_only && (
<button
onClick={() => resetSettingMutation.mutate(setting.key)}
disabled={resetSettingMutation.isPending}
className="p-1.5 rounded-lg text-dark-400 hover:text-dark-200 hover:bg-dark-700 transition-colors"
title={t('admin.settings.reset')}
>
<RefreshIcon />
</button>
)}
</div>
</div> </div>
</div> </div>
) )
@@ -800,7 +842,9 @@ export default function AdminSettings() {
<p className="text-dark-500 text-sm mt-1">{t('admin.settings.favoritesHint')}</p> <p className="text-dark-500 text-sm mt-1">{t('admin.settings.favoritesHint')}</p>
</div> </div>
) : ( ) : (
favoriteSettings.map(renderSettingRow) <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4">
{favoriteSettings.map(renderSettingRow)}
</div>
)} )}
</div> </div>
) )
@@ -809,15 +853,15 @@ export default function AdminSettings() {
<div className="space-y-4"> <div className="space-y-4">
{/* Sub-categories navigation */} {/* Sub-categories navigation */}
{currentCategories.length > 1 && !activeSubCategory && ( {currentCategories.length > 1 && !activeSubCategory && (
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3 mb-6"> <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3 mb-6">
{currentCategories.map((cat) => ( {currentCategories.map((cat) => (
<button <button
key={cat.key} key={cat.key}
onClick={() => setActiveSubCategory(cat.key)} onClick={() => setActiveSubCategory(cat.key)}
className="p-4 rounded-xl bg-dark-800/50 border border-dark-700/50 hover:border-dark-600 transition-all text-left" className="p-4 rounded-xl bg-dark-800/50 border border-dark-700/50 hover:border-dark-600 hover:bg-dark-800/70 transition-all text-left"
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="font-medium text-dark-200">{cat.label}</span> <span className="font-medium text-dark-200 truncate">{cat.label}</span>
<ChevronRightIcon /> <ChevronRightIcon />
</div> </div>
<span className="text-sm text-dark-500">{cat.settings.length} {t('admin.settings.settingsCount')}</span> <span className="text-sm text-dark-500">{cat.settings.length} {t('admin.settings.settingsCount')}</span>
@@ -837,13 +881,15 @@ export default function AdminSettings() {
</button> </button>
)} )}
{/* Settings list */} {/* Settings grid/list */}
{filteredSettings.length === 0 ? ( {filteredSettings.length === 0 ? (
<div className="p-12 rounded-2xl bg-dark-800/30 border border-dark-700/30 text-center"> <div className="p-12 rounded-2xl bg-dark-800/30 border border-dark-700/30 text-center">
<p className="text-dark-400">{t('admin.settings.noSettings')}</p> <p className="text-dark-400">{t('admin.settings.noSettings')}</p>
</div> </div>
) : ( ) : (
filteredSettings.map(renderSettingRow) <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4">
{filteredSettings.map(renderSettingRow)}
</div>
)} )}
</div> </div>
) )