Add module ordering feature with drag-and-drop

- Add modules_order setting in backend settings registry
- Update ModulesContext with moduleOrder state and save functions
- Create configuration tab in Features page with drag-and-drop reordering
- Make feature tabs dynamic based on order (updates in real-time)
- Sort sidebar modules based on saved order
- Add order-cards CSS with vertical layout
- Update API client to support string[] type for modules_order

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-15 21:25:43 +01:00
parent ba53e0eff0
commit 15f211493d
8 changed files with 384 additions and 68 deletions

View File

@@ -27,14 +27,14 @@ export default function Sidebar() {
} = useSidebar();
const { viewMode, toggleViewMode, isUserModeEnabled } = useViewMode();
const { user } = useAuth();
const { isModuleEnabled, isModuleEnabledForUser, hasInitialized: modulesInitialized } = useModules();
const { isModuleEnabled, isModuleEnabledForUser, moduleOrder, hasInitialized: modulesInitialized } = useModules();
// When admin is in "user mode", show only user-permitted modules
// Otherwise, show all globally enabled modules (admin view)
const shouldUseUserPermissions = viewMode === 'user' || !user?.is_superuser;
// Don't show modules until initialization is complete to prevent flash
const mainModules = !modulesInitialized ? [] : (appModules
const mainModulesFiltered = !modulesInitialized ? [] : (appModules
.find((cat) => cat.id === 'main')
?.modules.filter((m) => {
if (!m.enabled) return false;
@@ -44,6 +44,25 @@ export default function Sidebar() {
return isModuleEnabled(m.id);
}) || []);
// Sort modules based on moduleOrder (dashboard always first, then ordered features)
const mainModules = [...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 bIndex = moduleOrder.indexOf(b.id);
// If both are in the order array, sort by their position
if (aIndex !== -1 && bIndex !== -1) return aIndex - bIndex;
// If only one is in the order array, it comes first
if (aIndex !== -1) return -1;
if (bIndex !== -1) return 1;
// If neither is in the order array, maintain original order
return 0;
});
const handleCollapseClick = () => {
if (isMobileOpen) {
closeMobileMenu();