feat: enhance admin catalog UI and functionality
Some checks failed
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 7s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Failing after 27s
Some checks failed
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 7s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Failing after 27s
- Added new CSS styles for admin catalog sections, improving layout and responsiveness. - Implemented icon support for catalog section titles, enhancing visual clarity. - Refactored loading and error states for better user experience in the CatalogsTab and HierarchyTab components. - Updated AdminCatalogsPage to utilize new styles and improve tab navigation. - Enhanced accessibility with appropriate ARIA roles and attributes for better usability.
This commit is contained in:
parent
8b86021293
commit
657f73d2c5
|
|
@ -1411,6 +1411,20 @@ button.capture-shell__nav-item {
|
|||
.nav-item span {
|
||||
font-size: 9.5px;
|
||||
}
|
||||
|
||||
.admin-page-subtabs {
|
||||
margin-left: calc(-1 * max(12px, env(safe-area-inset-left, 0px)));
|
||||
margin-right: calc(-1 * max(12px, env(safe-area-inset-right, 0px)));
|
||||
padding-left: max(12px, env(safe-area-inset-left, 0px));
|
||||
padding-right: max(12px, env(safe-area-inset-right, 0px));
|
||||
margin-bottom: 14px;
|
||||
border-bottom: none;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.admin-catalog-section {
|
||||
padding: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Trainingsplanung: kompakte Segmente (Gruppe / Verein) */
|
||||
|
|
@ -1461,6 +1475,180 @@ button.capture-shell__nav-item {
|
|||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
/* Admin-Kataloge: Seite „Stammdaten“ — viele Unter-Tabs, Chip-Scroll */
|
||||
.admin-page-subtabs {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 6px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
margin-bottom: 18px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
scroll-snap-type: x proximity;
|
||||
}
|
||||
.admin-page-subtabs::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.admin-page-subtabs__btn {
|
||||
flex: 0 0 auto;
|
||||
scroll-snap-align: start;
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
padding: 8px 13px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border2);
|
||||
background: var(--surface2);
|
||||
color: var(--text2);
|
||||
transition: background 0.12s, color 0.12s, border-color 0.12s;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.admin-page-subtabs__btn:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--text1);
|
||||
}
|
||||
.admin-page-subtabs__btn--active {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
.admin-page-subtabs__btn--active:hover {
|
||||
color: #fff;
|
||||
background: color-mix(in srgb, var(--accent) 92%, #000);
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.admin-page-subtabs__btn {
|
||||
font-size: 13px;
|
||||
padding: 9px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Admin Hierarchy & Catalog Section (Komponenten) */
|
||||
.admin-hierarchy-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.admin-hierarchy-pane {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 14px 16px;
|
||||
background: var(--surface);
|
||||
}
|
||||
.admin-hierarchy-pane__title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
font-size: 1.15rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.admin-hierarchy-back {
|
||||
margin-bottom: 14px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.admin-catalog-stack {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.admin-catalog-stack {
|
||||
gap: 1.25rem;
|
||||
}
|
||||
}
|
||||
.admin-catalog-section {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.admin-catalog-section {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
.admin-catalog-section__head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.admin-catalog-section__title {
|
||||
margin: 0;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 700;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.admin-catalog-section__icon {
|
||||
flex-shrink: 0;
|
||||
color: var(--accent-dark);
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.admin-catalog-section__icon {
|
||||
color: var(--accent);
|
||||
}
|
||||
}
|
||||
.admin-catalog-inline-form {
|
||||
margin-bottom: 16px;
|
||||
padding: 14px;
|
||||
background: var(--surface2);
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.admin-catalog-inline-form h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.admin-catalog-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
.admin-catalog-item {
|
||||
padding: 12px;
|
||||
background: var(--surface2);
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.admin-catalog-item__name-row {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.admin-catalog-meta {
|
||||
margin-left: 10px;
|
||||
color: var(--text3);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.admin-catalog-desc {
|
||||
color: var(--text2);
|
||||
font-size: 0.875rem;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.admin-catalog-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.admin-catalog-empty {
|
||||
text-align: center;
|
||||
color: var(--text3);
|
||||
padding: 1.25rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Ausklappbare Kontext-Hilfe (Filterzeile Planung) */
|
||||
.planning-filter-help {
|
||||
flex: 1 1 100%;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,23 @@
|
|||
import React, { useState } from 'react'
|
||||
import { Target, Tags, Dumbbell } from 'lucide-react'
|
||||
import { api } from '../../utils/api'
|
||||
|
||||
function CatalogsTab({ targetGroups, skillCategories, trainingCharacters, loading, error, onUpdate }) {
|
||||
if (loading) {
|
||||
return <div style={{ textAlign: 'center', padding: '40px' }}><div className="spinner"></div></div>
|
||||
return (
|
||||
<div className="empty-state" style={{ padding: '2.5rem' }}>
|
||||
<div className="spinner" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: '24px' }}>
|
||||
{error && <div style={{ color: 'var(--danger)', padding: '16px', background: 'var(--surface)', borderRadius: '8px' }}>{error}</div>}
|
||||
<div className="admin-catalog-stack">
|
||||
{error && <div className="admin-matrix-alert">{error}</div>}
|
||||
|
||||
<CatalogSection
|
||||
title="Zielgruppen"
|
||||
icon="🎯"
|
||||
Icon={Target}
|
||||
items={targetGroups}
|
||||
onUpdate={onUpdate}
|
||||
createFn={api.createTargetGroup}
|
||||
|
|
@ -28,7 +33,7 @@ function CatalogsTab({ targetGroups, skillCategories, trainingCharacters, loadin
|
|||
|
||||
<CatalogSection
|
||||
title="Fähigkeitskategorien"
|
||||
icon="⚡"
|
||||
Icon={Tags}
|
||||
items={skillCategories}
|
||||
onUpdate={onUpdate}
|
||||
createFn={api.createSkillCategory}
|
||||
|
|
@ -42,7 +47,7 @@ function CatalogsTab({ targetGroups, skillCategories, trainingCharacters, loadin
|
|||
|
||||
<CatalogSection
|
||||
title="Trainingscharakter"
|
||||
icon="💪"
|
||||
Icon={Dumbbell}
|
||||
items={trainingCharacters}
|
||||
onUpdate={onUpdate}
|
||||
createFn={api.createTrainingCharacter}
|
||||
|
|
@ -57,27 +62,27 @@ function CatalogsTab({ targetGroups, skillCategories, trainingCharacters, loadin
|
|||
)
|
||||
}
|
||||
|
||||
function CatalogSection({ title, icon, items, onUpdate, createFn, updateFn, deleteFn, fields }) {
|
||||
function CatalogSection({ title, Icon, items, onUpdate, createFn, updateFn, deleteFn, fields }) {
|
||||
const [creating, setCreating] = useState(false)
|
||||
const [editing, setEditing] = useState(null)
|
||||
const [form, setForm] = useState({})
|
||||
|
||||
function startCreate() {
|
||||
const emptyForm = {}
|
||||
fields.forEach(f => { emptyForm[f.key] = '' })
|
||||
fields.forEach((f) => { emptyForm[f.key] = '' })
|
||||
setForm(emptyForm)
|
||||
setCreating(true)
|
||||
}
|
||||
|
||||
function startEdit(item) {
|
||||
const editForm = {}
|
||||
fields.forEach(f => { editForm[f.key] = item[f.key] || '' })
|
||||
fields.forEach((f) => { editForm[f.key] = item[f.key] || '' })
|
||||
setEditing(item.id)
|
||||
setForm(editForm)
|
||||
}
|
||||
|
||||
async function handleCreate() {
|
||||
const required = fields.filter(f => f.required)
|
||||
const required = fields.filter((f) => f.required)
|
||||
for (const field of required) {
|
||||
if (!form[field.key]) {
|
||||
alert(`${field.label} ist erforderlich`)
|
||||
|
|
@ -116,75 +121,116 @@ function CatalogSection({ title, icon, items, onUpdate, createFn, updateFn, dele
|
|||
}
|
||||
|
||||
return (
|
||||
<div style={{ background: 'var(--surface)', borderRadius: '12px', padding: '20px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
|
||||
<h3 style={{ margin: 0 }}>{icon} {title}</h3>
|
||||
<button className="btn btn-primary" onClick={startCreate}>+ Neu</button>
|
||||
<div className="admin-catalog-section">
|
||||
<div className="admin-catalog-section__head">
|
||||
<h3 className="admin-catalog-section__title">
|
||||
{Icon ? (
|
||||
<Icon className="admin-catalog-section__icon" size={20} strokeWidth={2} aria-hidden />
|
||||
) : null}
|
||||
{title}
|
||||
</h3>
|
||||
<button type="button" className="btn btn-primary btn-small" onClick={startCreate}>
|
||||
+ Neu
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{creating && (
|
||||
<div style={{ marginBottom: '20px', padding: '16px', background: 'var(--surface2)', borderRadius: '8px' }}>
|
||||
<h4 style={{ marginTop: 0 }}>Neu erstellen</h4>
|
||||
{fields.map(field => (
|
||||
<div className="admin-catalog-inline-form">
|
||||
<h4>Neu erstellen</h4>
|
||||
{fields.map((field) => (
|
||||
<div key={field.key} className="form-row">
|
||||
<label className="form-label">{field.label} {field.required && '*'}</label>
|
||||
<label className="form-label">
|
||||
{field.label} {field.required && '*'}
|
||||
</label>
|
||||
{field.type === 'textarea' ? (
|
||||
<textarea className="form-input" value={form[field.key] || ''} onChange={e => setForm({ ...form, [field.key]: e.target.value })} rows={3} />
|
||||
<textarea
|
||||
className="form-input"
|
||||
value={form[field.key] || ''}
|
||||
onChange={(e) => setForm({ ...form, [field.key]: e.target.value })}
|
||||
rows={3}
|
||||
/>
|
||||
) : (
|
||||
<input className="form-input" type={field.type} value={form[field.key] || ''} onChange={e => setForm({ ...form, [field.key]: e.target.value })} />
|
||||
<input
|
||||
className="form-input"
|
||||
type={field.type}
|
||||
value={form[field.key] || ''}
|
||||
onChange={(e) => setForm({ ...form, [field.key]: e.target.value })}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div style={{ display: 'flex', gap: '8px', marginTop: '12px' }}>
|
||||
<button className="btn btn-primary" onClick={handleCreate}>Erstellen</button>
|
||||
<button className="btn" onClick={() => setCreating(false)}>Abbrechen</button>
|
||||
<div className="admin-catalog-actions">
|
||||
<button type="button" className="btn btn-primary" onClick={handleCreate}>
|
||||
Erstellen
|
||||
</button>
|
||||
<button type="button" className="btn btn-secondary" onClick={() => setCreating(false)}>
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'grid', gap: '12px' }}>
|
||||
{items.map(item => (
|
||||
<div key={item.id} style={{ padding: '12px', background: 'var(--surface2)', borderRadius: '8px' }}>
|
||||
<div className="admin-catalog-list">
|
||||
{items.map((item) => (
|
||||
<div key={item.id} className="admin-catalog-item">
|
||||
{editing === item.id ? (
|
||||
<div>
|
||||
{fields.map(field => (
|
||||
{fields.map((field) => (
|
||||
<div key={field.key} className="form-row">
|
||||
<label className="form-label">{field.label}</label>
|
||||
{field.type === 'textarea' ? (
|
||||
<textarea className="form-input" value={form[field.key] || ''} onChange={e => setForm({ ...form, [field.key]: e.target.value })} rows={3} />
|
||||
<textarea
|
||||
className="form-input"
|
||||
value={form[field.key] || ''}
|
||||
onChange={(e) => setForm({ ...form, [field.key]: e.target.value })}
|
||||
rows={3}
|
||||
/>
|
||||
) : (
|
||||
<input className="form-input" type={field.type} value={form[field.key] || ''} onChange={e => setForm({ ...form, [field.key]: e.target.value })} />
|
||||
<input
|
||||
className="form-input"
|
||||
type={field.type}
|
||||
value={form[field.key] || ''}
|
||||
onChange={(e) => setForm({ ...form, [field.key]: e.target.value })}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div style={{ display: 'flex', gap: '8px', marginTop: '12px' }}>
|
||||
<button className="btn btn-primary" onClick={() => handleUpdate(item.id)}>Speichern</button>
|
||||
<button className="btn" onClick={() => setEditing(null)}>Abbrechen</button>
|
||||
<div className="admin-catalog-actions">
|
||||
<button type="button" className="btn btn-primary" onClick={() => handleUpdate(item.id)}>
|
||||
Speichern
|
||||
</button>
|
||||
<button type="button" className="btn btn-secondary" onClick={() => setEditing(null)}>
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div style={{ marginBottom: '8px' }}>
|
||||
<div className="admin-catalog-item__name-row">
|
||||
<strong>{item.name}</strong>
|
||||
{item.min_age !== null && item.max_age !== null && (
|
||||
<span style={{ marginLeft: '12px', color: 'var(--text3)', fontSize: '14px' }}>
|
||||
{item.min_age != null && item.max_age != null && (
|
||||
<span className="admin-catalog-meta">
|
||||
Alter: {item.min_age}-{item.max_age}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{item.description && <p style={{ color: 'var(--text2)', fontSize: '14px', margin: '8px 0' }}>{item.description}</p>}
|
||||
<div style={{ display: 'flex', gap: '8px', marginTop: '8px' }}>
|
||||
<button className="btn" onClick={() => startEdit(item)}>Bearbeiten</button>
|
||||
<button className="btn" onClick={() => handleDelete(item.id, item.name)}>Löschen</button>
|
||||
{item.description ? (
|
||||
<p className="admin-catalog-desc">{item.description}</p>
|
||||
) : null}
|
||||
<div className="admin-catalog-actions">
|
||||
<button type="button" className="btn btn-secondary btn-small" onClick={() => startEdit(item)}>
|
||||
Bearbeiten
|
||||
</button>
|
||||
<button type="button" className="btn btn-danger btn-small" onClick={() => handleDelete(item.id, item.name)}>
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{items.length === 0 && !creating && (
|
||||
<div style={{ textAlign: 'center', color: 'var(--text3)', padding: '20px' }}>
|
||||
Noch keine Einträge vorhanden
|
||||
</div>
|
||||
<div className="admin-catalog-empty">Noch keine Einträge vorhanden</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,29 +1,24 @@
|
|||
import React from 'react'
|
||||
import { ArrowLeft } from 'lucide-react'
|
||||
import FocusAreaNode from './FocusAreaNode'
|
||||
import DetailPanel from './DetailPanel'
|
||||
|
||||
function HierarchyTab({ hierarchy, expandedNodes, selectedItem, loading, error, onToggleNode, onSelectItem, onUpdate }) {
|
||||
if (loading && hierarchy.length === 0) {
|
||||
return <div style={{ textAlign: 'center', padding: '40px' }}><div className="spinner"></div></div>
|
||||
return (
|
||||
<div className="empty-state" style={{ padding: '2.5rem' }}>
|
||||
<div className="spinner" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="admin-hierarchy-container">
|
||||
{/* Tree View */}
|
||||
<div
|
||||
className="admin-tree-view"
|
||||
style={{
|
||||
display: selectedItem ? 'none' : 'block',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: '12px',
|
||||
padding: '16px',
|
||||
background: 'var(--surface)'
|
||||
}}
|
||||
>
|
||||
<h2 style={{ marginTop: 0 }}>Katalog-Hierarchie</h2>
|
||||
{error && <div style={{ color: 'var(--danger)', marginBottom: '16px' }}>{error}</div>}
|
||||
<div className="admin-hierarchy-container admin-hierarchy-layout">
|
||||
<div className="admin-hierarchy-pane admin-hierarchy-pane--tree" hidden={!!selectedItem}>
|
||||
<h2 className="admin-hierarchy-pane__title">Katalog-Hierarchie</h2>
|
||||
{error && <div className="admin-matrix-alert">{error}</div>}
|
||||
|
||||
{hierarchy.map(fa => (
|
||||
{hierarchy.map((fa) => (
|
||||
<FocusAreaNode
|
||||
key={fa.id}
|
||||
focusArea={fa}
|
||||
|
|
@ -36,22 +31,15 @@ function HierarchyTab({ hierarchy, expandedNodes, selectedItem, loading, error,
|
|||
))}
|
||||
</div>
|
||||
|
||||
{/* Detail Panel */}
|
||||
{selectedItem && (
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: '12px',
|
||||
padding: '20px',
|
||||
background: 'var(--surface)'
|
||||
}}
|
||||
>
|
||||
<div className="admin-hierarchy-pane admin-hierarchy-pane--detail">
|
||||
<button
|
||||
className="btn admin-back-button"
|
||||
type="button"
|
||||
className="btn btn-secondary btn-small admin-hierarchy-back"
|
||||
onClick={() => onSelectItem(null)}
|
||||
style={{ marginBottom: '16px' }}
|
||||
>
|
||||
← Zurück zur Übersicht
|
||||
<ArrowLeft size={16} strokeWidth={2} aria-hidden />
|
||||
Zurück zur Übersicht
|
||||
</button>
|
||||
<DetailPanel item={selectedItem} onUpdate={onUpdate} focusAreas={hierarchy} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -316,10 +316,9 @@ export default function AdminCatalogsPage() {
|
|||
<div className="app-page">
|
||||
<AdminPageNav />
|
||||
|
||||
<h1 style={{ marginBottom: '24px' }}>Stammdaten-Kataloge</h1>
|
||||
<h1 className="page-title">Stammdaten-Kataloge</h1>
|
||||
|
||||
{/* Tabs */}
|
||||
<div style={{ display: 'flex', gap: '8px', borderBottom: '2px solid var(--border)', marginBottom: '24px', overflowX: 'auto' }}>
|
||||
<div className="admin-page-subtabs" role="tablist" aria-label="Katalogbereiche">
|
||||
{[
|
||||
{ id: 'focus-areas', label: 'Fokusbereiche' },
|
||||
{ id: 'training-styles', label: 'Stilrichtungen' },
|
||||
|
|
@ -330,30 +329,24 @@ export default function AdminCatalogsPage() {
|
|||
{ id: 'training-characters', label: 'Trainingscharakter' },
|
||||
{ id: 'skill-categories', label: 'Fähigkeitskategorien' },
|
||||
{ id: 'trainer-assignments', label: 'Trainer-Zuordnungen' }
|
||||
].map(tab => (
|
||||
].map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeTab === tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className="btn"
|
||||
style={{
|
||||
borderBottom: activeTab === tab.id ? '3px solid var(--accent)' : 'none',
|
||||
borderRadius: 0,
|
||||
fontWeight: activeTab === tab.id ? 600 : 400,
|
||||
color: activeTab === tab.id ? 'var(--accent)' : 'var(--text2)',
|
||||
padding: '12px 16px',
|
||||
whiteSpace: 'nowrap'
|
||||
}}
|
||||
className={
|
||||
'admin-page-subtabs__btn' +
|
||||
(activeTab === tab.id ? ' admin-page-subtabs__btn--active' : '')
|
||||
}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div style={{ padding: '12px', background: 'var(--danger)', color: 'white', borderRadius: '8px', marginBottom: '16px' }}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
{error && <div className="admin-matrix-alert">{error}</div>}
|
||||
|
||||
{loading ? (
|
||||
<div className="spinner" />
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user