diff --git a/containers/libreportal/frontend/js/components/app/apps-manager.js b/containers/libreportal/frontend/js/components/app/apps-manager.js index e87f6bd..183b812 100755 --- a/containers/libreportal/frontend/js/components/app/apps-manager.js +++ b/containers/libreportal/frontend/js/components/app/apps-manager.js @@ -997,72 +997,52 @@ class AppsManager { let tabsHTML = ''; let contentHTML = ''; - let firstTab = null; - + // Sort categories by order const sortedCategories = Object.entries(categories) .sort(([,a], [,b]) => a.order - b.order); - - // Find first tab with fields + + // Render each category's fields up front and keep only the ones that + // actually produced fields. A "hasFields" heuristic used to gate the tabs + // separately, but it drifted from what generateConfigFields really emits — + // so a category like Network could pass the check yet render empty, leaving + // a blank tab whose body just reads "No configuration options available". + // Trusting the rendered output keeps tabs and content in lockstep. + const renderedCategories = []; for (const [key, category] of sortedCategories) { - - const hasFields = Object.entries(fieldMappings).some(([fieldKey, fieldConfig]) => { - if (fieldConfig.category === key) { - const cfgKey = this.findMatchingCFGKey(fieldKey, appConfig); - return cfgKey && appConfig.hasOwnProperty(cfgKey); - } - return false; - }); - - if (hasFields) { - firstTab = key; - break; - } + const content = await this.generateConfigFields(key, appData); + if (!content || content.includes('class="no-fields"')) continue; + renderedCategories.push({ key, category, content }); } - - // Use preferred category if available and valid, otherwise use firstTab - const activeTab = preferredCategory && categories[preferredCategory] ? preferredCategory : firstTab; - - // Generate tabs and content together - for (const [key, category] of sortedCategories) { - const hasFields = Object.entries(fieldMappings).some(([fieldKey, fieldConfig]) => { - if (fieldConfig.category === key) { - const cfgKey = this.findMatchingCFGKey(fieldKey, appConfig); - return cfgKey && appConfig.hasOwnProperty(cfgKey); - } - return false; - }); - - if (hasFields) { - const isActive = key === activeTab ? 'active' : ''; - - // Generate tab button - tabsHTML += ` + + // Use the preferred category if it's one that has fields, else the first. + const activeTab = (preferredCategory && renderedCategories.some(c => c.key === preferredCategory)) + ? preferredCategory + : (renderedCategories[0] ? renderedCategories[0].key : null); + + for (const { key, category, content } of renderedCategories) { + const isActive = key === activeTab ? 'active' : ''; + + tabsHTML += ` `; - - // Generate content panel - //// // console.log(`🔧 Generating content for category: ${key}`); - const categoryContent = await this.generateConfigFields(key, appData); - - contentHTML += ` + + contentHTML += `
${category.description}