/** * taggcodeApp.js * Dynamische AssetMenu implementatie met TagcodeApp API * Vervangt de statische assetData met calls naar REST-endpoints. */ class TagcodeApp { constructor(baseURL = 'https://pimmoerman.nl/rdlab/tagcode.app/v2.1/api') { this.baseURL = baseURL; } async fetchData(path, params = {}) { const url = new URL(`${this.baseURL}/${path}`); Object.entries(params).forEach(([key, value]) => { url.searchParams.append(key, value); }); const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); const json = await response.json(); if (!json.success) throw new Error(json.error || json.message); return json.data; } // Asset endpoints getAllAssets() { return this.fetchData('asset/get_all_assets.php'); } getAssetDetail(tag_code) { return this.fetchData('asset/get_detail_asset.php', { tag_code }); } getAssetHistory(asset_tag_number) { return this.fetchData('asset/get_history_asset.php', { asset_tag_number }); } getAssetHierarchy(asset_tag_number) { return this.fetchData('asset/get_asset_hierarchy.php', { asset_tag_number }); } createOrUpdateAsset(params) { return this.fetchData('asset/create_asset.php', params); } // Product & vendor endpoints getVendors() { return this.fetchData('vendor/get_vendors.php'); } getSubtypes(vendor_name) { return this.fetchData('product/get_subtypesFromVendor.php', { vendor_name }); } getSubtypesForCategory(vendor_name, category) { return this.fetchData('product/get_subtypesFromVendorAndCategory.php', { vendor_name, category }); } getProductModels(vendor_name, product_subtype_name) { return this.fetchData('product/get_product_models.php', { vendor_name, product_subtype_name }); } getLocations() { return this.fetchData('location/get_locations.php'); } } class DynamicAssetMenu { constructor(nodeName, api = new TagcodeApp()) { this.nodeName = nodeName; this.api = api; //temp translation table for nodeName to API // Mapping van nodeName naar softwareType this.softwareTypeMapping = { 'measurement': 'Sensor', 'rotatingMachine': 'machine', 'valve': 'valve', 'pump': 'machine', 'heatExchanger': 'machine', // Voeg meer mappings toe als nodig }; // Bepaal automatisch de softwareType this.softwareType = this.softwareTypeMapping[nodeName] || nodeName; this.data = { vendors: [], subtypes: {}, models: {} }; } //Added missing getAllMenuData method getAllMenuData() { return { vendors: this.data.vendors || [], locations: this.data.locations || [], htmlTemplate: this.getHtmlTemplate() }; } /** * Initialiseer: haal alleen de vendor-lijst en locaties op */ async init() { try { this.data.suppliers = await this.api.getVendors(); this.data.locations = await this.api.getLocations(); } catch (error) { console.error('Failed to initialize DynamicAssetMenu:', error); this.data.suppliers = []; this.data.locations = []; } } //Complete getClientInitCode method with full TagcodeApp definition getClientInitCode(nodeName) { return ` // --- DynamicAssetMenu voor ${nodeName} --- // ✅ Define COMPLETE TagcodeApp class in browser context window.TagcodeApp = window.TagcodeApp || class { constructor(baseURL = 'https://pimmoerman.nl/rdlab/tagcode.app/v2.1/api') { this.baseURL = baseURL; } async fetchData(path, params = {}) { const url = new URL(this.baseURL + '/' + path); Object.entries(params).forEach(([key, value]) => { url.searchParams.append(key, value); }); const response = await fetch(url); if (!response.ok) throw new Error('HTTP ' + response.status + ': ' + response.statusText); const json = await response.json(); if (!json.success) throw new Error(json.error || json.message); return json.data; } // ✅ ALL API methods defined here getAllAssets() { return this.fetchData('asset/get_all_assets.php'); } getAssetDetail(tag_code) { return this.fetchData('asset/get_detail_asset.php', { tag_code }); } getVendors() { return this.fetchData('vendor/get_vendors.php'); } getSubtypes(vendor_name, category = null) { const params = { vendor_name }; if (category) params.category = category; return this.fetchData('product/get_subtypesFromVendor.php', params); } getProductModels(vendor_name, product_subtype_name) { return this.fetchData('product/get_product_models.php', { vendor_name, product_subtype_name }); } getLocations() { return this.fetchData('location/get_locations.php'); } }; // ✅ Initialize the API instance BEFORE it's needed window.assetAPI = window.assetAPI || new window.TagcodeApp(); // Helper populate function function populate(el, opts, sel) { if (!el) return; const old = el.value; el.innerHTML = ''; (opts||[]).forEach(o=>{ const opt = document.createElement('option'); opt.value = o; opt.textContent = o; el.appendChild(opt); }); el.value = sel || ''; if (el.value !== old) el.dispatchEvent(new Event('change')); } // ✅ Ensure namespace exists and initialize properly if (!window.EVOLV.nodes.${nodeName}.assetMenu) { window.EVOLV.nodes.${nodeName}.assetMenu = {}; } // ✅ Complete initEditor function window.EVOLV.nodes.${nodeName}.assetMenu.initEditor = async function(node) { try { console.log('🚀 Starting asset menu initialization for ${nodeName}'); console.log('🎯 Automatic softwareType: ${this.softwareType}'); // ✅ Verify API is available if (!window.assetAPI) { console.error('❌ window.assetAPI not available'); return; } // ✅ Wait for DOM to be ready and inject HTML with retry const waitForDialogAndInject = () => { return new Promise((resolve) => { let attempts = 0; const maxAttempts = 20; const tryInject = () => { attempts++; console.log('Injection attempt ' + attempts + '/' + maxAttempts); const injectionSuccess = this.injectHtml ? this.injectHtml() : false; if (injectionSuccess) { console.log('✅ HTML injection successful on attempt:', attempts); resolve(true); } else if (attempts < maxAttempts) { setTimeout(tryInject, 100); } else { console.warn('⚠️ HTML injection failed after ' + maxAttempts + ' attempts'); resolve(false); } }; setTimeout(tryInject, 200); }); }; // Wait for HTML injection const htmlReady = await waitForDialogAndInject(); if (!htmlReady) { console.error('❌ Could not inject HTML, continuing without asset menu'); return; } console.log('🔧 Setting up asset menu functionality'); // ✅ Load vendor list with error handling try { console.log('📡 Loading vendors...'); const vendors = await window.assetAPI.getVendors(); console.log('✅ Vendors loaded:', vendors.length); // ✅ Handle both string arrays and object arrays const vendorNames = vendors.map(v => v.name || v); populate(document.getElementById('node-input-supplier'), vendorNames, node.supplier); } catch (vendorError) { console.error('❌ Error loading vendors:', vendorError); } // ✅ Get form elements const elems = { supplier: document.getElementById('node-input-supplier'), category: document.getElementById('node-input-category'), type: document.getElementById('node-input-assetType'), model: document.getElementById('node-input-model'), unit: document.getElementById('node-input-unit') }; // ✅ Set automatic category value if (elems.category) { elems.category.value = '${this.softwareType}'; console.log('✅ Automatic category set to:', elems.category.value); } // ✅ Supplier change: load subtypes for automatic category if (elems.supplier) { elems.supplier.addEventListener('change', async () => { const vendor = elems.supplier.value; const category = '${this.softwareType}'; if (!vendor) { populate(elems.type, [], ''); populate(elems.model, [], ''); populate(elems.unit, [], ''); return; } try { console.log('📡 Loading subtypes for vendor:', vendor, 'category:', category); const subtypes = await window.assetAPI.getSubtypes(vendor, category); console.log('✅ Subtypes loaded:', subtypes.length); const subtypeNames = subtypes.map(s => s.name || s.subtype_name || s); populate(elems.type, subtypeNames, node.assetType); populate(elems.model, [], ''); populate(elems.unit, [], ''); } catch (error) { console.error('❌ Error loading subtypes:', error); populate(elems.type, [], ''); } }); } // ✅ Type change: load models for vendor + selected subtype if (elems.type) { elems.type.addEventListener('change', async () => { const vendor = elems.supplier.value; const selectedSubtype = elems.type.value; if (!vendor || !selectedSubtype) { populate(elems.model, [], ''); populate(elems.unit, [], ''); return; } try { console.log('📡 Loading models for vendor:', vendor, 'subtype:', selectedSubtype); const models = await window.assetAPI.getProductModels(vendor, selectedSubtype); console.log('✅ Models loaded:', models.length); window._currentModels = models; const modelNames = models.map(m => m.name || m.model_name || m); populate(elems.model, modelNames, node.model); populate(elems.unit, [], ''); } catch (error) { console.error('❌ Error loading models:', error); populate(elems.model, [], ''); } }); } // ✅ Model change: show units for selected model if (elems.model) { elems.model.addEventListener('change', () => { const selectedModelName = elems.model.value; const models = window._currentModels || []; const selectedModel = models.find(m => (m.name || m.model_name) === selectedModelName ); const units = selectedModel && selectedModel.product_model_meta ? Object.keys(selectedModel.product_model_meta) : []; populate(elems.unit, units, node.unit); }); } // ✅ Trigger supplier change if there's a saved value if (node.supplier && elems.supplier) { setTimeout(() => { elems.supplier.dispatchEvent(new Event('change')); }, 100); } console.log('✅ Asset menu initialization complete for ${nodeName}'); } catch (error) { console.error('❌ Error in asset menu initialization:', error); } }; `; } getHtmlTemplate() { return `