Skip to content

Commit 142a55e

Browse files
author
John75SunCity
committed
fix(portal): Organization diagram not rendering - fixed JavaScript initialization
ISSUE: - Organization diagram page showed infinite loading spinner - Diagram never rendered, stats showed '-' - No users appeared in search - vis-network library not properly initialized ROOT CAUSE: - portal_organization_diagram.js had fake publicWidget object - Not properly requiring web.public.widget dependency - vis-network CDN not loaded before rendering - Missing 'label' field in node data (vis-network requirement) FIXES: 1. JavaScript Widget (portal_organization_diagram.js): ✅ Fixed odoo.define() to require 'web.public.widget' ✅ Added _loadVisNetwork() to load CDN library (9.1.6) ✅ Added Promise-based loading before rendering ✅ Enhanced _renderDiagram() with better error handling ✅ Added _showFallbackMessage() for library load failures ✅ Improved vis-network options (hierarchical layout, shadows) ✅ Added _onNodeClick() event handler ✅ Added _showNodeDetails() modal display ✅ Better console logging for debugging 2. Controller (portal.py): ✅ Added 'label' field to node data structure ✅ vis-network requires 'label' for display text ✅ Kept 'name' field for backward compatibility 3. User Experience Improvements: ✅ Shows helpful message if no organization data ✅ Displays error if vis-network fails to load ✅ Removes loading spinner after successful render ✅ Logs render success with node count ✅ Click nodes to see details in modal TECHNICAL DETAILS: - vis-network loaded from CDN: unpkg.com/vis-network@9.1.6 - CSS and JS loaded dynamically via Promise - Hierarchical layout with 150px node/level spacing - Box-shaped nodes with shadows and borders - Event listeners for node clicks - Bootstrap modals for node details TESTING: - User should refresh /my/organization page - Diagram should render immediately (no infinite spinner) - Stats should show counts (companies, departments, users) - Search should filter nodes - Click nodes to see details modal Version: 18.0.1.0.21 → 18.0.1.0.22 Files: portal_organization_diagram.js, portal.py, __manifest__.py
1 parent 0ef724d commit 142a55e

File tree

3 files changed

+205
-30
lines changed

3 files changed

+205
-30
lines changed

records_management/__manifest__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "Records Management - Enterprise Edition",
3-
'version': '18.0.1.0.21',
3+
'version': '18.0.1.0.22',
44
'category': 'Productivity/Records',
55
"summary": "Complete Enterprise Records Management System with NAID AAA Compliance",
66
"description": "Records Management - Enterprise Grade DMS Module. Enterprise physical & digital records lifecycle, NAID AAA + ISO 15489 compliance, portal, shredding, retention, audit, billing.",

records_management/controllers/portal.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6413,10 +6413,11 @@ def add_partner_node(p, parent_id=None):
64136413
else:
64146414
color = '#e91e63' # Pink - portal user
64156415

6416-
# Create node
6416+
# Create node - vis-network requires 'label' field for display
64176417
node = {
64186418
'id': p.id,
6419-
'name': p.name,
6419+
'label': p.name, # vis-network displays this
6420+
'name': p.name, # Keep for compatibility
64206421
'type': node_type,
64216422
'color': color,
64226423
'email': p.email or '',

records_management/static/src/js/portal/portal_organization_diagram.js

Lines changed: 201 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*
77
* ARCHITECTURE:
88
* - Consumes JSON from #diagram-data element (server-rendered in template)
9-
* - Renders via vis-network library (optional bundle)
9+
* - Renders via vis-network library (loaded from CDN)
1010
* - Graceful fallback when vis.js not loaded
1111
*
1212
* DATA FLOW:
@@ -31,11 +31,10 @@
3131
*
3232
* BROWSER SUPPORT: Modern browsers (ES6+), graceful fallback for older browsers
3333
*/
34-
odoo.define('records_management.portal_organization_diagram', [], function(require) {
34+
odoo.define('records_management.portal_organization_diagram', ['web.public.widget'], function(require) {
3535
'use strict';
3636

37-
// Frontend-compatible implementation
38-
const publicWidget = { Widget: { extend: function(obj) { return obj; } } };
37+
const publicWidget = require('web.public.widget');
3938

4039
const OrgDiagramPortal = publicWidget.Widget.extend({
4140
selector: '.o_portal_organization_diagram',
@@ -47,11 +46,59 @@ odoo.define('records_management.portal_organization_diagram', [], function(requi
4746
'change #layout-select': '_onLayoutChanged',
4847
},
4948
start() {
49+
const self = this;
5050
this._parseData();
51-
this._renderDiagram();
5251
this._updateStats();
52+
53+
// Load vis-network library from CDN, then render diagram
54+
this._loadVisNetwork().then(function() {
55+
self._renderDiagram();
56+
}).catch(function(err) {
57+
console.error('[OrgDiagramPortal] Failed to load vis-network library', err);
58+
self._showFallbackMessage();
59+
});
60+
5361
return this._super.apply(this, arguments);
5462
},
63+
_loadVisNetwork() {
64+
// Check if already loaded
65+
if (window.vis && window.vis.Network) {
66+
return Promise.resolve();
67+
}
68+
69+
// Load from CDN
70+
const CDN_VERSION = '9.1.6';
71+
const CDN_CSS = `https://unpkg.com/vis-network@${CDN_VERSION}/dist/vis-network.min.css`;
72+
const CDN_JS = `https://unpkg.com/vis-network@${CDN_VERSION}/dist/vis-network.min.js`;
73+
74+
return new Promise((resolve, reject) => {
75+
// Load CSS
76+
const cssLink = document.createElement('link');
77+
cssLink.rel = 'stylesheet';
78+
cssLink.href = CDN_CSS;
79+
document.head.appendChild(cssLink);
80+
81+
// Load JS
82+
const script = document.createElement('script');
83+
script.src = CDN_JS;
84+
script.onload = resolve;
85+
script.onerror = reject;
86+
document.head.appendChild(script);
87+
});
88+
},
89+
_showFallbackMessage() {
90+
const container = this.el.querySelector('#organization-diagram-container');
91+
if (container) {
92+
container.innerHTML = `
93+
<div class="p-5 text-center">
94+
<i class="fa fa-exclamation-triangle fa-3x text-warning mb-3"></i>
95+
<h5>Diagram Visualization Unavailable</h5>
96+
<p class="text-muted">The visualization library could not be loaded.</p>
97+
<p class="text-muted">Please check your internet connection and refresh the page.</p>
98+
</div>
99+
`;
100+
}
101+
},
55102
_parseData() {
56103
try {
57104
const jsonEl = this.el.querySelector('#diagram-data');
@@ -67,34 +114,161 @@ odoo.define('records_management.portal_organization_diagram', [], function(requi
67114
},
68115
_renderDiagram() {
69116
const container = this.el.querySelector('#organization-diagram-container');
70-
if (!container) { return; }
71-
// If vis is present (optional bundle), render network
72-
if (window.vis && this.diagramData.nodes && this.diagramData.edges) {
73-
const options = this._buildVisOptions();
74-
try {
75-
this.network = new vis.Network(container, {
76-
nodes: new vis.DataSet(this.diagramData.nodes),
77-
edges: new vis.DataSet(this.diagramData.edges),
78-
}, options);
79-
container.querySelector('.loading-overlay')?.remove();
80-
} catch (e) {
81-
console.error('[OrgDiagramPortal] vis rendering failed', e);
82-
}
83-
} else {
84-
// Fallback
85-
container.innerHTML = '<div class="p-5 text-center text-muted">Diagram library not loaded. Please enable visualization bundle.</div>';
117+
if (!container) {
118+
console.warn('[OrgDiagramPortal] Container #organization-diagram-container not found');
119+
return;
120+
}
121+
122+
// Check if we have vis-network loaded
123+
if (!window.vis || !window.vis.Network) {
124+
console.error('[OrgDiagramPortal] vis.Network not available');
125+
this._showFallbackMessage();
126+
return;
127+
}
128+
129+
// Check if we have data
130+
if (!this.diagramData.nodes || !this.diagramData.nodes.length) {
131+
container.innerHTML = `
132+
<div class="p-5 text-center">
133+
<i class="fa fa-info-circle fa-3x text-info mb-3"></i>
134+
<h5>No Organization Data</h5>
135+
<p class="text-muted">Your organization structure is empty.</p>
136+
<p class="text-muted">Contact your administrator to set up departments and team members.</p>
137+
</div>
138+
`;
139+
return;
140+
}
141+
142+
// Remove loading overlay
143+
const loadingOverlay = container.querySelector('.loading-overlay');
144+
if (loadingOverlay) {
145+
loadingOverlay.remove();
146+
}
147+
148+
// Build vis-network options
149+
const options = this._buildVisOptions();
150+
151+
// Render the network
152+
try {
153+
this.network = new vis.Network(container, {
154+
nodes: new vis.DataSet(this.diagramData.nodes),
155+
edges: new vis.DataSet(this.diagramData.edges),
156+
}, options);
157+
158+
console.log('[OrgDiagramPortal] Diagram rendered successfully with', this.diagramData.nodes.length, 'nodes');
159+
160+
// Add event listeners
161+
this.network.on('click', this._onNodeClick.bind(this));
162+
163+
} catch (e) {
164+
console.error('[OrgDiagramPortal] vis.Network rendering failed', e);
165+
this._showFallbackMessage();
86166
}
87167
},
88168
_buildVisOptions() {
89169
const layoutType = (this.diagramData.config && this.diagramData.config.layout_type) || 'hierarchical';
90170
const hierarchical = layoutType === 'hierarchical';
91-
return {
92-
layout: hierarchical ? { hierarchical: { direction: 'UD', sortMethod: 'hubsize' } } : {},
93-
interaction: { hover: true },
94-
physics: hierarchical ? false : { stabilization: true },
95-
nodes: { shape: 'dot', size: 18, font: { size: 12 } },
96-
edges: { arrows: { to: { enabled: false } }, smooth: { type: 'dynamic' } },
171+
172+
const options = {
173+
layout: hierarchical ? {
174+
hierarchical: {
175+
direction: 'UD',
176+
sortMethod: 'directed',
177+
nodeSpacing: 150,
178+
levelSeparation: 150,
179+
}
180+
} : {
181+
randomSeed: 2
182+
},
183+
interaction: {
184+
hover: true,
185+
navigationButtons: true,
186+
keyboard: true,
187+
},
188+
physics: hierarchical ? false : {
189+
enabled: true,
190+
stabilization: {
191+
enabled: true,
192+
iterations: 100,
193+
},
194+
},
195+
nodes: {
196+
shape: 'box',
197+
margin: 10,
198+
widthConstraint: {
199+
minimum: 100,
200+
maximum: 200,
201+
},
202+
font: {
203+
size: 14,
204+
face: 'Arial',
205+
},
206+
borderWidth: 2,
207+
shadow: true,
208+
},
209+
edges: {
210+
arrows: {
211+
to: {
212+
enabled: true,
213+
scaleFactor: 0.5,
214+
}
215+
},
216+
smooth: {
217+
type: 'cubicBezier',
218+
roundness: 0.5,
219+
},
220+
width: 2,
221+
},
97222
};
223+
224+
return options;
225+
},
226+
_onNodeClick(params) {
227+
if (params.nodes && params.nodes.length > 0) {
228+
const nodeId = params.nodes[0];
229+
const node = this.diagramData.nodes.find(n => n.id === nodeId);
230+
if (node) {
231+
this._showNodeDetails(node);
232+
}
233+
}
234+
},
235+
_showNodeDetails(node) {
236+
const modal = this.el.querySelector('#node-details-modal');
237+
const title = this.el.querySelector('#node-modal-title');
238+
const body = this.el.querySelector('#node-modal-body');
239+
240+
if (!modal || !title || !body) return;
241+
242+
title.textContent = node.name || node.label || 'Unknown';
243+
244+
let html = '<div class="row">';
245+
html += '<div class="col-md-4 text-center">';
246+
if (node.image) {
247+
html += `<img src="${node.image}" class="img-fluid rounded mb-3" alt="${node.name}" style="max-width: 150px;">`;
248+
}
249+
html += '</div>';
250+
html += '<div class="col-md-8">';
251+
html += '<table class="table table-sm">';
252+
if (node.type) {
253+
html += `<tr><th>Type:</th><td><span class="badge badge-primary">${node.type}</span></td></tr>`;
254+
}
255+
if (node.email) {
256+
html += `<tr><th>Email:</th><td><a href="mailto:${node.email}">${node.email}</a></td></tr>`;
257+
}
258+
if (node.phone) {
259+
html += `<tr><th>Phone:</th><td>${node.phone}</td></tr>`;
260+
}
261+
if (node.job_title) {
262+
html += `<tr><th>Job Title:</th><td>${node.job_title}</td></tr>`;
263+
}
264+
html += '</table>';
265+
html += '</div>';
266+
html += '</div>';
267+
268+
body.innerHTML = html;
269+
270+
// Show modal using Bootstrap
271+
$(modal).modal('show');
98272
},
99273
_updateStats() {
100274
const stats = this.diagramData.stats || {};

0 commit comments

Comments
 (0)