Files
FrontOffice/src/FrontOffice.Main/wwwroot/js/org-chart.js
T
masoodafar-web 34c5c508e0 feat: update package version and enhance network membership DTOs
- Updated the version of the Foursat.CMSMicroservice.Protobuf package from 0.0.197 to 0.0.199.
- Added a new property `PackageName` to `NetworkNodeDto` and `FlatNetworkNodeDto` for better representation of membership packages.
- Updated mapping in `NetworkMembershipService` to include the new `PackageName` property.
- Enhanced the org chart display by adding a badge for the package name in the UI.

These changes improve the clarity and functionality of the network membership features.
2026-06-11 23:33:40 +03:30

319 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* d3-org-chart integration for Blazor
* Network Binary Tree visualization for FourSat
*/
window.OrgChart = {
chart: null,
dotNetHelper: null,
/**
* Initialize the organization chart
* @param {string} containerId - The ID of the container element
* @param {object} data - The tree data in flat array format
* @param {object} dotNetHelper - Blazor .NET helper for callbacks
*/
init: function (containerId, data, dotNetHelper) {
this.dotNetHelper = dotNetHelper;
const container = document.getElementById(containerId);
if (!container) {
console.error('OrgChart: Container not found:', containerId);
return;
}
// Clear previous chart
container.innerHTML = '';
if (!data || data.length === 0) {
container.innerHTML = '<div class="no-data-message">داده‌ای برای نمایش وجود ندارد</div>';
return;
}
// Debug: log first node to see data structure
console.log('OrgChart Data Sample:', data[0]);
try {
this.chart = new d3.OrgChart()
.container('#' + containerId)
.data(data)
.nodeWidth((d) => 140)
.nodeHeight((d) => 85)
.childrenMargin((d) => 50)
.compactMarginBetween((d) => 15)
.compactMarginPair((d) => 15)
.neighbourMargin((a, b) => 15)
.siblingsMargin((d) => 15)
.buttonContent(({ node, state }) => {
const hasChildren = node.data._directSubordinates > 0;
const isExpanded = node.children;
return hasChildren ? `<div class="org-expand-btn">
<span>${isExpanded ? '' : '+'}</span>
</div>` : '';
})
.linkUpdate(function (d, i, arr) {
d3.select(this)
.attr('stroke', (d) => d.data._highlighted || d.data._upToTheRootHighlighted ? '#0380C0' : '#ccc')
.attr('stroke-width', (d) => d.data._highlighted || d.data._upToTheRootHighlighted ? 3 : 2);
})
.nodeContent(function (d, i, arr, state) {
const data = d.data;
const isRoot = !data.parentId || data.parentId === '';
const positionClass = data.position === 'Left' ? 'position-left' :
data.position === 'Right' ? 'position-right' : 'position-root';
const activeClass = data.isActive ? 'active' : 'inactive';
const clubActiveClass = data.isClubActive ? 'club-active' : '';
// Avatar - first letter of name
const firstChar = data.fullName ? data.fullName.charAt(0) : '?';
const packageName = data.packageName || data.PackageName || '';
const packageBadge = packageName
? `<div class="package-name-badge">${packageName}</div>`
: '';
// نمایش کد معرف فقط برای کاربران فعال در باشگاه
const referralCodeHtml = data.isClubActive && data.referralCode
? `<div class="node-referral" onclick="event.stopPropagation(); OrgChart.copyReferralCode('${data.referralCode}');" title="کپی کد معرف">
<span class="referral-icon">📋</span>
<span class="referral-code">${data.referralCode}</span>
</div>`
: '';
return `
<div class="org-node-card ${positionClass} ${activeClass} ${clubActiveClass}" data-user-id="${data.id}">
<div class="node-header-compact ${isRoot ? 'root' : ''}">
<div class="node-avatar-sm">${firstChar}</div>
<div class="node-info">
<div class="node-name-sm">${data.fullName || 'بدون نام'}</div>
<div class="node-level-sm">L${data.level || 0}${!isRoot ? ' • ' + (data.position === 'Left' ? 'چپ' : 'راست') : ''}</div>
${packageBadge}
${referralCodeHtml}
</div>
</div>
</div>
`;
})
.onNodeClick((d) => {
if (this.dotNetHelper) {
// Convert string id to number for C# long
const userId = parseInt(d.data.id, 10);
this.dotNetHelper.invokeMethodAsync('OnNodeClicked', userId);
}
})
.render();
// Initial centering and zoom
this.chart.fit();
} catch (error) {
console.error('OrgChart: Error initializing chart:', error);
container.innerHTML = '<div class="error-message">خطا در بارگذاری نمودار</div>';
}
},
/**
* Update the chart with new data
* @param {object} data - The new tree data
*/
update: function (data) {
if (this.chart) {
this.chart.data(data).render();
this.chart.fit();
}
},
/**
* Expand all nodes
*/
expandAll: function () {
if (this.chart) {
this.chart.expandAll().render();
}
},
/**
* Collapse all nodes
*/
collapseAll: function () {
if (this.chart) {
this.chart.collapseAll().render();
}
},
/**
* Center the chart
*/
center: function () {
if (this.chart) {
this.chart.fit();
}
},
/**
* Fit entire tree to screen (zoom out completely)
*/
fitToScreen: function () {
if (this.chart) {
this.chart.fit();
}
},
/**
* Zoom to specific node
* @param {string} nodeId - The ID of the node to zoom to
*/
zoomToNode: function (nodeId) {
if (this.chart) {
this.chart.setCentered(nodeId).render();
}
},
/**
* Highlight path to specific node
* @param {string} nodeId - The ID of the node
*/
highlightNode: function (nodeId) {
if (this.chart) {
this.chart.setHighlighted(nodeId).render();
}
},
/**
* Clear highlighting
*/
clearHighlight: function () {
if (this.chart) {
this.chart.clearHighlighting().render();
}
},
/**
* Export chart as PNG
*/
exportPng: function () {
if (this.chart) {
this.chart.exportImg({ full: true });
}
},
/**
* Export chart as SVG
*/
exportSvg: function () {
if (this.chart) {
this.chart.exportSvg();
}
},
/**
* Copy referral code to clipboard
* @param {string} code - The referral code to copy
*/
copyReferralCode: function(code) {
if (navigator.clipboard) {
navigator.clipboard.writeText(code).then(() => {
// Show toast notification
this.showToast('کد معرف کپی شد: ' + code);
}).catch(err => {
console.error('Failed to copy:', err);
this.fallbackCopy(code);
});
} else {
this.fallbackCopy(code);
}
},
/**
* Fallback copy method for older browsers
*/
fallbackCopy: function(text) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
this.showToast('کد معرف کپی شد: ' + text);
} catch (err) {
console.error('Fallback copy failed:', err);
}
document.body.removeChild(textArea);
},
/**
* Show toast notification
*/
showToast: function(message) {
// Remove existing toast
const existingToast = document.querySelector('.org-chart-toast');
if (existingToast) existingToast.remove();
const toast = document.createElement('div');
toast.className = 'org-chart-toast';
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.classList.add('show'), 10);
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 2000);
},
/**
* Dispose the chart
*/
dispose: function () {
if (this.chart) {
// d3-org-chart doesn't have built-in dispose, so we just clear the reference
this.chart = null;
this.dotNetHelper = null;
}
},
/**
* Convert hierarchical tree data to flat array format for d3-org-chart
* @param {object} rootNode - The root node with nested children
* @returns {array} - Flat array of nodes
*/
convertToFlatArray: function (rootNode) {
if (!rootNode) return [];
const result = [];
function traverse(node, parentId) {
const flatNode = {
id: node.userId?.toString() || node.id?.toString(),
parentId: parentId,
fullName: node.fullName || '',
mobile: node.mobile || '',
avatar: node.avatar,
position: node.position || 'Root',
level: node.level || 0,
isActive: node.isActive !== false,
isClubActive: node.isClubActive || false,
activationWeekNumber: node.activationWeekNumber,
joinedAt: node.joinedAt
};
result.push(flatNode);
// Process left child
if (node.leftChild) {
traverse(node.leftChild, flatNode.id);
}
// Process right child
if (node.rightChild) {
traverse(node.rightChild, flatNode.id);
}
}
traverse(rootNode, '');
return result;
}
};