feat: separate fusion field service and LTC into standalone modules, update core modules
- fusion_claims: separated field service logic, updated controllers/views - fusion_tasks: updated task views and map integration - fusion_authorizer_portal: added page 11 signing, schedule booking, migrations - fusion_shipping: new standalone shipping module (Canada Post, FedEx, DHL, Purolator) - fusion_ltc_management: new standalone LTC management module
This commit is contained in:
BIN
fusion_ltc_management/static/description/icon.png
Normal file
BIN
fusion_ltc_management/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,578 @@
|
||||
/** @odoo-module **/
|
||||
// Fusion LTC Management - Google Address Autocomplete for LTC Facilities
|
||||
// Copyright 2024-2026 Nexa Systems Inc.
|
||||
// License OPL-1
|
||||
|
||||
import { FormController } from "@web/views/form/form_controller";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { onMounted, onWillUnmount } from "@odoo/owl";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
// Module-scoped state
|
||||
let googleMapsLoaded = false;
|
||||
let googleMapsLoading = false;
|
||||
let googleMapsApiKey = null;
|
||||
let globalOrm = null;
|
||||
const autocompleteInstances = new Map();
|
||||
|
||||
/**
|
||||
* Load Google Maps API dynamically
|
||||
*/
|
||||
async function loadGoogleMapsApi(apiKey) {
|
||||
if (googleMapsLoaded) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (googleMapsLoading) {
|
||||
return new Promise((resolve) => {
|
||||
const checkLoaded = setInterval(() => {
|
||||
if (googleMapsLoaded) {
|
||||
clearInterval(checkLoaded);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
googleMapsLoading = true;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Check if already loaded by another module (e.g. fusion_claims)
|
||||
if (window.google && window.google.maps && window.google.maps.places) {
|
||||
googleMapsLoaded = true;
|
||||
googleMapsLoading = false;
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if script tag already exists
|
||||
const existingScript = document.querySelector('script[src*="maps.googleapis.com/maps/api/js"]');
|
||||
if (existingScript) {
|
||||
const checkReady = setInterval(() => {
|
||||
if (window.google && window.google.maps && window.google.maps.places) {
|
||||
googleMapsLoaded = true;
|
||||
googleMapsLoading = false;
|
||||
clearInterval(checkReady);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
window.initGoogleMapsAutocompleteLTC = () => {
|
||||
googleMapsLoaded = true;
|
||||
googleMapsLoading = false;
|
||||
resolve();
|
||||
};
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places&callback=initGoogleMapsAutocompleteLTC`;
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
script.onerror = () => {
|
||||
googleMapsLoading = false;
|
||||
reject(new Error('Failed to load Google Maps API'));
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API key from Odoo config (uses same key as fusion_claims)
|
||||
*/
|
||||
async function getGoogleMapsApiKey(orm) {
|
||||
if (googleMapsApiKey) {
|
||||
return googleMapsApiKey;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await orm.call(
|
||||
'ir.config_parameter',
|
||||
'get_param',
|
||||
['fusion_claims.google_maps_api_key']
|
||||
);
|
||||
googleMapsApiKey = result || null;
|
||||
return googleMapsApiKey;
|
||||
} catch (error) {
|
||||
console.warn('[LTC GooglePlaces] Could not fetch Google Maps API key:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate Many2One field selection by finding the widget and triggering its update
|
||||
*/
|
||||
async function simulateMany2OneSelection(formEl, fieldName, valueId, displayName) {
|
||||
const fieldSelectors = [
|
||||
`[name="${fieldName}"]`,
|
||||
`.o_field_widget[name="${fieldName}"]`,
|
||||
`div[name="${fieldName}"]`,
|
||||
];
|
||||
|
||||
let fieldContainer = null;
|
||||
for (const selector of fieldSelectors) {
|
||||
fieldContainer = formEl.querySelector(selector);
|
||||
if (fieldContainer) break;
|
||||
}
|
||||
|
||||
if (!fieldContainer) return false;
|
||||
|
||||
// Try OWL component approach
|
||||
let owlComponent = null;
|
||||
let el = fieldContainer;
|
||||
while (el && !owlComponent) {
|
||||
if (el.__owl__) {
|
||||
owlComponent = el.__owl__;
|
||||
break;
|
||||
}
|
||||
el = el.parentElement;
|
||||
}
|
||||
|
||||
if (owlComponent && owlComponent.component) {
|
||||
const component = owlComponent.component;
|
||||
|
||||
if (component.props && component.props.record) {
|
||||
try {
|
||||
await component.props.record.update({ [fieldName]: valueId });
|
||||
return true;
|
||||
} catch (_) { /* fallthrough */ }
|
||||
}
|
||||
|
||||
if (typeof component.updateValue === 'function') {
|
||||
try {
|
||||
await component.updateValue(valueId);
|
||||
return true;
|
||||
} catch (_) { /* fallthrough */ }
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: manipulate input directly
|
||||
const inputEl = fieldContainer.querySelector('input:not([type="hidden"])');
|
||||
if (inputEl) {
|
||||
inputEl.focus();
|
||||
inputEl.value = '';
|
||||
inputEl.value = displayName;
|
||||
|
||||
inputEl.dispatchEvent(new InputEvent('input', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
data: displayName,
|
||||
inputType: 'insertText'
|
||||
}));
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 250));
|
||||
|
||||
const dropdownSelectors = [
|
||||
'.o-autocomplete--dropdown-menu .o-autocomplete--dropdown-item',
|
||||
'.o_m2o_dropdown_option',
|
||||
'.dropdown-menu .dropdown-item',
|
||||
'.o-autocomplete .dropdown-item',
|
||||
'ul.ui-autocomplete li',
|
||||
'.o_field_many2one_selection li',
|
||||
];
|
||||
|
||||
let found = false;
|
||||
for (const selector of dropdownSelectors) {
|
||||
const dropdownItems = document.querySelectorAll(selector);
|
||||
for (const item of dropdownItems) {
|
||||
const itemText = item.textContent.trim();
|
||||
if (itemText.includes(displayName) || displayName.includes(itemText)) {
|
||||
item.click();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) break;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
inputEl.dispatchEvent(new KeyboardEvent('keydown', {
|
||||
key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true
|
||||
}));
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
inputEl.dispatchEvent(new KeyboardEvent('keydown', {
|
||||
key: 'Tab', code: 'Tab', keyCode: 9, bubbles: true
|
||||
}));
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
inputEl.blur();
|
||||
return found;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup autocomplete for LTC Facility form.
|
||||
* Attaches establishment search on the name field and address search on street.
|
||||
*/
|
||||
async function setupFacilityAutocomplete(el, model, orm) {
|
||||
globalOrm = orm;
|
||||
|
||||
const apiKey = await getGoogleMapsApiKey(orm);
|
||||
if (!apiKey) return;
|
||||
try { await loadGoogleMapsApi(apiKey); } catch (e) { return; }
|
||||
|
||||
// --- Name field: establishment autocomplete ---
|
||||
const nameSelectors = [
|
||||
'.oe_title [name="name"] input',
|
||||
'div[name="name"] input',
|
||||
'.o_field_widget[name="name"] input',
|
||||
'[name="name"] input',
|
||||
];
|
||||
|
||||
let nameInput = null;
|
||||
for (const sel of nameSelectors) {
|
||||
nameInput = el.querySelector(sel);
|
||||
if (nameInput) break;
|
||||
}
|
||||
|
||||
if (nameInput && !autocompleteInstances.has('facility_name_' + (nameInput.id || 'default'))) {
|
||||
_attachFacilityNameAutocomplete(nameInput, el, model);
|
||||
}
|
||||
|
||||
// --- Street field: address autocomplete ---
|
||||
const streetSelectors = [
|
||||
'div[name="street"] input',
|
||||
'.o_field_widget[name="street"] input',
|
||||
'[name="street"] input',
|
||||
];
|
||||
|
||||
let streetInput = null;
|
||||
for (const sel of streetSelectors) {
|
||||
streetInput = el.querySelector(sel);
|
||||
if (streetInput) break;
|
||||
}
|
||||
|
||||
if (streetInput && !autocompleteInstances.has(streetInput)) {
|
||||
_attachFacilityAddressAutocomplete(streetInput, el, model);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach establishment (business) autocomplete on facility name field.
|
||||
* Selecting a business fills name, address, phone, email, and website.
|
||||
*/
|
||||
function _attachFacilityNameAutocomplete(input, el, model) {
|
||||
if (!input || !window.google?.maps?.places) return;
|
||||
|
||||
const instanceKey = 'facility_name_' + (input.id || 'default');
|
||||
if (autocompleteInstances.has(instanceKey)) return;
|
||||
|
||||
const autocomplete = new google.maps.places.Autocomplete(input, {
|
||||
componentRestrictions: { country: 'ca' },
|
||||
types: ['establishment'],
|
||||
fields: [
|
||||
'place_id', 'name', 'address_components', 'formatted_address',
|
||||
'formatted_phone_number', 'international_phone_number', 'website',
|
||||
],
|
||||
});
|
||||
|
||||
autocomplete.addListener('place_changed', async () => {
|
||||
let place = autocomplete.getPlace();
|
||||
if (!place.name && !place.place_id) return;
|
||||
|
||||
if (place.place_id && !place.formatted_phone_number && !place.website) {
|
||||
try {
|
||||
const service = new google.maps.places.PlacesService(document.createElement('div'));
|
||||
const details = await new Promise((resolve, reject) => {
|
||||
service.getDetails(
|
||||
{
|
||||
placeId: place.place_id,
|
||||
fields: ['formatted_phone_number', 'international_phone_number', 'website'],
|
||||
},
|
||||
(result, status) => {
|
||||
if (status === google.maps.places.PlacesServiceStatus.OK) resolve(result);
|
||||
else reject(new Error(status));
|
||||
}
|
||||
);
|
||||
});
|
||||
if (details.formatted_phone_number) place.formatted_phone_number = details.formatted_phone_number;
|
||||
if (details.international_phone_number) place.international_phone_number = details.international_phone_number;
|
||||
if (details.website) place.website = details.website;
|
||||
} catch (_) { /* ignore */ }
|
||||
}
|
||||
|
||||
let streetNumber = '', streetName = '', unitNumber = '';
|
||||
let city = '', province = '', postalCode = '', countryCode = '';
|
||||
|
||||
if (place.address_components) {
|
||||
for (const c of place.address_components) {
|
||||
const t = c.types;
|
||||
if (t.includes('street_number')) streetNumber = c.long_name;
|
||||
else if (t.includes('route')) streetName = c.long_name;
|
||||
else if (t.includes('subpremise')) unitNumber = c.long_name;
|
||||
else if (t.includes('floor') && !unitNumber) unitNumber = 'Floor ' + c.long_name;
|
||||
else if (t.includes('locality')) city = c.long_name;
|
||||
else if (t.includes('sublocality_level_1') && !city) city = c.long_name;
|
||||
else if (t.includes('administrative_area_level_1')) province = c.short_name;
|
||||
else if (t.includes('postal_code')) postalCode = c.long_name;
|
||||
else if (t.includes('country')) countryCode = c.short_name;
|
||||
}
|
||||
}
|
||||
|
||||
const street = streetNumber ? `${streetNumber} ${streetName}` : streetName;
|
||||
const phone = place.formatted_phone_number || place.international_phone_number || '';
|
||||
|
||||
if (!model?.root) return;
|
||||
const record = model.root;
|
||||
|
||||
let countryId = null, stateId = null;
|
||||
if (globalOrm && countryCode) {
|
||||
try {
|
||||
const [countries, states] = await Promise.all([
|
||||
globalOrm.searchRead('res.country', [['code', '=', countryCode]], ['id'], { limit: 1 }),
|
||||
province
|
||||
? globalOrm.searchRead('res.country.state', [['code', '=', province], ['country_id.code', '=', countryCode]], ['id'], { limit: 1 })
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
if (countries.length) countryId = countries[0].id;
|
||||
if (states.length) stateId = states[0].id;
|
||||
} catch (_) { /* ignore */ }
|
||||
}
|
||||
|
||||
if (record.resId && globalOrm) {
|
||||
try {
|
||||
const firstWrite = {};
|
||||
if (place.name) firstWrite.name = place.name;
|
||||
if (street) firstWrite.street = street;
|
||||
if (unitNumber) firstWrite.street2 = unitNumber;
|
||||
if (city) firstWrite.city = city;
|
||||
if (postalCode) firstWrite.zip = postalCode;
|
||||
if (phone) firstWrite.phone = phone;
|
||||
if (place.website) firstWrite.website = place.website;
|
||||
if (countryId) firstWrite.country_id = countryId;
|
||||
|
||||
await globalOrm.write('fusion.ltc.facility', [record.resId], firstWrite);
|
||||
|
||||
if (stateId) {
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
await globalOrm.write('fusion.ltc.facility', [record.resId], { state_id: stateId });
|
||||
}
|
||||
|
||||
await record.load();
|
||||
} catch (err) {
|
||||
console.error('[LTC GooglePlaces] Name autocomplete ORM write failed:', err);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const textUpdate = {};
|
||||
if (place.name) textUpdate.name = place.name;
|
||||
if (street) textUpdate.street = street;
|
||||
if (unitNumber) textUpdate.street2 = unitNumber;
|
||||
if (city) textUpdate.city = city;
|
||||
if (postalCode) textUpdate.zip = postalCode;
|
||||
if (phone) textUpdate.phone = phone;
|
||||
if (place.website) textUpdate.website = place.website;
|
||||
|
||||
await record.update(textUpdate);
|
||||
|
||||
if (countryId && globalOrm) {
|
||||
const formEl = input.closest('.o_form_view') || input.closest('.o_content') || document.body;
|
||||
const [countryData, stateData] = await Promise.all([
|
||||
globalOrm.read('res.country', [countryId], ['display_name']),
|
||||
stateId ? globalOrm.read('res.country.state', [stateId], ['display_name']) : Promise.resolve([]),
|
||||
]);
|
||||
|
||||
await simulateMany2OneSelection(formEl, 'country_id', countryId, countryData[0]?.display_name || 'Canada');
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
|
||||
if (stateId && stateData.length) {
|
||||
await simulateMany2OneSelection(formEl, 'state_id', stateId, stateData[0]?.display_name || province);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[LTC GooglePlaces] Name autocomplete update failed:', err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
autocompleteInstances.set(instanceKey, autocomplete);
|
||||
|
||||
input.style.backgroundImage = 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 24 24\' fill=\'%232196F3\'%3E%3Cpath d=\'M12 7V3H2v18h20V7H12zM6 19H4v-2h2v2zm0-4H4v-2h2v2zm0-4H4V9h2v2zm0-4H4V5h2v2zm4 12H8v-2h2v2zm0-4H8v-2h2v2zm0-4H8V9h2v2zm0-4H8V5h2v2zm10 12h-8v-2h2v-2h-2v-2h2v-2h-2V9h8v10zm-2-8h-2v2h2v-2zm0 4h-2v2h2v-2z\'/%3E%3C/svg%3E")';
|
||||
input.style.backgroundRepeat = 'no-repeat';
|
||||
input.style.backgroundPosition = 'right 8px center';
|
||||
input.style.backgroundSize = '20px';
|
||||
input.style.paddingRight = '35px';
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach address autocomplete on facility street field.
|
||||
* Fills street, street2, city, state, zip, and country.
|
||||
*/
|
||||
function _attachFacilityAddressAutocomplete(input, el, model) {
|
||||
if (!input || !window.google?.maps?.places) return;
|
||||
if (autocompleteInstances.has(input)) return;
|
||||
|
||||
const autocomplete = new google.maps.places.Autocomplete(input, {
|
||||
componentRestrictions: { country: 'ca' },
|
||||
types: ['address'],
|
||||
fields: ['address_components', 'formatted_address'],
|
||||
});
|
||||
|
||||
autocomplete.addListener('place_changed', async () => {
|
||||
const place = autocomplete.getPlace();
|
||||
if (!place.address_components) return;
|
||||
|
||||
let streetNumber = '', streetName = '', unitNumber = '';
|
||||
let city = '', province = '', postalCode = '', countryCode = '';
|
||||
|
||||
for (const c of place.address_components) {
|
||||
const t = c.types;
|
||||
if (t.includes('street_number')) streetNumber = c.long_name;
|
||||
else if (t.includes('route')) streetName = c.long_name;
|
||||
else if (t.includes('subpremise')) unitNumber = c.long_name;
|
||||
else if (t.includes('floor') && !unitNumber) unitNumber = 'Floor ' + c.long_name;
|
||||
else if (t.includes('locality')) city = c.long_name;
|
||||
else if (t.includes('sublocality_level_1') && !city) city = c.long_name;
|
||||
else if (t.includes('administrative_area_level_1')) province = c.short_name;
|
||||
else if (t.includes('postal_code')) postalCode = c.long_name;
|
||||
else if (t.includes('country')) countryCode = c.short_name;
|
||||
}
|
||||
|
||||
const street = streetNumber ? `${streetNumber} ${streetName}` : streetName;
|
||||
|
||||
if (!model?.root) return;
|
||||
const record = model.root;
|
||||
|
||||
let countryId = null, stateId = null;
|
||||
if (globalOrm && countryCode) {
|
||||
try {
|
||||
const [countries, states] = await Promise.all([
|
||||
globalOrm.searchRead('res.country', [['code', '=', countryCode]], ['id'], { limit: 1 }),
|
||||
province
|
||||
? globalOrm.searchRead('res.country.state', [['code', '=', province], ['country_id.code', '=', countryCode]], ['id'], { limit: 1 })
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
if (countries.length) countryId = countries[0].id;
|
||||
if (states.length) stateId = states[0].id;
|
||||
} catch (_) { /* ignore */ }
|
||||
}
|
||||
|
||||
if (record.resId && globalOrm) {
|
||||
try {
|
||||
const firstWrite = {};
|
||||
if (street) firstWrite.street = street;
|
||||
if (unitNumber) firstWrite.street2 = unitNumber;
|
||||
if (city) firstWrite.city = city;
|
||||
if (postalCode) firstWrite.zip = postalCode;
|
||||
if (countryId) firstWrite.country_id = countryId;
|
||||
|
||||
await globalOrm.write('fusion.ltc.facility', [record.resId], firstWrite);
|
||||
|
||||
if (stateId) {
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
await globalOrm.write('fusion.ltc.facility', [record.resId], { state_id: stateId });
|
||||
}
|
||||
|
||||
await record.load();
|
||||
} catch (err) {
|
||||
console.error('[LTC GooglePlaces] Address ORM write failed:', err);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const textUpdate = {};
|
||||
if (street) textUpdate.street = street;
|
||||
if (unitNumber) textUpdate.street2 = unitNumber;
|
||||
if (city) textUpdate.city = city;
|
||||
if (postalCode) textUpdate.zip = postalCode;
|
||||
|
||||
await record.update(textUpdate);
|
||||
|
||||
if (countryId && globalOrm) {
|
||||
const formEl = input.closest('.o_form_view') || input.closest('.o_content') || document.body;
|
||||
const [countryData, stateData] = await Promise.all([
|
||||
globalOrm.read('res.country', [countryId], ['display_name']),
|
||||
stateId ? globalOrm.read('res.country.state', [stateId], ['display_name']) : Promise.resolve([]),
|
||||
]);
|
||||
|
||||
await simulateMany2OneSelection(formEl, 'country_id', countryId, countryData[0]?.display_name || 'Canada');
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
|
||||
if (stateId && stateData.length) {
|
||||
await simulateMany2OneSelection(formEl, 'state_id', stateId, stateData[0]?.display_name || province);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[LTC GooglePlaces] Address autocomplete update failed:', err);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => { _reattachFacilityAutocomplete(el, model); }, 400);
|
||||
});
|
||||
|
||||
autocompleteInstances.set(input, autocomplete);
|
||||
|
||||
input.style.backgroundImage = 'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 24 24\' fill=\'%234CAF50\'%3E%3Cpath d=\'M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z\'/%3E%3C/svg%3E")';
|
||||
input.style.backgroundRepeat = 'no-repeat';
|
||||
input.style.backgroundPosition = 'right 8px center';
|
||||
input.style.backgroundSize = '20px';
|
||||
input.style.paddingRight = '35px';
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-attach facility autocomplete after OWL re-renders inputs.
|
||||
*/
|
||||
function _reattachFacilityAutocomplete(el, model) {
|
||||
const streetSelectors = [
|
||||
'div[name="street"] input',
|
||||
'.o_field_widget[name="street"] input',
|
||||
'[name="street"] input',
|
||||
];
|
||||
for (const sel of streetSelectors) {
|
||||
const inp = el.querySelector(sel);
|
||||
if (inp && !autocompleteInstances.has(inp)) {
|
||||
_attachFacilityAddressAutocomplete(inp, el, model);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch FormController to add Google autocomplete for LTC Facility forms
|
||||
*/
|
||||
patch(FormController.prototype, {
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
|
||||
this.orm = useService("orm");
|
||||
|
||||
onMounted(() => {
|
||||
// LTC Facility form
|
||||
if (this.props.resModel === 'fusion.ltc.facility') {
|
||||
setTimeout(() => {
|
||||
if (this.rootRef && this.rootRef.el) {
|
||||
setupFacilityAutocomplete(this.rootRef.el, this.model, this.orm);
|
||||
}
|
||||
}, 800);
|
||||
|
||||
if (this.rootRef && this.rootRef.el) {
|
||||
this._facilityAddrObserver = new MutationObserver((mutations) => {
|
||||
const hasNewInputs = mutations.some(m =>
|
||||
m.addedNodes.length > 0 &&
|
||||
Array.from(m.addedNodes).some(n =>
|
||||
n.nodeType === 1 && (n.tagName === 'INPUT' || n.querySelector?.('input'))
|
||||
)
|
||||
);
|
||||
if (hasNewInputs) {
|
||||
setTimeout(() => {
|
||||
setupFacilityAutocomplete(this.rootRef.el, this.model, this.orm);
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
this._facilityAddrObserver.observe(this.rootRef.el, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onWillUnmount(() => {
|
||||
if (this._facilityAddrObserver) {
|
||||
this._facilityAddrObserver.disconnect();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user