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

@@ -18,12 +18,18 @@ export interface ModuleState {
user: boolean;
}
// Default order for modules
const DEFAULT_MODULE_ORDER: string[] = ['feature1', 'feature2', 'feature3'];
interface ModulesContextType {
moduleStates: Record<ModuleId, ModuleState>;
moduleOrder: string[];
isModuleEnabled: (moduleId: string) => boolean;
isModuleEnabledForUser: (moduleId: string, userPermissions: UserPermissions | undefined, isSuperuser: boolean) => boolean;
setModuleEnabled: (moduleId: ModuleId, type: 'admin' | 'user', enabled: boolean) => void;
setModuleOrder: (order: string[]) => void;
saveModulesToBackend: () => Promise<void>;
saveModuleOrder: (order: string[]) => Promise<void>;
isLoading: boolean;
hasInitialized: boolean;
}
@@ -42,6 +48,7 @@ const getDefaultStates = (): Record<ModuleId, ModuleState> => {
export function ModulesProvider({ children }: { children: ReactNode }) {
const { token } = useAuth();
const [moduleStates, setModuleStates] = useState<Record<ModuleId, ModuleState>>(getDefaultStates);
const [moduleOrder, setModuleOrderState] = useState<string[]>(DEFAULT_MODULE_ORDER);
const [isLoading, setIsLoading] = useState(true);
const [hasInitialized, setHasInitialized] = useState(false);
@@ -88,6 +95,24 @@ export function ModulesProvider({ children }: { children: ReactNode }) {
});
setModuleStates(newStates);
// Load module order
if (settings.modules_order) {
let order: string[];
if (typeof settings.modules_order === 'string') {
try {
order = JSON.parse(settings.modules_order);
} catch {
order = DEFAULT_MODULE_ORDER;
}
} else if (Array.isArray(settings.modules_order)) {
order = settings.modules_order;
} else {
order = DEFAULT_MODULE_ORDER;
}
setModuleOrderState(order);
}
setHasInitialized(true);
} catch (error) {
console.error('Failed to load modules from backend:', error);
@@ -112,6 +137,21 @@ export function ModulesProvider({ children }: { children: ReactNode }) {
}
}, [moduleStates]);
// Save module order to backend
const saveModuleOrder = useCallback(async (order: string[]) => {
try {
await settingsAPI.updateModules({ modules_order: order });
} catch (error) {
console.error('Failed to save module order to backend:', error);
throw error;
}
}, []);
// Set module order (local state only, call saveModuleOrder to persist)
const setModuleOrder = useCallback((order: string[]) => {
setModuleOrderState(order);
}, []);
// Load modules when token becomes available
useEffect(() => {
if (token) {
@@ -185,10 +225,13 @@ export function ModulesProvider({ children }: { children: ReactNode }) {
<ModulesContext.Provider
value={{
moduleStates,
moduleOrder,
isModuleEnabled,
isModuleEnabledForUser,
setModuleEnabled,
setModuleOrder,
saveModulesToBackend,
saveModuleOrder,
isLoading,
hasInitialized,
}}