diff --git a/frontend/src/api/hooks/auth.ts b/frontend/src/api/hooks/auth.ts index c7fa0f25..40f0ccfd 100644 --- a/frontend/src/api/hooks/auth.ts +++ b/frontend/src/api/hooks/auth.ts @@ -15,6 +15,7 @@ import { AuthHealthResponse } from '../types/auth'; import { ApiError } from '../client'; +import { useAuthStore } from '../../stores/auth.store'; // Query Keys export const authKeys = { @@ -149,6 +150,11 @@ export const useUpdateProfile = ( onSuccess: (data) => { // Update the profile cache queryClient.setQueryData(authKeys.profile(), data); + // Update the auth store user to maintain consistency + const authStore = useAuthStore.getState(); + if (authStore.user) { + authStore.updateUser(data); + } }, ...options, }); diff --git a/frontend/src/api/services/recipes.ts b/frontend/src/api/services/recipes.ts index ec414ea3..6df55983 100644 --- a/frontend/src/api/services/recipes.ts +++ b/frontend/src/api/services/recipes.ts @@ -14,6 +14,8 @@ import type { RecipeFeasibilityResponse, RecipeStatisticsResponse, RecipeCategoriesResponse, + RecipeQualityConfiguration, + RecipeQualityConfigurationUpdate, } from '../types/recipes'; /** @@ -139,6 +141,65 @@ export class RecipesService { const baseUrl = this.getBaseUrl(tenantId); return apiClient.get(`${baseUrl}/categories/list`); } + + // Quality Configuration Methods + + /** + * Get quality configuration for a recipe + */ + async getRecipeQualityConfiguration( + tenantId: string, + recipeId: string + ): Promise { + const baseUrl = this.getBaseUrl(tenantId); + return apiClient.get(`${baseUrl}/${recipeId}/quality-configuration`); + } + + /** + * Update quality configuration for a recipe + */ + async updateRecipeQualityConfiguration( + tenantId: string, + recipeId: string, + qualityConfig: RecipeQualityConfigurationUpdate + ): Promise { + const baseUrl = this.getBaseUrl(tenantId); + return apiClient.put( + `${baseUrl}/${recipeId}/quality-configuration`, + qualityConfig + ); + } + + /** + * Add quality templates to a recipe stage + */ + async addQualityTemplatesToStage( + tenantId: string, + recipeId: string, + stage: string, + templateIds: string[] + ): Promise<{ message: string }> { + const baseUrl = this.getBaseUrl(tenantId); + return apiClient.post<{ message: string }>( + `${baseUrl}/${recipeId}/quality-configuration/stages/${stage}/templates`, + templateIds + ); + } + + /** + * Remove a quality template from a recipe stage + */ + async removeQualityTemplateFromStage( + tenantId: string, + recipeId: string, + stage: string, + templateId: string + ): Promise<{ message: string }> { + const baseUrl = this.getBaseUrl(tenantId); + return apiClient.delete<{ message: string }>( + `${baseUrl}/${recipeId}/quality-configuration/stages/${stage}/templates/${templateId}` + ); + } } // Create and export singleton instance diff --git a/frontend/src/api/types/auth.ts b/frontend/src/api/types/auth.ts index 4f4dfec9..64f092e7 100644 --- a/frontend/src/api/types/auth.ts +++ b/frontend/src/api/types/auth.ts @@ -15,6 +15,7 @@ export interface User { phone?: string; language?: string; timezone?: string; + avatar?: string; // User avatar image URL tenant_id?: string; role?: GlobalUserRole; } @@ -66,6 +67,7 @@ export interface UserResponse { phone?: string; language?: string; timezone?: string; + avatar?: string; // User avatar image URL tenant_id?: string; role?: GlobalUserRole; } @@ -75,6 +77,7 @@ export interface UserUpdate { phone?: string; language?: string; timezone?: string; + avatar?: string; } export interface TokenVerificationResponse { diff --git a/frontend/src/api/types/recipes.ts b/frontend/src/api/types/recipes.ts index 2133b804..f6564806 100644 --- a/frontend/src/api/types/recipes.ts +++ b/frontend/src/api/types/recipes.ts @@ -34,6 +34,31 @@ export enum ProductionStatus { CANCELLED = 'cancelled' } +// Quality Template Association Types +export interface QualityStageConfiguration { + template_ids: string[]; + required_checks: string[]; + optional_checks: string[]; + blocking_on_failure: boolean; + min_quality_score?: number | null; +} + +export interface RecipeQualityConfiguration { + stages: Record; + overall_quality_threshold: number; + critical_stage_blocking: boolean; + auto_create_quality_checks: boolean; + quality_manager_approval_required: boolean; +} + +export interface RecipeQualityConfigurationUpdate { + stages?: Record; + overall_quality_threshold?: number; + critical_stage_blocking?: boolean; + auto_create_quality_checks?: boolean; + quality_manager_approval_required?: boolean; +} + export interface RecipeIngredientCreate { ingredient_id: string; @@ -106,6 +131,7 @@ export interface RecipeCreate { preparation_notes?: string | null; storage_instructions?: string | null; quality_standards?: string | null; + quality_check_configuration?: RecipeQualityConfiguration | null; serves_count?: number | null; nutritional_info?: Record | null; allergen_info?: Record | null; @@ -143,6 +169,7 @@ export interface RecipeUpdate { preparation_notes?: string | null; storage_instructions?: string | null; quality_standards?: string | null; + quality_check_configuration?: RecipeQualityConfiguration | null; serves_count?: number | null; nutritional_info?: Record | null; allergen_info?: Record | null; @@ -189,6 +216,7 @@ export interface RecipeResponse { preparation_notes?: string | null; storage_instructions?: string | null; quality_standards?: string | null; + quality_check_configuration?: RecipeQualityConfiguration | null; serves_count?: number | null; nutritional_info?: Record | null; allergen_info?: Record | null; diff --git a/frontend/src/components/domain/auth/ProfileSettings.tsx b/frontend/src/components/domain/auth/ProfileSettings.tsx index b1c9ad97..2327eb97 100644 --- a/frontend/src/components/domain/auth/ProfileSettings.tsx +++ b/frontend/src/components/domain/auth/ProfileSettings.tsx @@ -2,6 +2,8 @@ import React, { useState, useEffect, useRef } from 'react'; import { Button, Input, Card, Select, Avatar, Modal } from '../../ui'; import { useAuthUser } from '../../../stores/auth.store'; import { useToast } from '../../../hooks/ui/useToast'; +import { useUpdateProfile, useChangePassword, useAuthProfile } from '../../../api/hooks/auth'; +import { useEffect } from 'react'; interface ProfileSettingsProps { onSuccess?: () => void; @@ -16,7 +18,7 @@ interface ProfileFormData { phone: string; language: string; timezone: string; - avatar_url?: string; + avatar?: string; } @@ -48,16 +50,39 @@ export const ProfileSettings: React.FC = ({ const [activeTab, setActiveTab] = useState<'profile' | 'security' | 'preferences' | 'notifications'>(initialTab); - // Mock data for profile + const updateProfileMutation = useUpdateProfile(); + const changePasswordMutation = useChangePassword(); + const { data: userProfile, isLoading: profileLoading } = useAuthProfile(); + const [profileData, setProfileData] = useState({ - first_name: 'María', - last_name: 'González Pérez', - email: 'admin@bakery.com', - phone: '+34 612 345 678', + first_name: '', + last_name: '', + email: '', + phone: '', language: 'es', timezone: 'Europe/Madrid', - avatar_url: 'https://images.unsplash.com/photo-1494790108755-2616b612b372?w=150&h=150&fit=crop&crop=face' + avatar: '' // Using empty string to allow Avatar to show default bakery-themed icon }); + + const originalProfileDataRef = useRef(null); + + // Load profile data from API when available + useEffect(() => { + if (userProfile) { + const profileFormData: ProfileFormData = { + first_name: userProfile.full_name?.split(' ')[0] || '', + last_name: userProfile.full_name?.split(' ').slice(1).join(' ') || '', + email: userProfile.email || '', + phone: userProfile.phone || '', + language: userProfile.language || 'es', + timezone: userProfile.timezone || 'Europe/Madrid', + avatar: userProfile.avatar || '' + }; + + setProfileData(profileFormData); + originalProfileDataRef.current = profileFormData; // Store original data for reset + } + }, [userProfile]); const [notificationSettings, setNotificationSettings] = useState({ email_notifications: true, @@ -106,25 +131,7 @@ export const ProfileSettings: React.FC = ({ { value: 'Europe/Rome', label: 'Roma (CET/CEST)' } ]; - // Mock update profile function - const updateProfile = async (data: any): Promise => { - setIsLoading(true); - setError(null); - - try { - // Simulate API delay - await new Promise(resolve => setTimeout(resolve, 1500)); - - // Simulate successful update - console.log('Profile updated:', data); - setIsLoading(false); - return true; - } catch (err) { - setError('Error updating profile'); - setIsLoading(false); - return false; - } - }; + // Profile picture upload handler const handleImageUpload = async (event: React.ChangeEvent) => { @@ -165,7 +172,7 @@ export const ProfileSettings: React.FC = ({ await new Promise(resolve => setTimeout(resolve, 2000)); const newImageUrl = URL.createObjectURL(file); // Temporary URL - setProfileData(prev => ({ ...prev, avatar_url: newImageUrl })); + setProfileData(prev => ({ ...prev, avatar: newImageUrl })); setHasChanges(prev => ({ ...prev, profile: true })); showToast({ @@ -186,7 +193,7 @@ export const ProfileSettings: React.FC = ({ }; const handleRemoveImage = () => { - setProfileData(prev => ({ ...prev, avatar_url: '' })); + setProfileData(prev => ({ ...prev, avatar: '' })); setHasChanges(prev => ({ ...prev, profile: true })); setShowDeleteConfirm(false); }; @@ -268,27 +275,26 @@ export const ProfileSettings: React.FC = ({ } try { - const success = await updateProfile(profileData); - if (success) { - setHasChanges(false); - showToast({ - type: 'success', - title: 'Perfil actualizado', - message: 'Tu información ha sido guardada correctamente' - }); - onSuccess?.(); - } else { - showToast({ - type: 'error', - title: 'Error al actualizar', - message: error || 'No se pudo actualizar tu perfil' - }); - } + await updateProfileMutation.mutateAsync({ + full_name: `${profileData.first_name} ${profileData.last_name}`.trim() || profileData.first_name, + email: profileData.email, + phone: profileData.phone, + language: profileData.language, + timezone: profileData.timezone, + }); + + setHasChanges(false); + showToast({ + type: 'success', + title: 'Perfil actualizado', + message: 'Tu información ha sido guardada correctamente' + }); + onSuccess?.(); } catch (err) { showToast({ type: 'error', - title: 'Error de conexión', - message: 'No se pudo conectar con el servidor' + title: 'Error al actualizar', + message: 'No se pudo actualizar tu perfil' }); } }; @@ -300,19 +306,30 @@ export const ProfileSettings: React.FC = ({ return; } - // Note: This would typically call a separate password change endpoint - // For now, we'll show a placeholder message - showToast({ - type: 'success', - title: 'Contraseña actualizada', - message: 'Tu contraseña ha sido cambiada correctamente' - }); - - setPasswordData({ - currentPassword: '', - newPassword: '', - confirmNewPassword: '' - }); + try { + await changePasswordMutation.mutateAsync({ + current_password: passwordData.currentPassword, + new_password: passwordData.newPassword, + }); + + showToast({ + type: 'success', + title: 'Contraseña actualizada', + message: 'Tu contraseña ha sido cambiada correctamente' + }); + + setPasswordData({ + currentPassword: '', + newPassword: '', + confirmNewPassword: '' + }); + } catch (error) { + showToast({ + type: 'error', + title: 'Error al cambiar contraseña', + message: 'No se pudo cambiar tu contraseña. Por favor, verifica tu contraseña actual.' + }); + } }; const handleProfileInputChange = (field: keyof ProfileFormData) => (e: React.ChangeEvent) => { @@ -353,13 +370,13 @@ export const ProfileSettings: React.FC = ({ { id: 'preferences' as const, label: 'Preferencias', icon: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z' } ]; - // Mock user data for display - const mockUser = { - first_name: 'María', - last_name: 'González', - email: 'admin@bakery.com', - bakery_name: 'Panadería San Miguel', - avatar_url: 'https://images.unsplash.com/photo-1494790108755-2616b612b372?w=150&h=150&fit=crop&crop=face' + // User data for display - based on actual profile data + const displayUser = { + first_name: profileData.first_name || '', + last_name: profileData.last_name || '', + email: profileData.email || '', + bakery_name: user?.tenant_id ? 'Current Bakery' : 'No Bakery Assigned', + avatar: profileData.avatar || '' }; return ( @@ -369,8 +386,9 @@ export const ProfileSettings: React.FC = ({
@@ -378,14 +396,14 @@ export const ProfileSettings: React.FC = ({

- {mockUser.first_name} {mockUser.last_name} + {displayUser.first_name} {displayUser.last_name}

-

{mockUser.email}

+

{displayUser.email}

- Trabajando en {mockUser.bakery_name} + Trabajando en {displayUser.bakery_name}

@@ -428,8 +446,9 @@ export const ProfileSettings: React.FC = ({
@@ -556,15 +575,10 @@ export const ProfileSettings: React.FC = ({ variant="outline" size="lg" onClick={() => { - setProfileData({ - first_name: 'María', - last_name: 'González Pérez', - email: 'admin@bakery.com', - phone: '+34 612 345 678', - language: 'es', - timezone: 'Europe/Madrid', - avatar_url: 'https://images.unsplash.com/photo-1494790108755-2616b612b372?w=150&h=150&fit=crop&crop=face' - }); + // Reset to original profile data + if (originalProfileDataRef.current) { + setProfileData(originalProfileDataRef.current); + } setHasChanges(prev => ({ ...prev, profile: false })); setErrors({}); }} diff --git a/frontend/src/components/domain/auth/types.ts b/frontend/src/components/domain/auth/types.ts index a99902d2..13ac6923 100644 --- a/frontend/src/components/domain/auth/types.ts +++ b/frontend/src/components/domain/auth/types.ts @@ -69,7 +69,7 @@ export interface ProfileFormData { phone: string; language: string; timezone: string; - avatar_url?: string; + avatar?: string; } export interface BakeryFormData { diff --git a/frontend/src/components/domain/production/CreateQualityTemplateModal.tsx b/frontend/src/components/domain/production/CreateQualityTemplateModal.tsx index 50683bdd..0fd4135b 100644 --- a/frontend/src/components/domain/production/CreateQualityTemplateModal.tsx +++ b/frontend/src/components/domain/production/CreateQualityTemplateModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { useForm, Controller } from 'react-hook-form'; import { Modal, @@ -9,18 +9,47 @@ import { Badge, Card } from '../../ui'; +import { LoadingSpinner } from '../../shared'; import { QualityCheckType, ProcessStage, type QualityCheckTemplateCreate } from '../../../api/types/qualityTemplates'; import { useCurrentTenant } from '../../../stores/tenant.store'; +import { useQuery } from '@tanstack/react-query'; +import { recipesService } from '../../../api/services/recipes'; +import type { RecipeResponse } from '../../../api/types/recipes'; +import { + Plus, + X, + ChevronDown, + ChevronUp, + AlertTriangle, + Info, + Settings, + Link as LinkIcon +} from 'lucide-react'; interface CreateQualityTemplateModalProps { isOpen: boolean; onClose: () => void; - onCreateTemplate: (templateData: QualityCheckTemplateCreate) => Promise; + onCreateTemplate: (templateData: QualityCheckTemplateCreate, recipeAssociations?: RecipeAssociation[]) => Promise; isLoading?: boolean; + initialRecipe?: RecipeResponse; // Pre-select recipe if coming from recipe page +} + +interface RecipeAssociation { + recipeId: string; + recipeName: string; + stages: ProcessStage[]; +} + +interface StageConfiguration { + stage: ProcessStage; + isRequired: boolean; + isOptional: boolean; + blockingOnFailure: boolean; + minQualityScore?: number; } const QUALITY_CHECK_TYPE_OPTIONS = [ @@ -58,10 +87,22 @@ export const CreateQualityTemplateModal: React.FC { const currentTenant = useCurrentTenant(); const [selectedStages, setSelectedStages] = useState([]); + const [recipeAssociations, setRecipeAssociations] = useState([]); + const [stageConfigurations, setStageConfigurations] = useState([]); + const [showRecipeAssociation, setShowRecipeAssociation] = useState(false); + const [showAdvancedConfig, setShowAdvancedConfig] = useState(false); + + // Fetch available recipes for association + const { data: recipes, isLoading: recipesLoading } = useQuery({ + queryKey: ['recipes', currentTenant?.id], + queryFn: () => recipesService.searchRecipes(currentTenant?.id || '', { limit: 1000 }), + enabled: isOpen && !!currentTenant?.id && showRecipeAssociation + }); const { register, @@ -94,6 +135,33 @@ export const CreateQualityTemplateModal: React.FC { + if (initialRecipe && isOpen) { + setRecipeAssociations([{ + recipeId: initialRecipe.id, + recipeName: initialRecipe.name, + stages: [] + }]); + setShowRecipeAssociation(true); + } + }, [initialRecipe, isOpen]); + + // Update stage configurations when selected stages change + useEffect(() => { + const newConfigs = selectedStages.map(stage => { + const existing = stageConfigurations.find(c => c.stage === stage); + return existing || { + stage, + isRequired: false, + isOptional: true, + blockingOnFailure: true, + minQualityScore: undefined + }; + }); + setStageConfigurations(newConfigs); + }, [selectedStages]); + const handleStageToggle = (stage: ProcessStage) => { const newStages = selectedStages.includes(stage) ? selectedStages.filter(s => s !== stage) @@ -102,17 +170,78 @@ export const CreateQualityTemplateModal: React.FC { + const newAssociation: RecipeAssociation = { + recipeId: '', + recipeName: '', + stages: [] + }; + setRecipeAssociations([...recipeAssociations, newAssociation]); + }; + + const handleRemoveRecipeAssociation = (index: number) => { + setRecipeAssociations(recipeAssociations.filter((_, i) => i !== index)); + }; + + const handleRecipeAssociationChange = (index: number, field: keyof RecipeAssociation, value: any) => { + const updated = [...recipeAssociations]; + if (field === 'recipeId' && recipes) { + const recipe = recipes.find(r => r.id === value); + if (recipe) { + updated[index] = { + ...updated[index], + recipeId: value, + recipeName: recipe.name + }; + } + } else { + (updated[index] as any)[field] = value; + } + setRecipeAssociations(updated); + }; + + const handleStageConfigChange = (stage: ProcessStage, field: keyof StageConfiguration, value: any) => { + setStageConfigurations(configs => + configs.map(config => + config.stage === stage ? { ...config, [field]: value } : config + ) + ); + }; + const onSubmit = async (data: QualityCheckTemplateCreate) => { try { + // Validate recipe associations if any + const validRecipeAssociations = recipeAssociations + .filter(association => association.recipeId && association.stages.length > 0) + .map(association => ({ + ...association, + stageConfigurations: stageConfigurations + .filter(config => association.stages.includes(config.stage)) + .reduce((acc, config) => { + acc[config.stage] = { + template_ids: [], // Will be populated by backend + required_checks: config.isRequired ? [data.name] : [], + optional_checks: !config.isRequired ? [data.name] : [], + blocking_on_failure: config.blockingOnFailure, + min_quality_score: config.minQualityScore || null + }; + return acc; + }, {} as Record) + })); + await onCreateTemplate({ ...data, applicable_stages: selectedStages.length > 0 ? selectedStages : undefined, created_by: currentTenant?.id || '' - }); + }, validRecipeAssociations.length > 0 ? validRecipeAssociations : undefined); // Reset form reset(); setSelectedStages([]); + setRecipeAssociations([]); + setStageConfigurations([]); + setShowRecipeAssociation(false); + setShowAdvancedConfig(false); } catch (error) { console.error('Error creating template:', error); } @@ -121,6 +250,10 @@ export const CreateQualityTemplateModal: React.FC { reset(); setSelectedStages([]); + setRecipeAssociations([]); + setStageConfigurations([]); + setShowRecipeAssociation(false); + setShowAdvancedConfig(false); onClose(); }; @@ -296,16 +429,171 @@ export const CreateQualityTemplateModal: React.FC )} + {/* Recipe Association */} + +
+
+ +

+ Asociación con Recetas +

+ + Nuevo + +
+ +
+ + {showRecipeAssociation && ( +
+
+ +
+

Asociación Automática con Recetas

+

+ Al asociar esta plantilla con recetas, se aplicará automáticamente a los lotes de producción + creados a partir de esas recetas. Esto asegura consistencia en los controles de calidad. +

+
+
+ + {recipeAssociations.map((association, index) => { + const isComplete = association.recipeId && association.stages.length > 0; + return ( +
+
+
+
+ Receta {index + 1} +
+ {isComplete && ( + Configurada + )} +
+ {recipeAssociations.length > 1 && ( + + )} +
+ +
+ + {recipesLoading ? ( +
+ + Cargando recetas... +
+ ) : ( + + )} +
+ + {association.recipeId && ( +
+ +
+ {PROCESS_STAGE_OPTIONS.map(stage => ( + + ))} +
+
+ )} + + {association.recipeId && association.stages.length === 0 && ( +
+ +

+ Selecciona al menos una etapa para completar la configuración de esta receta. +

+
+ )} +
+ ); + })} + + +
+ )} +
+ {/* Process Stages */} -

- Etapas del Proceso Aplicables -

+
+

+ Etapas del Proceso Aplicables +

+ {selectedStages.length > 0 && ( + + )} +
+

Selecciona las etapas donde se debe aplicar este control. Si no seleccionas ninguna, se aplicará a todas las etapas.

-
+
{PROCESS_STAGE_OPTIONS.map(stage => (
+ + {/* Advanced Stage Configuration */} + {showAdvancedConfig && selectedStages.length > 0 && ( +
+
+ +
+

Configuración Avanzada por Etapa

+

+ Configura comportamientos específicos para cada etapa seleccionada. Esta configuración se aplicará + cuando se use la plantilla en recetas asociadas. +

+
+
+ +
+ {stageConfigurations.map(config => ( +
+
+ {PROCESS_STAGE_OPTIONS.find(s => s.value === config.stage)?.label} +
+ +
+
+ + + +
+ +
+ + handleStageConfigChange( + config.stage, + 'minQualityScore', + e.target.value ? parseFloat(e.target.value) : undefined + )} + placeholder="ej: 7.0" + className="w-24" + /> +

+ Puntuación mínima requerida (0-10) +

+
+
+
+ ))} +
+
+ )} {/* Settings */} diff --git a/frontend/src/components/layout/Sidebar/Sidebar.tsx b/frontend/src/components/layout/Sidebar/Sidebar.tsx index 6b2244a1..c0bfc4f5 100644 --- a/frontend/src/components/layout/Sidebar/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar/Sidebar.tsx @@ -515,12 +515,15 @@ export const Sidebar = forwardRef(({ level > 0 && 'pl-6', )} > -
+
{ItemIcon && ( (({ )} {/* Submenu indicator for collapsed sidebar */} - {isCollapsed && hasChildren && level === 0 && ( + {isCollapsed && hasChildren && level === 0 && item.children && item.children.length > 0 && (
)}
@@ -585,12 +588,12 @@ export const Sidebar = forwardRef(({ disabled={item.disabled} data-path={item.path} onMouseEnter={() => { - if (isCollapsed && hasChildren && level === 0) { + if (isCollapsed && hasChildren && level === 0 && item.children && item.children.length > 0) { setHoveredItem(item.id); } }} onMouseLeave={() => { - if (isCollapsed && hasChildren && level === 0) { + if (isCollapsed && hasChildren && level === 0 && item.children && item.children.length > 0) { setHoveredItem(null); } }} @@ -600,7 +603,7 @@ export const Sidebar = forwardRef(({ isActive && 'bg-[var(--color-primary)]/10 border-l-2 border-[var(--color-primary)]', !isActive && 'hover:bg-[var(--bg-secondary)]', item.disabled && 'opacity-50 cursor-not-allowed', - isCollapsed && level === 0 ? 'flex justify-center items-center p-3 aspect-square' : 'p-3' + isCollapsed && level === 0 ? 'flex justify-center items-center p-2 h-10 w-10 min-w-10 max-w-10' : 'p-3' )} aria-expanded={hasChildren ? isExpanded : undefined} aria-current={isActive ? 'page' : undefined} @@ -610,8 +613,8 @@ export const Sidebar = forwardRef(({ {/* Submenu overlay for collapsed sidebar */} - {isCollapsed && hasChildren && level === 0 && isHovered && ( -
0 && ( +
setHoveredItem(item.id)} onMouseLeave={() => setHoveredItem(null)} @@ -710,22 +713,22 @@ export const Sidebar = forwardRef(({ )} {/* Navigation */} -