From 96d8576103eae1c90997a3ed0b6d9b5cef05ed28 Mon Sep 17 00:00:00 2001 From: Urtzi Alfaro Date: Sun, 28 Dec 2025 22:29:27 +0100 Subject: [PATCH] Add frontend loading imporvements 2 --- frontend/src/api/hooks/subscription.ts | 7 +- .../components/layout/AppShell/AppShell.tsx | 5 -- .../src/components/layout/Sidebar/Sidebar.tsx | 21 ------ frontend/src/locales/en/wizards.json | 69 +++++++++++++++++-- frontend/src/locales/es/wizards.json | 69 +++++++++++++++++-- frontend/src/locales/eu/wizards.json | 69 +++++++++++++++++-- frontend/src/stores/useTenantInitializer.ts | 36 ++-------- 7 files changed, 206 insertions(+), 70 deletions(-) diff --git a/frontend/src/api/hooks/subscription.ts b/frontend/src/api/hooks/subscription.ts index 832fabd7..30d948b4 100644 --- a/frontend/src/api/hooks/subscription.ts +++ b/frontend/src/api/hooks/subscription.ts @@ -2,7 +2,7 @@ * Subscription hook for checking plan features and limits */ -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; import { subscriptionService } from '../services/subscription'; import { @@ -54,13 +54,14 @@ export const useSubscription = () => { }); // Derive subscription info from query data or tenant fallback - const subscriptionInfo: SubscriptionInfo = { + // IMPORTANT: Memoize to prevent infinite re-renders in dependent hooks + const subscriptionInfo: SubscriptionInfo = useMemo(() => ({ plan: usageSummary?.plan || initialPlan, status: usageSummary?.status || 'active', features: usageSummary?.usage || {}, loading: isLoading, error: error ? 'Failed to load subscription data' : undefined, - }; + }), [usageSummary?.plan, usageSummary?.status, usageSummary?.usage, initialPlan, isLoading, error]); // Check if user has a specific feature const hasFeature = useCallback(async (featureName: string): Promise => { diff --git a/frontend/src/components/layout/AppShell/AppShell.tsx b/frontend/src/components/layout/AppShell/AppShell.tsx index 11023111..709ac898 100644 --- a/frontend/src/components/layout/AppShell/AppShell.tsx +++ b/frontend/src/components/layout/AppShell/AppShell.tsx @@ -1,8 +1,6 @@ import React, { useState, useCallback, forwardRef } from 'react'; import { clsx } from 'clsx'; -import { useAuthUser, useIsAuthenticated } from '../../../stores'; import { useTheme } from '../../../contexts/ThemeContext'; -import { useTenantInitializer } from '../../../stores/useTenantInitializer'; import { useHasAccess } from '../../../hooks/useAccessControl'; import { Header } from '../Header'; import { Sidebar } from '../Sidebar'; @@ -80,9 +78,6 @@ export const AppShell = forwardRef(({ const { resolvedTheme } = useTheme(); const hasAccess = useHasAccess(); // Check both authentication and demo mode - // Initialize tenant data for authenticated users - useTenantInitializer(); - const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(initialSidebarCollapsed); const [error, setError] = useState(null); diff --git a/frontend/src/components/layout/Sidebar/Sidebar.tsx b/frontend/src/components/layout/Sidebar/Sidebar.tsx index 1a16ebbb..a97f5ff9 100644 --- a/frontend/src/components/layout/Sidebar/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar/Sidebar.tsx @@ -264,23 +264,6 @@ export const Sidebar = forwardRef(({ const allUserRoles = [...globalUserRoles, ...tenantRoles]; const tenantPermissions = currentTenantAccess?.permissions || []; - // Debug logging for analytics route - if (item.path === '/app/analytics') { - console.log('🔍 [Sidebar] Checking analytics menu item:', { - path: item.path, - requiredRoles: item.requiredRoles, - requiredPermissions: item.requiredPermissions, - globalUserRoles, - tenantRoles, - allUserRoles, - tenantPermissions, - isAuthenticated, - hasAccess, - user, - currentTenantAccess - }); - } - // If no specific permissions/roles required, allow access if (!item.requiredPermissions && !item.requiredRoles) { return true; @@ -298,10 +281,6 @@ export const Sidebar = forwardRef(({ tenantPermissions ); - if (item.path === '/app/analytics') { - console.log('🔍 [Sidebar] Analytics canAccessRoute result:', canAccessItem); - } - return canAccessItem; }); }; diff --git a/frontend/src/locales/en/wizards.json b/frontend/src/locales/en/wizards.json index 95cc1285..c8dcc611 100644 --- a/frontend/src/locales/en/wizards.json +++ b/frontend/src/locales/en/wizards.json @@ -1041,10 +1041,14 @@ } }, "itemTypeSelector": { - "searchPlaceholder": "Search by name or category...", - "noResults": "No results found", - "resultSingular": "result", - "resultPlural": "results", + "title": "Select Type", + "description": "Choose what you want to add", + "search": { + "placeholder": "Search by name or category...", + "noResults": "No results found", + "resultSingular": "result", + "resultPlural": "results" + }, "categories": { "all": "All", "daily": "Daily", @@ -1056,6 +1060,63 @@ "daily": "Daily", "common": "Common", "setup": "Setup" + }, + "items": { + "inventory": { + "title": "Inventory", + "subtitle": "Add ingredients or products to your inventory", + "keywords": ["stock", "ingredients", "products", "warehouse"] + }, + "supplier": { + "title": "Supplier", + "subtitle": "Add a new supplier or vendor", + "keywords": ["vendor", "purchases", "supplies"] + }, + "recipe": { + "title": "Recipe", + "subtitle": "Create a new recipe or formula", + "keywords": ["formula", "preparation", "ingredients"] + }, + "equipment": { + "title": "Equipment", + "subtitle": "Register bakery equipment or machinery", + "keywords": ["oven", "mixer", "equipment", "machine"] + }, + "quality-template": { + "title": "Quality Template", + "subtitle": "Create a quality control template", + "keywords": ["control", "quality", "inspection", "verification"] + }, + "customer-order": { + "title": "Customer Order", + "subtitle": "Create a new customer order", + "keywords": ["order", "customer", "sale"] + }, + "customer": { + "title": "Customer", + "subtitle": "Add a new customer", + "keywords": ["customer", "buyer", "contact"] + }, + "team-member": { + "title": "Team Member", + "subtitle": "Add a team member or employee", + "keywords": ["employee", "worker", "staff"] + }, + "sales-entry": { + "title": "Sales Entry", + "subtitle": "Record a sales transaction", + "keywords": ["sale", "cash", "revenue", "transaction"] + }, + "purchase-order": { + "title": "Purchase Order", + "subtitle": "Create a purchase order to supplier", + "keywords": ["purchase", "order", "supplier"] + }, + "production-batch": { + "title": "Production Batch", + "subtitle": "Create a new production batch", + "keywords": ["production", "batch", "manufacturing"] + } } } } diff --git a/frontend/src/locales/es/wizards.json b/frontend/src/locales/es/wizards.json index 64d8a245..b9c10c60 100644 --- a/frontend/src/locales/es/wizards.json +++ b/frontend/src/locales/es/wizards.json @@ -1426,10 +1426,14 @@ } }, "itemTypeSelector": { - "searchPlaceholder": "Buscar por nombre o categoría...", - "noResults": "No se encontraron resultados", - "resultSingular": "resultado", - "resultPlural": "resultados", + "title": "Seleccionar Tipo", + "description": "Elige qué deseas agregar", + "search": { + "placeholder": "Buscar por nombre o categoría...", + "noResults": "No se encontraron resultados", + "resultSingular": "resultado", + "resultPlural": "resultados" + }, "categories": { "all": "Todos", "daily": "Diario", @@ -1441,6 +1445,63 @@ "daily": "Diario", "common": "Común", "setup": "Configuración" + }, + "items": { + "inventory": { + "title": "Inventario", + "subtitle": "Agregar ingredientes o productos a tu inventario", + "keywords": ["stock", "ingredientes", "productos", "almacén"] + }, + "supplier": { + "title": "Proveedor", + "subtitle": "Agregar un nuevo proveedor o vendedor", + "keywords": ["vendedor", "compras", "suministros"] + }, + "recipe": { + "title": "Receta", + "subtitle": "Crear una nueva receta o fórmula", + "keywords": ["fórmula", "preparación", "ingredientes"] + }, + "equipment": { + "title": "Maquinaria", + "subtitle": "Registrar equipo o maquinaria de panadería", + "keywords": ["horno", "amasadora", "equipo", "máquina"] + }, + "quality-template": { + "title": "Plantilla de Calidad", + "subtitle": "Crear una plantilla de control de calidad", + "keywords": ["control", "calidad", "inspección", "verificación"] + }, + "customer-order": { + "title": "Pedido de Cliente", + "subtitle": "Crear un nuevo pedido de cliente", + "keywords": ["pedido", "orden", "cliente", "venta"] + }, + "customer": { + "title": "Cliente", + "subtitle": "Agregar un nuevo cliente", + "keywords": ["cliente", "comprador", "contacto"] + }, + "team-member": { + "title": "Miembro del Equipo", + "subtitle": "Agregar un miembro del equipo o empleado", + "keywords": ["empleado", "trabajador", "personal", "staff"] + }, + "sales-entry": { + "title": "Registro de Ventas", + "subtitle": "Registrar una transacción de venta", + "keywords": ["venta", "caja", "ingreso", "transacción"] + }, + "purchase-order": { + "title": "Orden de Compra", + "subtitle": "Crear una orden de compra a proveedor", + "keywords": ["compra", "orden", "proveedor", "pedido"] + }, + "production-batch": { + "title": "Lote de Producción", + "subtitle": "Crear un nuevo lote de producción", + "keywords": ["producción", "lote", "fabricación", "elaboración"] + } } } } diff --git a/frontend/src/locales/eu/wizards.json b/frontend/src/locales/eu/wizards.json index 49f7e47a..7370325b 100644 --- a/frontend/src/locales/eu/wizards.json +++ b/frontend/src/locales/eu/wizards.json @@ -1035,10 +1035,14 @@ } }, "itemTypeSelector": { - "searchPlaceholder": "Bilatu izenaren edo kategoriaren arabera...", - "noResults": "Ez da emaitzarik aurkitu", - "resultSingular": "emaitza", - "resultPlural": "emaitzak", + "title": "Mota Hautatu", + "description": "Aukeratu zer gehitu nahi duzun", + "search": { + "placeholder": "Bilatu izenaren edo kategoriaren arabera...", + "noResults": "Ez da emaitzarik aurkitu", + "resultSingular": "emaitza", + "resultPlural": "emaitzak" + }, "categories": { "all": "Guztiak", "daily": "Egunerokoa", @@ -1050,6 +1054,63 @@ "daily": "Egunerokoa", "common": "Arrunta", "setup": "Konfigurazioa" + }, + "items": { + "inventory": { + "title": "Inbentarioa", + "subtitle": "Gehitu osagaiak edo produktuak zure inbentariora", + "keywords": ["stock", "osagaiak", "produktuak", "biltegi"] + }, + "supplier": { + "title": "Hornitzailea", + "subtitle": "Gehitu hornitzaile edo saltzaile berri bat", + "keywords": ["saltzaile", "erosketak", "hornidura"] + }, + "recipe": { + "title": "Errezeta", + "subtitle": "Sortu errezeta edo formula berri bat", + "keywords": ["formula", "prestaketa", "osagaiak"] + }, + "equipment": { + "title": "Ekipamendua", + "subtitle": "Erregistratu okindegiaren ekipamendua edo makina", + "keywords": ["labea", "oramailea", "ekipamendua", "makina"] + }, + "quality-template": { + "title": "Kalitate Txantiloia", + "subtitle": "Sortu kalitate kontrol txantiloi bat", + "keywords": ["kontrola", "kalitatea", "ikuskatzea", "egiaztapena"] + }, + "customer-order": { + "title": "Bezeroaren Eskaera", + "subtitle": "Sortu bezero eskaera berri bat", + "keywords": ["eskaera", "bezeroa", "salmenta"] + }, + "customer": { + "title": "Bezeroa", + "subtitle": "Gehitu bezero berri bat", + "keywords": ["bezeroa", "eroslea", "kontaktua"] + }, + "team-member": { + "title": "Taldeko Kidea", + "subtitle": "Gehitu taldeko kide edo langile bat", + "keywords": ["langilea", "enplegatu", "pertsonal"] + }, + "sales-entry": { + "title": "Salmenta Erregistroa", + "subtitle": "Erregistratu salmenta transakzio bat", + "keywords": ["salmenta", "kutxa", "diru-sarrera", "transakzioa"] + }, + "purchase-order": { + "title": "Erosketa Eskaera", + "subtitle": "Sortu erosketa eskaera hornitzaileari", + "keywords": ["erosketa", "eskaera", "hornitzailea"] + }, + "production-batch": { + "title": "Ekoizpen Lotea", + "subtitle": "Sortu ekoizpen lote berri bat", + "keywords": ["ekoizpena", "lotea", "fabrikazioa"] + } } } } diff --git a/frontend/src/stores/useTenantInitializer.ts b/frontend/src/stores/useTenantInitializer.ts index 0b1b5330..5de1d7d1 100644 --- a/frontend/src/stores/useTenantInitializer.ts +++ b/frontend/src/stores/useTenantInitializer.ts @@ -69,18 +69,8 @@ export const useTenantInitializer = () => { const virtualTenantId = localStorage.getItem('virtual_tenant_id'); const storedTier = localStorage.getItem('subscription_tier'); - console.log('🔍 [TenantInitializer] Demo mode detected:', { - isDemoMode, - demoSessionId, - virtualTenantId, - demoAccountType, - storedTier, - currentTenant: currentTenant?.id - }); - // Guard: If no virtual_tenant_id is available, skip tenant setup if (!virtualTenantId) { - console.warn('⚠️ [TenantInitializer] No virtual_tenant_id found in localStorage'); return; } @@ -90,8 +80,6 @@ export const useTenantInitializer = () => { currentTenant.id === virtualTenantId; if (!isValidDemoTenant) { - console.log('🔧 [TenantInitializer] Setting up demo tenant...'); - // Determine the appropriate subscription tier based on stored value or account type const subscriptionTier = storedTier as SubscriptionTier || getDemoTierForAccountType(demoAccountType); @@ -111,50 +99,40 @@ export const useTenantInitializer = () => { postal_code: '28001', phone: null, is_active: true, - subscription_plan: subscriptionTier, // New field name - subscription_tier: subscriptionTier, // Deprecated but kept for backward compatibility + subscription_plan: subscriptionTier, + subscription_tier: subscriptionTier, ml_model_trained: false, last_training_date: null, owner_id: 'demo-user', created_at: new Date().toISOString(), }; - console.log(`✅ [TenantInitializer] Setting up tenant with tier: ${subscriptionTier}`); - // Set the demo tenant as current setCurrentTenant(mockTenant); - // **CRITICAL: Also set tenant ID in API client** - // This ensures API requests include the tenant ID header + // Set tenant ID in API client import('../api/client').then(({ apiClient }) => { apiClient.setTenantId(virtualTenantId); - console.log('✅ [TenantInitializer] Set API client tenant ID:', virtualTenantId); }); - // For enterprise demos, load child tenants immediately (session is already ready when we navigate here) + // For enterprise demos, load child tenants immediately if (demoAccountType === 'enterprise') { - console.log('🔄 [TenantInitializer] Loading available tenants for enterprise demo...'); const mockUserId = 'demo-user'; import('../api/services/tenant').then(({ TenantService }) => { const tenantService = new TenantService(); tenantService.getUserTenants(mockUserId) .then(tenants => { - console.log('📋 [TenantInitializer] Loaded available tenants:', tenants.length); - if (tenants.length === 0) { - console.warn('⚠️ [TenantInitializer] No child tenants found yet - they may still be cloning'); - } - // Update the tenant store with available tenants import('../stores/tenant.store').then(({ useTenantStore }) => { useTenantStore.getState().setAvailableTenants(tenants); }); }) - .catch(error => { - console.error('❌ [TenantInitializer] Failed to load available tenants:', error); + .catch(() => { + // Silently handle error }); }); } } } }, [isDemoMode, demoSessionId, demoAccountType, currentTenant, setCurrentTenant]); -};; \ No newline at end of file +}; \ No newline at end of file