diff --git a/backend/app/schemas/settings.py b/backend/app/schemas/settings.py
index 319363b0b..72fff4818 100644
--- a/backend/app/schemas/settings.py
+++ b/backend/app/schemas/settings.py
@@ -533,6 +533,11 @@ class AppSettingsUpdate(BaseModel):
raise ValueError("default_sidebar_order must be valid JSON or empty")
if isinstance(parsed, dict):
order = parsed.get("order")
+ hidden_system_item_ids = parsed.get("hiddenSystemItemIds", [])
+ if not isinstance(hidden_system_item_ids, list) or not all(
+ isinstance(item, str) for item in hidden_system_item_ids
+ ):
+ raise ValueError("sidebar hidden system item IDs must be an array of strings")
elif isinstance(parsed, list):
order = parsed
else:
diff --git a/backend/tests/unit/test_sidebar_settings.py b/backend/tests/unit/test_sidebar_settings.py
new file mode 100644
index 000000000..b5cba3032
--- /dev/null
+++ b/backend/tests/unit/test_sidebar_settings.py
@@ -0,0 +1,17 @@
+import pytest
+from pydantic import ValidationError
+
+from backend.app.schemas.settings import AppSettingsUpdate
+
+
+def test_default_sidebar_order_accepts_hidden_system_item_ids():
+ value = '{"order":["printers","ext-1","settings"],"hiddenSystemItemIds":["stats"]}'
+
+ update = AppSettingsUpdate(default_sidebar_order=value)
+
+ assert update.default_sidebar_order == value
+
+
+def test_default_sidebar_order_rejects_invalid_hidden_system_item_ids():
+ with pytest.raises(ValidationError):
+ AppSettingsUpdate(default_sidebar_order='{"order":["printers"],"hiddenSystemItemIds":"stats"}')
diff --git a/frontend/src/__tests__/components/Layout.test.tsx b/frontend/src/__tests__/components/Layout.test.tsx
index f3c1905ec..c9b2d38d4 100644
--- a/frontend/src/__tests__/components/Layout.test.tsx
+++ b/frontend/src/__tests__/components/Layout.test.tsx
@@ -2,15 +2,21 @@
* Tests for the Layout component.
*/
-import { describe, it, expect, beforeEach } from 'vitest';
+import { describe, it, expect, beforeEach, vi } from 'vitest';
import { waitFor } from '@testing-library/react';
import { render } from '../utils';
import { Layout } from '../../components/Layout';
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/server';
+import { SIDEBAR_HIDDEN_SYSTEM_ITEMS_KEY, SIDEBAR_ORDER_KEY } from '../../utils/sidebarLayout';
describe('Layout', () => {
beforeEach(() => {
+ vi.mocked(localStorage.getItem).mockReset();
+ vi.mocked(localStorage.setItem).mockReset();
+ vi.mocked(localStorage.removeItem).mockReset();
+ vi.mocked(localStorage.clear).mockReset();
+ localStorage.clear();
server.use(
http.get('/api/v1/printers/', () => {
return HttpResponse.json([
@@ -102,6 +108,55 @@ describe('Layout', () => {
expect(settingsLink).toBeInTheDocument();
});
});
+
+ it('hides system nav items stored in sidebar layout preferences', async () => {
+ vi.mocked(localStorage.getItem).mockImplementation((key) => {
+ if (key === SIDEBAR_HIDDEN_SYSTEM_ITEMS_KEY) return JSON.stringify(['printers']);
+ return null;
+ });
+
+ render();
+
+ await waitFor(() => {
+ const sidebar = document.querySelector('aside');
+ expect(sidebar).toBeInTheDocument();
+ expect(sidebar?.querySelector('a[href="/inventory"]')).toBeInTheDocument();
+ });
+
+ expect(document.querySelector('aside a[href="/"]')).toBeNull();
+ });
+
+ it('applies admin default sidebar hidden state with the default order', async () => {
+ const storage: Record = {};
+ vi.mocked(localStorage.getItem).mockImplementation((key) => storage[key] ?? null);
+ vi.mocked(localStorage.setItem).mockImplementation((key, value) => {
+ storage[key] = value;
+ });
+ server.use(
+ http.get('/api/v1/settings/default-sidebar-order', () =>
+ HttpResponse.json({
+ default_sidebar_order: JSON.stringify({
+ order: ['inventory', 'printers', 'settings'],
+ hiddenSystemItemIds: ['printers'],
+ }),
+ }),
+ ),
+ );
+
+ render();
+
+ await waitFor(() => {
+ const sidebar = document.querySelector('aside');
+ expect(sidebar).toBeInTheDocument();
+ expect(sidebar?.querySelector('a[href="/inventory"]')).toBeInTheDocument();
+ });
+
+ await waitFor(() => {
+ expect(document.querySelector('aside a[href="/"]')).toBeNull();
+ expect(localStorage.setItem).toHaveBeenCalledWith(SIDEBAR_ORDER_KEY, JSON.stringify(['inventory', 'printers', 'settings']));
+ expect(localStorage.setItem).toHaveBeenCalledWith(SIDEBAR_HIDDEN_SYSTEM_ITEMS_KEY, JSON.stringify(['printers']));
+ });
+ });
});
describe('version display', () => {
diff --git a/frontend/src/__tests__/pages/SettingsPage.test.tsx b/frontend/src/__tests__/pages/SettingsPage.test.tsx
index 305326c3a..58c5095bc 100644
--- a/frontend/src/__tests__/pages/SettingsPage.test.tsx
+++ b/frontend/src/__tests__/pages/SettingsPage.test.tsx
@@ -2,13 +2,15 @@
* Tests for the SettingsPage component.
*/
-import { describe, it, expect, beforeEach } from 'vitest';
-import { screen, waitFor } from '@testing-library/react';
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { fireEvent, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render } from '../utils';
import { SettingsPage } from '../../pages/SettingsPage';
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/server';
+import { SIDEBAR_HIDDEN_SYSTEM_ITEMS_KEY, SIDEBAR_ORDER_KEY } from '../../utils/sidebarLayout';
+import { setAuthToken } from '../../api/client';
const mockSettings = {
auto_archive: true,
@@ -41,12 +43,18 @@ describe('SettingsPage', () => {
// switch in one test (e.g. clicking "Workflow") doesn't carry into
// sibling tests that expect to land on the default General tab.
window.history.replaceState({}, '', '/');
+ vi.mocked(localStorage.getItem).mockReset();
+ vi.mocked(localStorage.setItem).mockReset();
+ vi.mocked(localStorage.removeItem).mockReset();
+ vi.mocked(localStorage.clear).mockReset();
+ localStorage.clear();
+ setAuthToken(null);
server.use(
http.get('/api/v1/settings/', () => {
return HttpResponse.json(mockSettings);
}),
- http.patch('/api/v1/settings/', async ({ request }) => {
+ http.put('/api/v1/settings/', async ({ request }) => {
const body = await request.json();
return HttpResponse.json({ ...mockSettings, ...body });
}),
@@ -70,6 +78,9 @@ describe('SettingsPage', () => {
}),
http.get('/api/v1/auth/status', () => {
return HttpResponse.json({ auth_enabled: false, requires_setup: false });
+ }),
+ http.get('/api/v1/external-links/', () => {
+ return HttpResponse.json([]);
})
);
});
@@ -172,6 +183,244 @@ describe('SettingsPage', () => {
expect(screen.getByText('Check printer firmware')).toBeInTheDocument();
});
});
+
+ it('hides a Bambuddy sidebar page from Sidebar', async () => {
+ const user = userEvent.setup();
+ render();
+
+ await screen.findByRole('heading', { name: 'Sidebar' });
+ await screen.findAllByText('Visible in sidebar');
+
+ vi.mocked(localStorage.setItem).mockClear();
+ await user.click((await screen.findAllByLabelText('Hide page'))[0]);
+
+ expect(localStorage.setItem).toHaveBeenCalledWith(SIDEBAR_HIDDEN_SYSTEM_ITEMS_KEY, JSON.stringify(['printers']));
+ expect(screen.getByText('Hidden from sidebar')).toBeInTheDocument();
+ });
+
+ it('shows a previously hidden Bambuddy sidebar page from Sidebar', async () => {
+ vi.mocked(localStorage.getItem).mockImplementation((key) => {
+ if (key === SIDEBAR_HIDDEN_SYSTEM_ITEMS_KEY) return JSON.stringify(['printers']);
+ return null;
+ });
+
+ const user = userEvent.setup();
+ render();
+
+ await screen.findByRole('heading', { name: 'Sidebar' });
+ await screen.findByText('Hidden from sidebar');
+
+ vi.mocked(localStorage.setItem).mockClear();
+ await user.click(await screen.findByLabelText('Show page'));
+
+ expect(localStorage.setItem).toHaveBeenCalledWith(SIDEBAR_HIDDEN_SYSTEM_ITEMS_KEY, JSON.stringify([]));
+ expect(screen.getAllByText('Visible in sidebar').length).toBeGreaterThan(0);
+ });
+
+ it('does not allow Settings to be hidden from Sidebar', async () => {
+ render();
+
+ await screen.findByRole('heading', { name: 'Sidebar' });
+ await screen.findByText('Required in sidebar');
+
+ const settingsVisibilityButton = await screen.findByLabelText('Settings cannot be hidden');
+ expect(settingsVisibilityButton).toBeDisabled();
+ expect(screen.getByText('Required in sidebar')).toBeInTheDocument();
+ });
+
+ it('presents external links and Bambuddy pages in saved sidebar order', async () => {
+ vi.mocked(localStorage.getItem).mockImplementation((key) => {
+ if (key === SIDEBAR_ORDER_KEY) return JSON.stringify(['ext-7', 'printers', 'settings']);
+ return null;
+ });
+ server.use(
+ http.get('/api/v1/external-links/', () =>
+ HttpResponse.json([
+ {
+ id: 7,
+ name: 'Docs',
+ url: 'https://docs.example.test',
+ icon: 'Link',
+ open_in_new_tab: true,
+ custom_icon: null,
+ sort_order: 0,
+ created_at: '2026-01-01T00:00:00Z',
+ updated_at: '2026-01-01T00:00:00Z',
+ },
+ ]),
+ ),
+ );
+
+ render();
+
+ await screen.findByRole('heading', { name: 'Sidebar' });
+ const docs = await screen.findByText('Docs');
+ const printers = screen.getAllByText('Printers').find(element => element.closest('[draggable="true"]'));
+
+ expect(printers).toBeDefined();
+ expect(docs.compareDocumentPosition(printers) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
+ });
+
+ it('saves mixed Sidebar order when items are dragged', async () => {
+ server.use(
+ http.get('/api/v1/external-links/', () =>
+ HttpResponse.json([
+ {
+ id: 7,
+ name: 'Docs',
+ url: 'https://docs.example.test',
+ icon: 'Link',
+ open_in_new_tab: true,
+ custom_icon: null,
+ sort_order: 0,
+ created_at: '2026-01-01T00:00:00Z',
+ updated_at: '2026-01-01T00:00:00Z',
+ },
+ ]),
+ ),
+ );
+
+ render();
+
+ await screen.findByRole('heading', { name: 'Sidebar' });
+ const docsRow = (await screen.findByText('Docs')).closest('[draggable="true"]');
+ const printersRow = screen.getAllByText('Printers')
+ .find(element => element.closest('[draggable="true"]'))
+ ?.closest('[draggable="true"]');
+
+ expect(docsRow).not.toBeNull();
+ expect(printersRow).not.toBeNull();
+
+ vi.mocked(localStorage.setItem).mockClear();
+ const dataTransfer = {
+ effectAllowed: '',
+ dropEffect: '',
+ setData: vi.fn(),
+ };
+ fireEvent.dragStart(docsRow!, { dataTransfer });
+ fireEvent.dragOver(printersRow!, { dataTransfer });
+ fireEvent.drop(printersRow!, { dataTransfer });
+
+ expect(localStorage.setItem).toHaveBeenCalledWith(
+ SIDEBAR_ORDER_KEY,
+ JSON.stringify(['ext-7', 'printers', 'inventory', 'archives', 'queue', 'projects', 'files', 'makerworld', 'profiles', 'maintenance', 'stats', 'settings']),
+ );
+ });
+
+ it('resets Sidebar to all pages first and configured links at the bottom', async () => {
+ vi.mocked(localStorage.getItem).mockImplementation((key) => {
+ if (key === SIDEBAR_HIDDEN_SYSTEM_ITEMS_KEY) return JSON.stringify(['printers', 'stats']);
+ if (key === SIDEBAR_ORDER_KEY) return JSON.stringify(['ext-7', 'settings', 'printers']);
+ return null;
+ });
+ server.use(
+ http.get('/api/v1/external-links/', () =>
+ HttpResponse.json([
+ {
+ id: 7,
+ name: 'Docs',
+ url: 'https://docs.example.test',
+ icon: 'Link',
+ open_in_new_tab: true,
+ custom_icon: null,
+ sort_order: 0,
+ created_at: '2026-01-01T00:00:00Z',
+ updated_at: '2026-01-01T00:00:00Z',
+ },
+ ]),
+ ),
+ );
+
+ const user = userEvent.setup();
+ render();
+
+ const heading = await screen.findByRole('heading', { name: 'Sidebar' });
+ const card = heading.closest('#card-sidebar-links');
+ expect(card).not.toBeNull();
+ await screen.findByText('Docs');
+
+ vi.mocked(localStorage.setItem).mockClear();
+ await user.click(within(card as HTMLElement).getByRole('button', { name: /reset/i }));
+
+ expect(localStorage.setItem).toHaveBeenCalledWith(SIDEBAR_HIDDEN_SYSTEM_ITEMS_KEY, JSON.stringify([]));
+ expect(localStorage.setItem).toHaveBeenCalledWith(
+ SIDEBAR_ORDER_KEY,
+ JSON.stringify(['printers', 'inventory', 'archives', 'queue', 'projects', 'files', 'makerworld', 'profiles', 'maintenance', 'stats', 'settings', 'ext-7']),
+ );
+
+ const settingsRow = screen.getAllByText('Settings')
+ .find(element => element.closest('[draggable="true"]'))
+ ?.closest('[draggable="true"]');
+ const docsRow = screen.getByText('Docs').closest('[draggable="true"]');
+ expect(settingsRow).not.toBeNull();
+ expect(docsRow).not.toBeNull();
+ expect(settingsRow!.compareDocumentPosition(docsRow!) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
+ expect(screen.queryByText('Hidden from sidebar')).not.toBeInTheDocument();
+ });
+
+ it('sets the current Sidebar order as the backend default for settings admins', async () => {
+ let defaultSidebarOrderPayload: string | null = null;
+ vi.mocked(localStorage.getItem).mockImplementation((key) => {
+ if (key === SIDEBAR_HIDDEN_SYSTEM_ITEMS_KEY) return JSON.stringify(['stats']);
+ return null;
+ });
+
+ server.use(
+ http.get('/api/v1/auth/status', () =>
+ HttpResponse.json({ auth_enabled: true, requires_setup: false }),
+ ),
+ http.get('/api/v1/auth/me', () =>
+ HttpResponse.json({
+ id: 1,
+ username: 'admin',
+ role: 'admin',
+ is_active: true,
+ is_admin: false,
+ groups: [{ id: 1, name: 'Administrators' }],
+ permissions: ['settings:update'],
+ created_at: '2026-01-01T00:00:00Z',
+ }),
+ ),
+ http.get('/api/v1/settings/', () =>
+ HttpResponse.json({ ...mockSettings, default_sidebar_order: '' }),
+ ),
+ http.put('/api/v1/settings/', async ({ request }) => {
+ const body = await request.json() as { default_sidebar_order?: string };
+ defaultSidebarOrderPayload = body.default_sidebar_order ?? null;
+ return HttpResponse.json({ ...mockSettings, ...body });
+ }),
+ );
+ setAuthToken('test-token');
+
+ const user = userEvent.setup();
+ render();
+
+ const heading = await screen.findByRole('heading', { name: 'Sidebar' });
+ const card = heading.closest('#card-sidebar-links');
+ expect(card).not.toBeNull();
+
+ await user.click(within(card as HTMLElement).getByRole('switch', { name: 'Set Default' }));
+
+ await waitFor(() => {
+ expect(defaultSidebarOrderPayload).not.toBeNull();
+ });
+ expect(JSON.parse(defaultSidebarOrderPayload!)).toEqual({
+ order: [
+ 'printers',
+ 'inventory',
+ 'archives',
+ 'queue',
+ 'projects',
+ 'files',
+ 'makerworld',
+ 'profiles',
+ 'maintenance',
+ 'stats',
+ 'settings',
+ ],
+ hiddenSystemItemIds: ['stats'],
+ });
+ });
});
describe('update CTA per deployment shape', () => {
diff --git a/frontend/src/components/ExternalLinksSettings.tsx b/frontend/src/components/ExternalLinksSettings.tsx
index c004b9543..2991f7676 100644
--- a/frontend/src/components/ExternalLinksSettings.tsx
+++ b/frontend/src/components/ExternalLinksSettings.tsx
@@ -1,30 +1,47 @@
-import { useState } from 'react';
+import { useMemo, useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
-import { Link2, Plus, Pencil, Trash2, GripVertical, Loader2, ExternalLink as ExternalLinkIcon } from 'lucide-react';
+import { Eye, EyeOff, Link2, Plus, Pencil, Trash2, GripVertical, Loader2, ExternalLink as ExternalLinkIcon, RotateCcw } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { api } from '../api/client';
import type { ExternalLink } from '../api/client';
import { Card, CardContent, CardHeader } from './Card';
import { Button } from './Button';
+import { Toggle } from './Toggle';
import { AddExternalLinkModal } from './AddExternalLinkModal';
import { ConfirmModal } from './ConfirmModal';
import { getIconByName } from './IconPicker';
+import { defaultNavItems } from './Layout';
+import { useAuth } from '../contexts/AuthContext';
+import { useToast } from '../contexts/ToastContext';
+import {
+ getHiddenSidebarSystemItemIds,
+ getSidebarOrder,
+ saveHiddenSidebarSystemItemIds,
+ saveSidebarOrder,
+} from '../utils/sidebarLayout';
+
+type SidebarLayoutItem =
+ | { type: 'system'; id: string; navItem: typeof defaultNavItems[number] }
+ | { type: 'external'; id: string; link: ExternalLink };
export function ExternalLinksSettings() {
const { t } = useTranslation();
const queryClient = useQueryClient();
+ const { authEnabled, hasPermission } = useAuth();
+ const { showToast } = useToast();
const [showAddModal, setShowAddModal] = useState(false);
const [editingLink, setEditingLink] = useState(null);
const [deletingLink, setDeletingLink] = useState(null);
- const [draggedId, setDraggedId] = useState(null);
+ const [draggedId, setDraggedId] = useState(null);
+ const [dragOverId, setDragOverId] = useState(null);
+ const [hiddenSystemItemIds, setHiddenSystemItemIds] = useState(getHiddenSidebarSystemItemIds);
+ const [sidebarOrder, setSidebarOrder] = useState(() => getSidebarOrder(defaultNavItems.map(i => i.id)));
- // Fetch external links
const { data: links, isLoading } = useQuery({
queryKey: ['external-links'],
queryFn: api.getExternalLinks,
});
- // Delete mutation
const deleteMutation = useMutation({
mutationFn: (id: number) => api.deleteExternalLink(id),
onSuccess: () => {
@@ -32,41 +49,105 @@ export function ExternalLinksSettings() {
},
});
- // Reorder mutation
- const reorderMutation = useMutation({
- mutationFn: (ids: number[]) => api.reorderExternalLinks(ids),
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['external-links'] });
+ const { data: settings } = useQuery({
+ queryKey: ['settings'],
+ queryFn: api.getSettings,
+ enabled: authEnabled && hasPermission('settings:update'),
+ });
+
+ const updateDefaultSidebarMutation = useMutation({
+ mutationFn: (defaultSidebarOrder: string) => api.updateSettings({ default_sidebar_order: defaultSidebarOrder }),
+ onSuccess: (_data, defaultSidebarOrder) => {
+ queryClient.invalidateQueries({ queryKey: ['settings'] });
+ queryClient.invalidateQueries({ queryKey: ['default-sidebar-order'] });
+ showToast(
+ defaultSidebarOrder
+ ? t('settings.sidebarDefaultSet')
+ : t('settings.sidebarDefaultCleared'),
+ 'success'
+ );
+ },
+ onError: () => {
+ showToast(t('settings.sidebarDefaultFailed'), 'error');
},
});
- const handleDragStart = (e: React.DragEvent, id: number) => {
+ const layoutItems = useMemo(() => {
+ const navItemsMap = new Map(defaultNavItems.map(item => [item.id, item]));
+ const externalLinksMap = new Map((links || []).map(link => [`ext-${link.id}`, link]));
+ const result: SidebarLayoutItem[] = [];
+ const seen = new Set();
+
+ const addItem = (id: string) => {
+ if (seen.has(id)) return;
+
+ const navItem = navItemsMap.get(id);
+ if (navItem) {
+ result.push({ type: 'system', id, navItem });
+ seen.add(id);
+ return;
+ }
+
+ const link = externalLinksMap.get(id);
+ if (link) {
+ result.push({ type: 'external', id, link });
+ seen.add(id);
+ }
+ };
+
+ sidebarOrder.forEach(addItem);
+ defaultNavItems.forEach(item => addItem(item.id));
+ (links || []).forEach(link => addItem(`ext-${link.id}`));
+
+ return result;
+ }, [links, sidebarOrder]);
+
+ const persistSidebarOrder = (order: string[]) => {
+ setSidebarOrder(order);
+ saveSidebarOrder(order);
+ };
+
+ const handleDragStart = (e: React.DragEvent, id: string) => {
setDraggedId(id);
e.dataTransfer.effectAllowed = 'move';
+ e.dataTransfer.setData('text/plain', id);
};
- const handleDragOver = (e: React.DragEvent) => {
+ const handleDragOver = (e: React.DragEvent, id: string) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
+ setDragOverId(id);
};
- const handleDrop = (e: React.DragEvent, targetId: number) => {
+ const handleDrop = (e: React.DragEvent, targetId: string) => {
e.preventDefault();
- if (draggedId === null || draggedId === targetId || !links) return;
+ if (draggedId === null || draggedId === targetId) {
+ setDraggedId(null);
+ setDragOverId(null);
+ return;
+ }
- const currentIds = links.map((l) => l.id);
- const draggedIndex = currentIds.indexOf(draggedId);
- const targetIndex = currentIds.indexOf(targetId);
+ const currentOrder = layoutItems.map(item => item.id);
+ const draggedIndex = currentOrder.indexOf(draggedId);
+ const targetIndex = currentOrder.indexOf(targetId);
- if (draggedIndex === -1 || targetIndex === -1) return;
+ if (draggedIndex === -1 || targetIndex === -1) {
+ setDraggedId(null);
+ setDragOverId(null);
+ return;
+ }
- // Reorder
- const newIds = [...currentIds];
- newIds.splice(draggedIndex, 1);
- newIds.splice(targetIndex, 0, draggedId);
+ currentOrder.splice(draggedIndex, 1);
+ currentOrder.splice(targetIndex, 0, draggedId);
+ persistSidebarOrder(currentOrder);
- reorderMutation.mutate(newIds);
setDraggedId(null);
+ setDragOverId(null);
+ };
+
+ const handleDragEnd = () => {
+ setDraggedId(null);
+ setDragOverId(null);
};
const handleDelete = (link: ExternalLink) => {
@@ -80,48 +161,163 @@ export function ExternalLinksSettings() {
}
};
+ const resetSidebarLayout = () => {
+ const resetOrder = [
+ ...defaultNavItems.map(item => item.id),
+ ...(links || []).map(link => `ext-${link.id}`),
+ ];
+
+ setHiddenSystemItemIds([]);
+ setSidebarOrder(resetOrder);
+ saveHiddenSidebarSystemItemIds([]);
+ saveSidebarOrder(resetOrder);
+ };
+
+ const handleToggleDefaultSidebarOrder = (enabled: boolean) => {
+ const currentOrder = layoutItems.map(item => item.id);
+ updateDefaultSidebarMutation.mutate(enabled ? JSON.stringify({
+ order: currentOrder,
+ hiddenSystemItemIds,
+ }) : '');
+ };
+
+ const toggleSystemItemVisibility = (id: string) => {
+ if (id === 'settings') return;
+
+ const isHidden = hiddenSystemItemIds.includes(id);
+ const nextIds = isHidden
+ ? hiddenSystemItemIds.filter(hiddenId => hiddenId !== id)
+ : [...hiddenSystemItemIds, id];
+
+ setHiddenSystemItemIds(nextIds);
+ saveHiddenSidebarSystemItemIds(nextIds);
+ };
+
+ const canSetDefaultSidebarOrder = authEnabled && hasPermission('settings:update');
+ const isDefaultSidebarEnabled = !!settings?.default_sidebar_order;
+
return (
<>