beta New control panel
# Tab Management System Implementation ## What's Been Implemented 1. Complete Tab Management System with: - Drag and drop functionality for reordering tabs - Visual feedback during drag operations - Smooth animations and transitions - Dark mode support - Search functionality for tabs - Reset to defaults option 2. Developer Mode Features: - Shows ALL available tabs in developer mode - Maintains tab order across modes - Proper visibility toggles - Automatic inclusion of developer-specific tabs 3. User Mode Features: - Shows only user-configured tabs - Maintains separate tab configurations - Proper visibility management ## Key Components - `TabManagement.tsx`: Main management interface - `ControlPanel.tsx`: Main panel with tab display - Integration with tab configuration store - Proper type definitions and interfaces ## Technical Features - React DnD for drag and drop - Framer Motion for animations - TypeScript for type safety - UnoCSS for styling - Toast notifications for user feedback ## Next Steps 1. Testing: - Test tab visibility in both modes - Verify drag and drop persistence - Check dark mode compatibility - Verify search functionality - Test reset functionality 2. Potential Improvements: - Add tab grouping functionality - Implement tab pinning - Add keyboard shortcuts - Improve accessibility - Add tab descriptions - Add tab icons customization 3. Documentation: - Add inline code comments - Create user documentation - Document API interfaces - Add setup instructions 4. Future Features: - Tab export/import - Custom tab creation - Tab templates - User preferences sync - Tab statistics ## Known Issues to Address 1. Ensure all tabs are visible in developer mode 2. Improve drag and drop performance 3. Better state persistence 4. Enhanced error handling 5. Improved type safety ## Usage Instructions 1. Switch to developer mode to see all available tabs 2. Use drag and drop to reorder tabs 3. Toggle visibility using switches 4. Use search to filter tabs 5. Reset to defaults if needed ## Technical Debt 1. Refactor tab configuration store 2. Improve type definitions 3. Add proper error boundaries 4. Implement proper loading states 5. Add comprehensive testing ## Security Considerations 1. Validate tab configurations 2. Sanitize user input 3. Implement proper access control 4. Add audit logging 5. Secure state management
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import * as RadixDialog from '@radix-ui/react-dialog';
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
import { motion } from 'framer-motion';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
import { DialogTitle } from '~/components/ui/Dialog';
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
developerModeStore,
|
||||
setDeveloperMode,
|
||||
} from '~/lib/stores/settings';
|
||||
import { DEFAULT_TAB_CONFIG } from '~/components/settings/settings.types';
|
||||
|
||||
interface DraggableTabTileProps {
|
||||
tab: TabVisibilityConfig;
|
||||
@@ -123,6 +124,10 @@ interface UsersWindowProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface TabWithType extends TabVisibilityConfig {
|
||||
isExtraDevTab?: boolean;
|
||||
}
|
||||
|
||||
export const UsersWindow = ({ open, onClose }: UsersWindowProps) => {
|
||||
const [activeTab, setActiveTab] = useState<TabType | null>(null);
|
||||
const [loadingTab, setLoadingTab] = useState<TabType | null>(null);
|
||||
@@ -223,45 +228,48 @@ export const UsersWindow = ({ open, onClose }: UsersWindowProps) => {
|
||||
|
||||
// Only show tabs that are assigned to the user window AND are visible
|
||||
const visibleUserTabs = useMemo(() => {
|
||||
console.log('Filtering user tabs with configuration:', tabConfiguration);
|
||||
|
||||
if (!tabConfiguration?.userTabs || !Array.isArray(tabConfiguration.userTabs)) {
|
||||
console.warn('Invalid tab configuration, using empty array');
|
||||
return [];
|
||||
}
|
||||
|
||||
return tabConfiguration.userTabs
|
||||
.filter((tab) => {
|
||||
if (!tab || typeof tab.id !== 'string') {
|
||||
console.warn('Invalid tab entry:', tab);
|
||||
return false;
|
||||
}
|
||||
// Get the base user tabs that are visible
|
||||
const baseTabs = tabConfiguration.userTabs.filter((tab) => {
|
||||
if (!tab || typeof tab.id !== 'string') {
|
||||
console.warn('Invalid tab entry:', tab);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hide notifications tab if notifications are disabled
|
||||
if (tab.id === 'notifications' && !profile.notifications) {
|
||||
console.log('Hiding notifications tab due to disabled notifications');
|
||||
return false;
|
||||
}
|
||||
// Hide notifications tab if notifications are disabled
|
||||
if (tab.id === 'notifications' && !profile.notifications) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure the tab has the required properties
|
||||
if (typeof tab.visible !== 'boolean' || typeof tab.window !== 'string' || typeof tab.order !== 'number') {
|
||||
console.warn('Tab missing required properties:', tab);
|
||||
return false;
|
||||
}
|
||||
// Only show tabs that are explicitly visible and assigned to the user window
|
||||
return tab.visible && tab.window === 'user';
|
||||
});
|
||||
|
||||
// Only show tabs that are explicitly visible and assigned to the user window
|
||||
const isVisible = tab.visible && tab.window === 'user';
|
||||
console.log(`Tab ${tab.id} visibility:`, isVisible);
|
||||
// If in developer mode, add the developer-only tabs
|
||||
if (developerMode) {
|
||||
const developerOnlyTabs = DEFAULT_TAB_CONFIG.filter((tab) => {
|
||||
/*
|
||||
* Only include tabs that are:
|
||||
* 1. Assigned to developer window
|
||||
* 2. Not already in user tabs
|
||||
* 3. Marked as visible in developer window
|
||||
*/
|
||||
return tab.window === 'developer' && tab.visible && !baseTabs.some((baseTab) => baseTab.id === tab.id);
|
||||
}).map((tab) => ({
|
||||
...tab,
|
||||
isExtraDevTab: true,
|
||||
order: baseTabs.length + tab.order, // Place after user tabs
|
||||
}));
|
||||
|
||||
return isVisible;
|
||||
})
|
||||
.sort((a: TabVisibilityConfig, b: TabVisibilityConfig) => {
|
||||
const orderA = typeof a.order === 'number' ? a.order : 0;
|
||||
const orderB = typeof b.order === 'number' ? b.order : 0;
|
||||
return [...baseTabs, ...developerOnlyTabs].sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
return orderA - orderB;
|
||||
});
|
||||
}, [tabConfiguration, profile.notifications]);
|
||||
return baseTabs.sort((a, b) => a.order - b.order);
|
||||
}, [tabConfiguration, profile.notifications, developerMode]);
|
||||
|
||||
const moveTab = (dragIndex: number, hoverIndex: number) => {
|
||||
const draggedTab = visibleUserTabs[dragIndex];
|
||||
@@ -569,29 +577,50 @@ export const UsersWindow = ({ open, onClose }: UsersWindowProps) => {
|
||||
>
|
||||
<motion.div
|
||||
key={activeTab || 'home'}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="p-6"
|
||||
>
|
||||
{activeTab ? (
|
||||
getTabComponent()
|
||||
) : (
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
{visibleUserTabs.map((tab: TabVisibilityConfig, index: number) => (
|
||||
<DraggableTabTile
|
||||
key={tab.id}
|
||||
tab={tab}
|
||||
index={index}
|
||||
moveTab={moveTab}
|
||||
onClick={() => handleTabClick(tab.id)}
|
||||
isActive={activeTab === tab.id}
|
||||
hasUpdate={getTabUpdateStatus(tab.id)}
|
||||
statusMessage={getStatusMessage(tab.id)}
|
||||
description={TAB_DESCRIPTIONS[tab.id]}
|
||||
isLoading={loadingTab === tab.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<motion.div
|
||||
className="grid grid-cols-4 gap-4"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<AnimatePresence mode="popLayout">
|
||||
{visibleUserTabs.map((tab: TabWithType, index: number) => (
|
||||
<motion.div
|
||||
key={tab.id}
|
||||
layout
|
||||
initial={{ opacity: 0, scale: 0.8, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.8, y: -20 }}
|
||||
transition={{
|
||||
duration: 0.2,
|
||||
delay: index * 0.05,
|
||||
}}
|
||||
>
|
||||
<DraggableTabTile
|
||||
tab={tab}
|
||||
index={index}
|
||||
moveTab={moveTab}
|
||||
onClick={() => handleTabClick(tab.id)}
|
||||
isActive={activeTab === tab.id}
|
||||
hasUpdate={getTabUpdateStatus(tab.id)}
|
||||
statusMessage={getStatusMessage(tab.id)}
|
||||
description={TAB_DESCRIPTIONS[tab.id]}
|
||||
isLoading={loadingTab === tab.id}
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user