Panel admina: 16 zakladek jako podsciezki /admin/*
- aktywna zakladka wyznaczana z adresu (useParams), nie z useState - setTab() nawiguje zamiast zmieniac stan lokalny - sidebar admina: <button> -> <NavLink> - typ AdminTab zunifikowany z AdminTabKey z routes.ts - nieznana sekcja /admin/cokolwiek -> strona 404 - CSS .admin-nav rozszerzony o warianty <a> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+21
-17
@@ -3665,10 +3665,8 @@ function accountStorageKey(suffix: string, user: AuthUser | null): string {
|
|||||||
return `accountProfile:${user?.id ?? 'guest'}:${suffix}`;
|
return `accountProfile:${user?.id ?? 'guest'}:${suffix}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdminTab =
|
// Zakladki panelu maja odpowiedniki w sciezkach /admin/* - jedno zrodlo prawdy w routes.ts.
|
||||||
| 'dashboard' | 'listings' | 'users' | 'messages' | 'reports' | 'payments'
|
type AdminTab = AdminTabKey;
|
||||||
| 'stats' | 'settings' | 'moderation' | 'categories' | 'locations' | 'promotions' | 'forbiddenWords'
|
|
||||||
| 'leads' | 'campaigns' | 'mailConfig';
|
|
||||||
|
|
||||||
const ADMIN_TAB_META: Record<AdminTab, { title: string; subtitle: string }> = {
|
const ADMIN_TAB_META: Record<AdminTab, { title: string; subtitle: string }> = {
|
||||||
dashboard: { title: 'Panel administratora', subtitle: 'Zarządzaj ogłoszeniami i użytkownikami serwisu.' },
|
dashboard: { title: 'Panel administratora', subtitle: 'Zarządzaj ogłoszeniami i użytkownikami serwisu.' },
|
||||||
@@ -3848,7 +3846,14 @@ function AdminMessageModal({ user, onClose }: { user: AuthUser; onClose: () => v
|
|||||||
function AdminPage() {
|
function AdminPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const [tab, setTab] = useState<AdminTab>('dashboard');
|
// Aktywna zakladka wynika z adresu (/admin/leady), nie ze stanu komponentu -
|
||||||
|
// dzieki temu kazda sekcja panelu ma wlasny URL, dziala F5 i przycisk Wstecz.
|
||||||
|
const adminSegment = useParams()['*'] ?? '';
|
||||||
|
const tab: AdminTab = ADMIN_PATH_TO_TAB[adminSegment] ?? 'dashboard';
|
||||||
|
const isUnknownAdminSection = adminSegment !== '' && !(adminSegment in ADMIN_PATH_TO_TAB);
|
||||||
|
const setTab = useCallback((next: AdminTab) => {
|
||||||
|
navigate(adminTabPath(next));
|
||||||
|
}, [navigate]);
|
||||||
const [users, setUsers] = useState<AuthUser[]>([]);
|
const [users, setUsers] = useState<AuthUser[]>([]);
|
||||||
const [listings, setListings] = useState<AdminListing[]>([]);
|
const [listings, setListings] = useState<AdminListing[]>([]);
|
||||||
const [reports, setReports] = useState<AdminListingReport[]>([]);
|
const [reports, setReports] = useState<AdminListingReport[]>([]);
|
||||||
@@ -4193,6 +4198,10 @@ function AdminPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isUnknownAdminSection) {
|
||||||
|
return <NotFoundPage />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="admin-page admin-page-shell" aria-label="Panel administratora">
|
<section className="admin-page admin-page-shell" aria-label="Panel administratora">
|
||||||
<div className="admin-shell admin-shell-with-sidebar">
|
<div className="admin-shell admin-shell-with-sidebar">
|
||||||
@@ -4201,31 +4210,26 @@ function AdminPage() {
|
|||||||
|
|
||||||
<nav className="admin-nav">
|
<nav className="admin-nav">
|
||||||
{ADMIN_NAV_ITEMS.map((item) => (
|
{ADMIN_NAV_ITEMS.map((item) => (
|
||||||
<button
|
<NavLink
|
||||||
key={item.id}
|
key={item.id}
|
||||||
type="button"
|
to={adminTabPath(item.id)}
|
||||||
className={tab === item.id ? 'active' : ''}
|
end={item.id === 'dashboard'}
|
||||||
onClick={() => setTab(item.id)}
|
className={navLinkClass}
|
||||||
>
|
>
|
||||||
<Icon name={item.icon} /> {item.label}
|
<Icon name={item.icon} /> {item.label}
|
||||||
{item.id === 'listings' && pendingCount > 0 && <b>{pendingCount}</b>}
|
{item.id === 'listings' && pendingCount > 0 && <b>{pendingCount}</b>}
|
||||||
{item.id === 'users' && pendingVerificationCount > 0 && <b>{pendingVerificationCount}</b>}
|
{item.id === 'users' && pendingVerificationCount > 0 && <b>{pendingVerificationCount}</b>}
|
||||||
{item.id === 'reports' && pendingReportsCount > 0 && <b>{pendingReportsCount}</b>}
|
{item.id === 'reports' && pendingReportsCount > 0 && <b>{pendingReportsCount}</b>}
|
||||||
</button>
|
</NavLink>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<p className="admin-nav-group-label">Narzędzia</p>
|
<p className="admin-nav-group-label">Narzędzia</p>
|
||||||
<nav className="admin-nav">
|
<nav className="admin-nav">
|
||||||
{ADMIN_TOOL_ITEMS.map((item) => (
|
{ADMIN_TOOL_ITEMS.map((item) => (
|
||||||
<button
|
<NavLink key={item.id} to={adminTabPath(item.id)} className={navLinkClass}>
|
||||||
key={item.id}
|
|
||||||
type="button"
|
|
||||||
className={tab === item.id ? 'active' : ''}
|
|
||||||
onClick={() => setTab(item.id)}
|
|
||||||
>
|
|
||||||
<Icon name={item.icon} /> {item.label}
|
<Icon name={item.icon} /> {item.label}
|
||||||
</button>
|
</NavLink>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|||||||
@@ -21387,7 +21387,7 @@ svg {
|
|||||||
gap: 3px;
|
gap: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-nav button {
|
.admin-nav button, .admin-nav a {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
@@ -21404,22 +21404,22 @@ svg {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-nav button svg {
|
.admin-nav button svg, .admin-nav a svg {
|
||||||
color: #7186a0;
|
color: #7186a0;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
width: 15px;
|
width: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-nav button.active {
|
.admin-nav button.active, .admin-nav a.active {
|
||||||
background: #ebf6f0;
|
background: #ebf6f0;
|
||||||
color: #109253;
|
color: #109253;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-nav button.active svg {
|
.admin-nav button.active svg, .admin-nav a.active svg {
|
||||||
color: #109253;
|
color: #109253;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-nav button b {
|
.admin-nav button b, .admin-nav a b {
|
||||||
background: #129357;
|
background: #129357;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
@@ -26773,3 +26773,6 @@ svg {
|
|||||||
.not-found-actions a { border-radius: 10px; font-size: 14px; font-weight: 800; padding: 12px 22px; text-decoration: none; }
|
.not-found-actions a { border-radius: 10px; font-size: 14px; font-weight: 800; padding: 12px 22px; text-decoration: none; }
|
||||||
.not-found-primary { background: #13a360; color: #ffffff; }
|
.not-found-primary { background: #13a360; color: #ffffff; }
|
||||||
.not-found-secondary { border: 1px solid #d5dde7; color: #27384f; }
|
.not-found-secondary { border: 1px solid #d5dde7; color: #27384f; }
|
||||||
|
|
||||||
|
/* Routing: pozycje menu admina to teraz <a> (NavLink) */
|
||||||
|
.admin-nav a { color: inherit; cursor: pointer; text-decoration: none; }
|
||||||
|
|||||||
Reference in New Issue
Block a user