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:
2026-07-23 23:36:30 +02:00
parent ec5a7289f6
commit af21991d9f
2 changed files with 29 additions and 22 deletions
+21 -17
View File
@@ -3665,10 +3665,8 @@ function accountStorageKey(suffix: string, user: AuthUser | null): string {
return `accountProfile:${user?.id ?? 'guest'}:${suffix}`;
}
type AdminTab =
| 'dashboard' | 'listings' | 'users' | 'messages' | 'reports' | 'payments'
| 'stats' | 'settings' | 'moderation' | 'categories' | 'locations' | 'promotions' | 'forbiddenWords'
| 'leads' | 'campaigns' | 'mailConfig';
// Zakladki panelu maja odpowiedniki w sciezkach /admin/* - jedno zrodlo prawdy w routes.ts.
type AdminTab = AdminTabKey;
const ADMIN_TAB_META: Record<AdminTab, { title: string; subtitle: string }> = {
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() {
const navigate = useNavigate();
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 [listings, setListings] = useState<AdminListing[]>([]);
const [reports, setReports] = useState<AdminListingReport[]>([]);
@@ -4193,6 +4198,10 @@ function AdminPage() {
}
};
if (isUnknownAdminSection) {
return <NotFoundPage />;
}
return (
<section className="admin-page admin-page-shell" aria-label="Panel administratora">
<div className="admin-shell admin-shell-with-sidebar">
@@ -4201,31 +4210,26 @@ function AdminPage() {
<nav className="admin-nav">
{ADMIN_NAV_ITEMS.map((item) => (
<button
<NavLink
key={item.id}
type="button"
className={tab === item.id ? 'active' : ''}
onClick={() => setTab(item.id)}
to={adminTabPath(item.id)}
end={item.id === 'dashboard'}
className={navLinkClass}
>
<Icon name={item.icon} /> {item.label}
{item.id === 'listings' && pendingCount > 0 && <b>{pendingCount}</b>}
{item.id === 'users' && pendingVerificationCount > 0 && <b>{pendingVerificationCount}</b>}
{item.id === 'reports' && pendingReportsCount > 0 && <b>{pendingReportsCount}</b>}
</button>
</NavLink>
))}
</nav>
<p className="admin-nav-group-label">Narzędzia</p>
<nav className="admin-nav">
{ADMIN_TOOL_ITEMS.map((item) => (
<button
key={item.id}
type="button"
className={tab === item.id ? 'active' : ''}
onClick={() => setTab(item.id)}
>
<NavLink key={item.id} to={adminTabPath(item.id)} className={navLinkClass}>
<Icon name={item.icon} /> {item.label}
</button>
</NavLink>
))}
</nav>