Improve Features page ordering and mobile UI consistency
- Fix module ordering with local state tracking for immediate UI updates - Add tab centering when selected (scroll to center) - Use finally block in handleApplyOrder to ensure state reset - Add cancelOrder translation key - Increase order card min-width for better readability - Normalize mobile top bar height with min-height constraint - Add display:flex to mobile title sections for proper layout 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,21 +40,14 @@ export default function Sidebar() {
|
|||||||
.find((cat) => cat.id === 'main')
|
.find((cat) => cat.id === 'main')
|
||||||
?.modules.filter((m) => {
|
?.modules.filter((m) => {
|
||||||
if (!m.enabled) return false;
|
if (!m.enabled) return false;
|
||||||
// Dashboard is always shown
|
|
||||||
if (m.id === 'dashboard') return true;
|
|
||||||
if (shouldUseUserPermissions) {
|
if (shouldUseUserPermissions) {
|
||||||
return isModuleEnabledForUser(m.id, user?.permissions, user?.is_superuser || false);
|
return isModuleEnabledForUser(m.id, user?.permissions, user?.is_superuser || false);
|
||||||
}
|
}
|
||||||
return isModuleEnabled(m.id);
|
return isModuleEnabled(m.id);
|
||||||
}) || []);
|
}) || []);
|
||||||
|
|
||||||
// Sort modules based on moduleOrder (dashboard always first, then ordered features)
|
// Sort modules based on moduleOrder
|
||||||
const sortedModules = [...mainModulesFiltered].sort((a, b) => {
|
const sortedModules = [...mainModulesFiltered].sort((a, b) => {
|
||||||
// Dashboard always comes first
|
|
||||||
if (a.id === 'dashboard') return -1;
|
|
||||||
if (b.id === 'dashboard') return 1;
|
|
||||||
|
|
||||||
// Sort other modules by moduleOrder
|
|
||||||
const aIndex = moduleOrder.indexOf(a.id);
|
const aIndex = moduleOrder.indexOf(a.id);
|
||||||
const bIndex = moduleOrder.indexOf(b.id);
|
const bIndex = moduleOrder.indexOf(b.id);
|
||||||
|
|
||||||
@@ -69,13 +62,11 @@ export default function Sidebar() {
|
|||||||
|
|
||||||
// Split modules by position (top = main nav, bottom = above footer)
|
// Split modules by position (top = main nav, bottom = above footer)
|
||||||
const topModules = sortedModules.filter(m => {
|
const topModules = sortedModules.filter(m => {
|
||||||
if (m.id === 'dashboard') return true; // Dashboard always at top
|
|
||||||
const state = moduleStates[m.id as keyof typeof moduleStates];
|
const state = moduleStates[m.id as keyof typeof moduleStates];
|
||||||
return !state || state.position === 'top';
|
return !state || state.position === 'top';
|
||||||
});
|
});
|
||||||
|
|
||||||
const bottomModules = sortedModules.filter(m => {
|
const bottomModules = sortedModules.filter(m => {
|
||||||
if (m.id === 'dashboard') return false; // Dashboard never at bottom
|
|
||||||
const state = moduleStates[m.id as keyof typeof moduleStates];
|
const state = moduleStates[m.id as keyof typeof moduleStates];
|
||||||
return state && state.position === 'bottom';
|
return state && state.position === 'bottom';
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { UserPermissions } from '../types';
|
|||||||
|
|
||||||
// User-facing modules that can be toggled
|
// User-facing modules that can be toggled
|
||||||
export const TOGGLEABLE_MODULES = [
|
export const TOGGLEABLE_MODULES = [
|
||||||
|
{ id: 'dashboard', icon: 'dashboard', defaultEnabled: true, defaultPosition: 'top' as const },
|
||||||
{ id: 'feature1', icon: 'playlist_play', defaultEnabled: true, defaultPosition: 'top' as const },
|
{ id: 'feature1', icon: 'playlist_play', defaultEnabled: true, defaultPosition: 'top' as const },
|
||||||
{ id: 'feature2', icon: 'download', defaultEnabled: true, defaultPosition: 'top' as const },
|
{ id: 'feature2', icon: 'download', defaultEnabled: true, defaultPosition: 'top' as const },
|
||||||
{ id: 'feature3', icon: 'cast', defaultEnabled: true, defaultPosition: 'top' as const },
|
{ id: 'feature3', icon: 'cast', defaultEnabled: true, defaultPosition: 'top' as const },
|
||||||
@@ -23,7 +24,7 @@ export interface ModuleState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Default order for modules (top modules, then bottom modules)
|
// Default order for modules (top modules, then bottom modules)
|
||||||
const DEFAULT_MODULE_ORDER: string[] = ['feature1', 'feature2', 'feature3', 'search', 'notifications'];
|
const DEFAULT_MODULE_ORDER: string[] = ['dashboard', 'feature1', 'feature2', 'feature3', 'search', 'notifications'];
|
||||||
|
|
||||||
interface ModulesContextType {
|
interface ModulesContextType {
|
||||||
moduleStates: Record<ModuleId, ModuleState>;
|
moduleStates: Record<ModuleId, ModuleState>;
|
||||||
@@ -123,12 +124,14 @@ export function ModulesProvider({ children }: { children: ReactNode }) {
|
|||||||
} else {
|
} else {
|
||||||
order = DEFAULT_MODULE_ORDER;
|
order = DEFAULT_MODULE_ORDER;
|
||||||
}
|
}
|
||||||
// Ensure all toggleable modules are included (for newly added modules)
|
// Ensure all toggleable modules are included at correct positions (for newly added modules)
|
||||||
const allModuleIds = TOGGLEABLE_MODULES.map(m => m.id);
|
TOGGLEABLE_MODULES.forEach((module, defaultIndex) => {
|
||||||
const missingModules = allModuleIds.filter(id => !order.includes(id));
|
if (!order.includes(module.id)) {
|
||||||
if (missingModules.length > 0) {
|
// Insert at the default index position, or at end if index is beyond current length
|
||||||
order = [...order, ...missingModules];
|
const insertAt = Math.min(defaultIndex, order.length);
|
||||||
|
order.splice(insertAt, 0, module.id);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
setModuleOrderState(order);
|
setModuleOrderState(order);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
"orderSection": "Sidebar Order",
|
"orderSection": "Sidebar Order",
|
||||||
"orderDesc": "Drag to reorder features in the sidebar",
|
"orderDesc": "Drag to reorder features in the sidebar",
|
||||||
"applyOrder": "Apply",
|
"applyOrder": "Apply",
|
||||||
|
"cancelOrder": "Cancel",
|
||||||
"visibility": "Visibility",
|
"visibility": "Visibility",
|
||||||
"topSection": "Main Section",
|
"topSection": "Main Section",
|
||||||
"bottomSection": "Bottom Section",
|
"bottomSection": "Bottom Section",
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
"orderSection": "Ordine nella Sidebar",
|
"orderSection": "Ordine nella Sidebar",
|
||||||
"orderDesc": "Trascina per riordinare le funzioni nella barra laterale",
|
"orderDesc": "Trascina per riordinare le funzioni nella barra laterale",
|
||||||
"applyOrder": "Applica",
|
"applyOrder": "Applica",
|
||||||
|
"cancelOrder": "Annulla",
|
||||||
"visibility": "Visibilità",
|
"visibility": "Visibilità",
|
||||||
"topSection": "Sezione Principale",
|
"topSection": "Sezione Principale",
|
||||||
"bottomSection": "Sezione Inferiore",
|
"bottomSection": "Sezione Inferiore",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { useAuth } from '../../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
import { useTranslation } from '../../contexts/LanguageContext';
|
import { useTranslation } from '../../contexts/LanguageContext';
|
||||||
import { useSidebar } from '../../contexts/SidebarContext';
|
import { useSidebar } from '../../contexts/SidebarContext';
|
||||||
@@ -7,7 +7,7 @@ import type { ModuleId } from '../../contexts/ModulesContext';
|
|||||||
import Feature1Tab from '../../components/admin/Feature1Tab';
|
import Feature1Tab from '../../components/admin/Feature1Tab';
|
||||||
import '../../styles/AdminPanel.css';
|
import '../../styles/AdminPanel.css';
|
||||||
|
|
||||||
type TabId = 'config' | 'feature1' | 'feature2' | 'feature3' | 'search' | 'notifications';
|
type TabId = 'config' | 'dashboard' | 'feature1' | 'feature2' | 'feature3' | 'search' | 'notifications';
|
||||||
|
|
||||||
export default function Features() {
|
export default function Features() {
|
||||||
const { user: currentUser } = useAuth();
|
const { user: currentUser } = useAuth();
|
||||||
@@ -19,15 +19,64 @@ export default function Features() {
|
|||||||
const saveRef = useRef(saveModulesToBackend);
|
const saveRef = useRef(saveModulesToBackend);
|
||||||
const [draggedItem, setDraggedItem] = useState<string | null>(null);
|
const [draggedItem, setDraggedItem] = useState<string | null>(null);
|
||||||
const [localOrder, setLocalOrder] = useState<string[]>([]);
|
const [localOrder, setLocalOrder] = useState<string[]>([]);
|
||||||
|
const [localPositions, setLocalPositions] = useState<Record<string, 'top' | 'bottom'>>({});
|
||||||
const [hasOrderChanges, setHasOrderChanges] = useState(false);
|
const [hasOrderChanges, setHasOrderChanges] = useState(false);
|
||||||
|
const tabsContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const isUserEditing = useRef(false); // Track if user is actively editing
|
||||||
|
|
||||||
// Sync local order with context order
|
// Sync local order with context order (only when moduleOrder changes, not moduleStates)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (moduleOrder.length > 0) {
|
if (moduleOrder.length > 0 && !isUserEditing.current) {
|
||||||
setLocalOrder(moduleOrder);
|
// Start with current order
|
||||||
|
const fullOrder = [...moduleOrder];
|
||||||
|
|
||||||
|
// Insert missing modules at their default positions from TOGGLEABLE_MODULES
|
||||||
|
TOGGLEABLE_MODULES.forEach((module, defaultIndex) => {
|
||||||
|
if (!fullOrder.includes(module.id)) {
|
||||||
|
// Insert at the default index position, or at end if index is beyond current length
|
||||||
|
const insertAt = Math.min(defaultIndex, fullOrder.length);
|
||||||
|
fullOrder.splice(insertAt, 0, module.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setLocalOrder(fullOrder);
|
||||||
setHasOrderChanges(false);
|
setHasOrderChanges(false);
|
||||||
}
|
}
|
||||||
}, [moduleOrder]);
|
}, [moduleOrder]);
|
||||||
|
|
||||||
|
// Sync positions from moduleStates only on initial load or when not editing
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isUserEditing.current) {
|
||||||
|
const positions: Record<string, 'top' | 'bottom'> = {};
|
||||||
|
TOGGLEABLE_MODULES.forEach(module => {
|
||||||
|
const state = moduleStates[module.id];
|
||||||
|
positions[module.id] = state?.position || module.defaultPosition;
|
||||||
|
});
|
||||||
|
setLocalPositions(positions);
|
||||||
|
}
|
||||||
|
}, [moduleStates]);
|
||||||
|
|
||||||
|
// Scroll active tab to center of container
|
||||||
|
const scrollActiveTabIntoView = useCallback((tabId: string) => {
|
||||||
|
if (tabsContainerRef.current) {
|
||||||
|
const container = tabsContainerRef.current;
|
||||||
|
const activeButton = container.querySelector(`[data-tab-id="${tabId}"]`) as HTMLElement;
|
||||||
|
if (activeButton) {
|
||||||
|
const containerWidth = container.clientWidth;
|
||||||
|
const buttonLeft = activeButton.offsetLeft;
|
||||||
|
const buttonWidth = activeButton.offsetWidth;
|
||||||
|
// Calculate scroll position to center the button
|
||||||
|
const scrollLeft = buttonLeft - (containerWidth / 2) + (buttonWidth / 2);
|
||||||
|
container.scrollTo({ left: Math.max(0, scrollLeft), behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Handle tab change with scroll
|
||||||
|
const handleTabChange = useCallback((tabId: TabId) => {
|
||||||
|
setActiveTab(tabId);
|
||||||
|
setTimeout(() => scrollActiveTabIntoView(tabId), 50);
|
||||||
|
}, [scrollActiveTabIntoView]);
|
||||||
// Keep saveRef updated with latest function
|
// Keep saveRef updated with latest function
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
saveRef.current = saveModulesToBackend;
|
saveRef.current = saveModulesToBackend;
|
||||||
@@ -133,24 +182,32 @@ export default function Features() {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!draggedItem) return;
|
if (!draggedItem) return;
|
||||||
|
|
||||||
const draggedPosition = moduleStates[draggedItem as ModuleId]?.position || 'top';
|
const draggedPosition = localPositions[draggedItem] || 'top';
|
||||||
|
|
||||||
// If dropping on same item, just change section if different
|
// If dropping on same item, just change section if different
|
||||||
if (draggedItem === targetModuleId) {
|
if (draggedItem === targetModuleId) {
|
||||||
if (draggedPosition !== targetSection) {
|
if (draggedPosition !== targetSection) {
|
||||||
|
isUserEditing.current = true; // Mark as user editing
|
||||||
hasUserMadeChanges.current = true;
|
hasUserMadeChanges.current = true;
|
||||||
setModulePosition(draggedItem as ModuleId, targetSection);
|
setModulePosition(draggedItem as ModuleId, targetSection);
|
||||||
|
// Update local positions immediately for UI responsiveness
|
||||||
|
setLocalPositions(prev => ({ ...prev, [draggedItem]: targetSection }));
|
||||||
|
setHasOrderChanges(true);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change position if moving to different section
|
// Change position if moving to different section
|
||||||
if (draggedPosition !== targetSection) {
|
if (draggedPosition !== targetSection) {
|
||||||
|
isUserEditing.current = true; // Mark as user editing
|
||||||
hasUserMadeChanges.current = true;
|
hasUserMadeChanges.current = true;
|
||||||
setModulePosition(draggedItem as ModuleId, targetSection);
|
setModulePosition(draggedItem as ModuleId, targetSection);
|
||||||
|
// Update local positions immediately for UI responsiveness
|
||||||
|
setLocalPositions(prev => ({ ...prev, [draggedItem]: targetSection }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reorder within the list
|
// Reorder within the list
|
||||||
|
isUserEditing.current = true; // Mark as user editing
|
||||||
const newOrder = [...localOrder];
|
const newOrder = [...localOrder];
|
||||||
const draggedIndex = newOrder.indexOf(draggedItem);
|
const draggedIndex = newOrder.indexOf(draggedItem);
|
||||||
const targetIndex = newOrder.indexOf(targetModuleId);
|
const targetIndex = newOrder.indexOf(targetModuleId);
|
||||||
@@ -168,12 +225,16 @@ export default function Features() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!draggedItem) return;
|
if (!draggedItem) return;
|
||||||
|
|
||||||
const draggedPosition = moduleStates[draggedItem as ModuleId]?.position || 'top';
|
const draggedPosition = localPositions[draggedItem] || 'top';
|
||||||
|
|
||||||
// Change position if moving to different section
|
// Change position if moving to different section
|
||||||
if (draggedPosition !== section) {
|
if (draggedPosition !== section) {
|
||||||
|
isUserEditing.current = true; // Mark as user editing
|
||||||
hasUserMadeChanges.current = true;
|
hasUserMadeChanges.current = true;
|
||||||
setModulePosition(draggedItem as ModuleId, section);
|
setModulePosition(draggedItem as ModuleId, section);
|
||||||
|
// Update local positions immediately for UI responsiveness
|
||||||
|
setLocalPositions(prev => ({ ...prev, [draggedItem]: section }));
|
||||||
|
setHasOrderChanges(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -181,26 +242,41 @@ export default function Features() {
|
|||||||
try {
|
try {
|
||||||
setModuleOrder(localOrder);
|
setModuleOrder(localOrder);
|
||||||
await saveModuleOrder(localOrder);
|
await saveModuleOrder(localOrder);
|
||||||
setHasOrderChanges(false);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to save order:', error);
|
console.error('Failed to save order:', error);
|
||||||
|
} finally {
|
||||||
|
isUserEditing.current = false;
|
||||||
|
setHasOrderChanges(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCancelOrder = () => {
|
||||||
|
isUserEditing.current = false; // Done editing
|
||||||
|
setLocalOrder(moduleOrder);
|
||||||
|
// Reset positions from moduleStates
|
||||||
|
const positions: Record<string, 'top' | 'bottom'> = {};
|
||||||
|
TOGGLEABLE_MODULES.forEach(module => {
|
||||||
|
const state = moduleStates[module.id];
|
||||||
|
positions[module.id] = state?.position || module.defaultPosition;
|
||||||
|
});
|
||||||
|
setLocalPositions(positions);
|
||||||
|
setHasOrderChanges(false);
|
||||||
|
};
|
||||||
|
|
||||||
const getModuleInfo = (moduleId: string) => {
|
const getModuleInfo = (moduleId: string) => {
|
||||||
const module = TOGGLEABLE_MODULES.find(m => m.id === moduleId);
|
const module = TOGGLEABLE_MODULES.find(m => m.id === moduleId);
|
||||||
return module || { id: moduleId, icon: 'extension', defaultEnabled: true };
|
return module || { id: moduleId, icon: 'extension', defaultEnabled: true };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Split modules by position for the config tab
|
// Split modules by position for the config tab (using localPositions for immediate UI updates)
|
||||||
const topOrderModules = localOrder.filter(id => {
|
const topOrderModules = localOrder.filter(id => {
|
||||||
const state = moduleStates[id as ModuleId];
|
const position = localPositions[id];
|
||||||
return !state || state.position === 'top';
|
return !position || position === 'top';
|
||||||
});
|
});
|
||||||
|
|
||||||
const bottomOrderModules = localOrder.filter(id => {
|
const bottomOrderModules = localOrder.filter(id => {
|
||||||
const state = moduleStates[id as ModuleId];
|
const position = localPositions[id];
|
||||||
return state && state.position === 'bottom';
|
return position === 'bottom';
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderConfigTab = () => {
|
const renderConfigTab = () => {
|
||||||
@@ -233,7 +309,6 @@ export default function Features() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="order-card-info">
|
<div className="order-card-info">
|
||||||
<span className="order-card-name">{moduleName}</span>
|
<span className="order-card-name">{moduleName}</span>
|
||||||
<span className="order-card-desc">{t.featuresPage?.orderDesc || 'Trascina per riordinare'}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="order-card-handle">
|
<div className="order-card-handle">
|
||||||
<span className="material-symbols-outlined">drag_indicator</span>
|
<span className="material-symbols-outlined">drag_indicator</span>
|
||||||
@@ -274,7 +349,6 @@ export default function Features() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="order-card-info">
|
<div className="order-card-info">
|
||||||
<span className="order-card-name">{moduleName}</span>
|
<span className="order-card-name">{moduleName}</span>
|
||||||
<span className="order-card-desc">{t.featuresPage?.orderDesc || 'Trascina per riordinare'}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="order-card-handle">
|
<div className="order-card-handle">
|
||||||
<span className="material-symbols-outlined">drag_indicator</span>
|
<span className="material-symbols-outlined">drag_indicator</span>
|
||||||
@@ -290,6 +364,10 @@ export default function Features() {
|
|||||||
|
|
||||||
{hasOrderChanges && (
|
{hasOrderChanges && (
|
||||||
<div className="order-actions">
|
<div className="order-actions">
|
||||||
|
<button className="btn-secondary" onClick={handleCancelOrder}>
|
||||||
|
<span className="material-symbols-outlined">close</span>
|
||||||
|
{t.featuresPage?.cancelOrder || 'Annulla'}
|
||||||
|
</button>
|
||||||
<button className="btn-primary" onClick={handleApplyOrder}>
|
<button className="btn-primary" onClick={handleApplyOrder}>
|
||||||
<span className="material-symbols-outlined">check</span>
|
<span className="material-symbols-outlined">check</span>
|
||||||
{t.featuresPage?.applyOrder || 'Applica'}
|
{t.featuresPage?.applyOrder || 'Applica'}
|
||||||
@@ -307,6 +385,19 @@ export default function Features() {
|
|||||||
switch (activeTab) {
|
switch (activeTab) {
|
||||||
case 'config':
|
case 'config':
|
||||||
return renderConfigTab();
|
return renderConfigTab();
|
||||||
|
case 'dashboard':
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{renderModuleToggle('dashboard')}
|
||||||
|
<div className="tab-content-placeholder">
|
||||||
|
<div className="placeholder-icon">
|
||||||
|
<span className="material-symbols-outlined">dashboard</span>
|
||||||
|
</div>
|
||||||
|
<h3>{t.sidebar.dashboard}</h3>
|
||||||
|
<p>{t.features.comingSoon}</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
case 'feature1':
|
case 'feature1':
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -374,7 +465,7 @@ export default function Features() {
|
|||||||
return (
|
return (
|
||||||
<main className="main-content admin-panel-root">
|
<main className="main-content admin-panel-root">
|
||||||
<div className="page-tabs-container">
|
<div className="page-tabs-container">
|
||||||
<div className="page-tabs-slider">
|
<div className="page-tabs-slider" ref={tabsContainerRef}>
|
||||||
<button className="mobile-menu-btn" onClick={toggleMobileMenu} aria-label={t.theme.toggleMenu}>
|
<button className="mobile-menu-btn" onClick={toggleMobileMenu} aria-label={t.theme.toggleMenu}>
|
||||||
<span className="material-symbols-outlined">menu</span>
|
<span className="material-symbols-outlined">menu</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -384,21 +475,24 @@ export default function Features() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="page-tabs-divider"></div>
|
<div className="page-tabs-divider"></div>
|
||||||
<button
|
<button
|
||||||
|
data-tab-id="config"
|
||||||
className={`page-tab-btn ${activeTab === 'config' ? 'active' : ''}`}
|
className={`page-tab-btn ${activeTab === 'config' ? 'active' : ''}`}
|
||||||
onClick={() => setActiveTab('config')}
|
onClick={() => handleTabChange('config')}
|
||||||
>
|
>
|
||||||
<span className="material-symbols-outlined">tune</span>
|
<span className="material-symbols-outlined">tune</span>
|
||||||
<span>{t.featuresPage?.configTab || 'Configurazione'}</span>
|
<span>{t.featuresPage?.configTab || 'Configurazione'}</span>
|
||||||
</button>
|
</button>
|
||||||
{TOGGLEABLE_MODULES.map((module) => {
|
{[...topOrderModules, ...bottomOrderModules].map((moduleId) => {
|
||||||
const moduleName = t.sidebar[module.id as keyof typeof t.sidebar] || module.id;
|
const moduleInfo = getModuleInfo(moduleId);
|
||||||
|
const moduleName = t.sidebar[moduleId as keyof typeof t.sidebar] || moduleId;
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={module.id}
|
key={moduleId}
|
||||||
className={`page-tab-btn ${activeTab === module.id ? 'active' : ''}`}
|
data-tab-id={moduleId}
|
||||||
onClick={() => setActiveTab(module.id as TabId)}
|
className={`page-tab-btn ${activeTab === moduleId ? 'active' : ''}`}
|
||||||
|
onClick={() => handleTabChange(moduleId as TabId)}
|
||||||
>
|
>
|
||||||
<span className="material-symbols-outlined">{module.icon}</span>
|
<span className="material-symbols-outlined">{moduleInfo.icon}</span>
|
||||||
<span>{moduleName}</span>
|
<span>{moduleName}</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2542,7 +2542,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
min-width: 0;
|
min-width: 280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-card-name {
|
.order-card-name {
|
||||||
@@ -2642,6 +2642,30 @@
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-actions .btn-secondary {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 500;
|
||||||
|
background: var(--color-bg-elevated);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-actions .btn-secondary:hover {
|
||||||
|
background: var(--color-bg-card);
|
||||||
|
border-color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-actions .btn-secondary .material-symbols-outlined {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mobile Responsive */
|
/* Mobile Responsive */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.order-cards {
|
.order-cards {
|
||||||
|
|||||||
@@ -34,10 +34,16 @@
|
|||||||
.admin-tabs-container {
|
.admin-tabs-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 0.75rem var(--page-padding-x);
|
padding: 0.75rem;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure no extra margin from body */
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Page header slider - rounded pill style (like admin panel) */
|
/* Page header slider - rounded pill style (like admin panel) */
|
||||||
.page-tabs-slider,
|
.page-tabs-slider,
|
||||||
.admin-tabs-slider {
|
.admin-tabs-slider {
|
||||||
@@ -51,6 +57,16 @@
|
|||||||
box-shadow: var(--shadow-md);
|
box-shadow: var(--shadow-md);
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
backdrop-filter: blur(14px) saturate(1.15);
|
backdrop-filter: blur(14px) saturate(1.15);
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-tabs-slider::-webkit-scrollbar,
|
||||||
|
.admin-tabs-slider::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@supports (color: color-mix(in srgb, black, white)) {
|
@supports (color: color-mix(in srgb, black, white)) {
|
||||||
@@ -149,7 +165,6 @@
|
|||||||
background: linear-gradient(135deg, var(--color-accent) 0%, var(--color-accent-hover) 100%);
|
background: linear-gradient(135deg, var(--color-accent) 0%, var(--color-accent-hover) 100%);
|
||||||
color: white;
|
color: white;
|
||||||
box-shadow: 0 2px 8px rgba(var(--color-accent-rgb), 0.3);
|
box-shadow: 0 2px 8px rgba(var(--color-accent-rgb), 0.3);
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-subtitle {
|
.page-subtitle {
|
||||||
@@ -291,7 +306,7 @@
|
|||||||
.mobile-menu-btn {
|
.mobile-menu-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 4px;
|
left: 16px;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@@ -315,30 +330,39 @@
|
|||||||
|
|
||||||
.page-tabs-container,
|
.page-tabs-container,
|
||||||
.admin-tabs-container {
|
.admin-tabs-container {
|
||||||
padding: var(--page-padding-y-mobile) var(--page-padding-x-mobile);
|
padding: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-tabs-slider,
|
.page-tabs-slider,
|
||||||
.admin-tabs-slider {
|
.admin-tabs-slider {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-wrap: wrap;
|
flex-wrap: nowrap;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 8px;
|
gap: 4px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
min-height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title-section,
|
.page-title-section,
|
||||||
.admin-title-section {
|
.admin-title-section {
|
||||||
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
padding-left: 48px;
|
padding-left: 72px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title-section .material-symbols-outlined,
|
.page-title-section .material-symbols-outlined,
|
||||||
.admin-title-section .material-symbols-outlined {
|
.admin-title-section .material-symbols-outlined {
|
||||||
font-size: 24px;
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title-text,
|
||||||
|
.admin-title-text {
|
||||||
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide divider on mobile */
|
/* Hide divider on mobile */
|
||||||
@@ -349,19 +373,29 @@
|
|||||||
|
|
||||||
/* Hide title section when tabs are present on mobile */
|
/* Hide title section when tabs are present on mobile */
|
||||||
.page-tabs-slider:has(.page-tab-btn) .page-title-section,
|
.page-tabs-slider:has(.page-tab-btn) .page-title-section,
|
||||||
.admin-tabs-slider:has(.admin-tab-btn) .admin-title-section {
|
.page-tabs-slider:has(.admin-tab-btn) .page-title-section,
|
||||||
|
.admin-tabs-slider:has(.admin-tab-btn) .admin-title-section,
|
||||||
|
.admin-tabs-slider:has(.page-tab-btn) .admin-title-section {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Center title section absolutely when no tabs are present on mobile */
|
/* Add padding-left when tabs are present to avoid logo overlap */
|
||||||
.page-tabs-slider:not(:has(.page-tab-btn)),
|
.page-tabs-slider:has(.page-tab-btn),
|
||||||
.admin-tabs-slider:not(:has(.admin-tab-btn)) {
|
.page-tabs-slider:has(.admin-tab-btn),
|
||||||
justify-content: center;
|
.admin-tabs-slider:has(.admin-tab-btn),
|
||||||
min-height: 48px;
|
.admin-tabs-slider:has(.page-tab-btn) {
|
||||||
|
padding-left: 72px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-tabs-slider:not(:has(.page-tab-btn)) .page-title-section,
|
/* Center title section absolutely when no tabs are present on mobile */
|
||||||
.admin-tabs-slider:not(:has(.admin-tab-btn)) .admin-title-section {
|
.page-tabs-slider:not(:has(.page-tab-btn)):not(:has(.admin-tab-btn)),
|
||||||
|
.admin-tabs-slider:not(:has(.admin-tab-btn)):not(:has(.page-tab-btn)) {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-tabs-slider:not(:has(.page-tab-btn)):not(:has(.admin-tab-btn)) .page-title-section,
|
||||||
|
.admin-tabs-slider:not(:has(.admin-tab-btn)):not(:has(.page-tab-btn)) .admin-title-section {
|
||||||
|
display: flex;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@@ -371,19 +405,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Lighter icon color in dark theme when only title is shown */
|
/* Lighter icon color in dark theme when only title is shown */
|
||||||
.page-tabs-slider:not(:has(.page-tab-btn)) .page-title-section .material-symbols-outlined,
|
.page-tabs-slider:not(:has(.page-tab-btn)):not(:has(.admin-tab-btn)) .page-title-section .material-symbols-outlined,
|
||||||
.admin-tabs-slider:not(:has(.admin-tab-btn)) .admin-title-section .material-symbols-outlined {
|
.admin-tabs-slider:not(:has(.admin-tab-btn)):not(:has(.page-tab-btn)) .admin-title-section .material-symbols-outlined {
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tabs on second row - full width */
|
/* Tabs - expand to fill, but scrollable when overflow */
|
||||||
.page-tab-btn,
|
.page-tab-btn,
|
||||||
.admin-tab-btn {
|
.admin-tab-btn {
|
||||||
flex: 1;
|
flex: 1 0 auto;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.5rem 0.75rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
min-width: 0;
|
min-width: 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide text on mobile, show only icons */
|
/* Hide text on mobile, show only icons */
|
||||||
@@ -492,6 +526,52 @@
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========== DARK THEME OVERRIDES ========== */
|
||||||
|
|
||||||
|
/* Top bar in dark mode: match sidebar style exactly */
|
||||||
|
[data-theme='dark'] .page-tabs-slider,
|
||||||
|
[data-theme='dark'] .admin-tabs-slider {
|
||||||
|
background: var(--color-bg-sidebar);
|
||||||
|
border: 1px solid var(--color-sidebar-border);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (color: color-mix(in srgb, black, white)) {
|
||||||
|
[data-theme='dark'] .page-tabs-slider,
|
||||||
|
[data-theme='dark'] .admin-tabs-slider {
|
||||||
|
background: color-mix(in srgb, var(--color-bg-sidebar) 88%, transparent);
|
||||||
|
backdrop-filter: blur(18px) saturate(1.15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text colors in dark top bar */
|
||||||
|
[data-theme='dark'] .page-title-section,
|
||||||
|
[data-theme='dark'] .admin-title-section {
|
||||||
|
color: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .page-title-text,
|
||||||
|
[data-theme='dark'] .admin-title-text {
|
||||||
|
color: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .page-tab-btn,
|
||||||
|
[data-theme='dark'] .admin-tab-btn {
|
||||||
|
color: rgba(241, 245, 249, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .page-tab-btn:hover:not(.active),
|
||||||
|
[data-theme='dark'] .admin-tab-btn:hover:not(.active) {
|
||||||
|
color: #f1f5f9;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='dark'] .page-tabs-divider,
|
||||||
|
[data-theme='dark'] .admin-tabs-divider {
|
||||||
|
background: rgba(255, 255, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
/* ========== DARK THEME + AUTO ACCENT OVERRIDES ========== */
|
/* ========== DARK THEME + AUTO ACCENT OVERRIDES ========== */
|
||||||
|
|
||||||
/* Tab buttons with auto accent in dark mode: use off-white background with dark text */
|
/* Tab buttons with auto accent in dark mode: use off-white background with dark text */
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
.sidebar {
|
.sidebar {
|
||||||
width: 260px;
|
width: 260px;
|
||||||
height: calc(100vh - 2rem);
|
height: calc(100vh - 1.5rem);
|
||||||
/* Floating height */
|
/* Floating height */
|
||||||
max-height: calc(100vh - 2rem);
|
max-height: calc(100vh - 1.5rem);
|
||||||
background: var(--color-bg-sidebar);
|
background: var(--color-bg-sidebar);
|
||||||
color: var(--color-text-sidebar);
|
color: var(--color-text-sidebar);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 1rem;
|
left: 0.75rem;
|
||||||
/* Floating position */
|
/* Floating position */
|
||||||
top: 1rem;
|
top: 0.75rem;
|
||||||
bottom: 1rem;
|
bottom: 0.75rem;
|
||||||
box-shadow: var(--shadow-xl);
|
box-shadow: var(--shadow-xl);
|
||||||
/* Enhanced shadow for floating effect */
|
/* Enhanced shadow for floating effect */
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
@@ -657,12 +657,12 @@ button.nav-item {
|
|||||||
/* Ensure fully hidden */
|
/* Ensure fully hidden */
|
||||||
width: 280px;
|
width: 280px;
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
height: calc(100dvh - 2rem);
|
height: calc(100dvh - 1.5rem);
|
||||||
/* Floating height on mobile too */
|
/* Floating height on mobile too */
|
||||||
max-height: calc(100dvh - 2rem);
|
max-height: calc(100dvh - 1.5rem);
|
||||||
left: 1rem;
|
left: 0.75rem;
|
||||||
top: 1rem;
|
top: 0.75rem;
|
||||||
bottom: 1rem;
|
bottom: 0.75rem;
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
/* Rounded on mobile */
|
/* Rounded on mobile */
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -42,12 +42,12 @@
|
|||||||
--height-header: 70px;
|
--height-header: 70px;
|
||||||
|
|
||||||
/* Sidebar Dimensions */
|
/* Sidebar Dimensions */
|
||||||
/* Sidebar is positioned at left: 1rem (16px) with these widths */
|
/* Sidebar is positioned at left: 0.75rem (12px) with these widths */
|
||||||
/* margin-left = left offset (16px) + sidebar width */
|
/* margin-left = left offset (12px) + sidebar width */
|
||||||
--sidebar-width: 276px;
|
--sidebar-width: 272px;
|
||||||
/* 16px offset + 260px width */
|
/* 12px offset + 260px width */
|
||||||
--sidebar-width-collapsed: 96px;
|
--sidebar-width-collapsed: 92px;
|
||||||
/* 16px offset + 80px width */
|
/* 12px offset + 80px width */
|
||||||
--sidebar-mobile-width: 280px;
|
--sidebar-mobile-width: 280px;
|
||||||
|
|
||||||
/* Page Layout Spacing */
|
/* Page Layout Spacing */
|
||||||
|
|||||||
Reference in New Issue
Block a user