const rootMenusElement = document.getElementById("menusPopup"); const rootMenus = ReactDOM.createRoot(rootMenusElement); //alert(_token_encode); const MenusComponent=()=>{ return( ) } rootMenus.render(, rootMenusElement); /////////////////////////////////////////////////// const CMSMenus =(props)=>{ const {userLogin} = props const [groupFeatures, setGroupFeatures] =useState([]) const [loading, setLoading] = useState(false) let config = null try { config = { token: _token_encode } }catch (e) { config = { token: '' } } useEffect(()=>{ fetchMenus() },[]) const fetchMenus= async () => { try { setLoading(true) const data ={ active:true, } const result = await commonApi.APIPOSTByURL('/api/get-menus', data,config) if (result.status==200){ setGroupFeatures (result.data); }else{ setGroupFeatures ([]); } } catch (e) { // Handle error setGroupFeatures([]) //console.log("ERROR" ) } finally { setLoading(false) } }; function removeEmptyParentNodes(groupFeatures) { // First, organize the features into a hierarchical structure for easier processing. const featureMap = new Map(); const rootNodes = []; groupFeatures.forEach(feature => { featureMap.set(feature.id, { ...feature, children: [] }); }); featureMap.forEach(feature => { if (feature.parent_id) { const parent = featureMap.get(feature.parent_id); if (parent) { parent.children.push(feature); } } else { rootNodes.push(feature); } }); // Recursively filter out invisible children and empty parent nodes. function filterNodes(nodes) { return nodes.filter(node => { if (node.is_parent) { // If it's a parent, recursively filter its children. node.children = filterNodes(node.children); // Keep the parent only if it has visible children remaining. return node.children.length > 0; } else { // If it's a child, keep it only if it's visible. return node.visible; } }); } // Start the filtering process from the root nodes. const filteredTree = filterNodes(rootNodes); // Flatten the filtered tree back into a list (optional, depending on your needs) const flattenedResult = []; function flatten(nodes) { nodes.forEach(node => { // Create a shallow copy to avoid modifying the original 'children' property // if you intend to reuse the `filteredTree` for other purposes. const { children, ...rest } = node; flattenedResult.push(rest); if (children.length > 0) { flatten(children); } }); } flatten(filteredTree); return flattenedResult; } const group =removeEmptyParentNodes(groupFeatures) return( <> {groupFeatures?.length>0 ? : <>None } ) } const CMSMenuTreeGroupFeature = ({ group }) => { const { Accordion } = ReactBootstrap; const [searchQuery, setSearchQuery] = React.useState(''); // Dynamic import of Outfit and Inter fonts for premium typography React.useEffect(() => { if (!document.getElementById('mimi-fonts-pre')) { const link = document.createElement('link'); link.id = 'mimi-fonts-pre'; link.href = 'https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600;700&display=swap'; link.rel = 'stylesheet'; document.head.appendChild(link); } }, []); const isVi = (typeof commonFunc !== 'undefined' && commonFunc.getCookies('user_lang') === 'en') ? false : true; const [expandedKeys, setExpandedKeys] = React.useState(() => { if (Array.isArray(group) && group.length > 0) { const firstParent = group.find(g => g.parent_id === null && group.some(child => child.parent_id === g.id) ); return firstParent ? [firstParent.id.toString()] : []; } return []; }); // Handle search filtering with null name check and keep matching items and their ancestors const filteredGroup = React.useMemo(() => { if (!searchQuery.trim()) return group; const query = searchQuery.toLowerCase().trim(); const matchingItems = group.filter(item => (item.name && item.name.toLowerCase().includes(query)) || (item.path && item.path.toLowerCase().includes(query)) ); const keepIds = new Set(); matchingItems.forEach(item => { keepIds.add(item.id); let parentId = item.parent_id; while (parentId) { const parent = group.find(g => g.id === parentId); if (parent) { keepIds.add(parent.id); parentId = parent.parent_id; } else { break; } } }); return group.filter(item => keepIds.has(item.id)); }, [group, searchQuery]); // Automatically expand nodes containing search results, utilizing cycle-prevention sets and name checks React.useEffect(() => { if (searchQuery.trim() && group.length > 0) { const query = searchQuery.toLowerCase().trim(); const keysToExpand = group .filter(g => g.is_parent || group.some(child => child.parent_id === g.id)) .filter(g => { const hasMatchingDescendant = (parentId, visited = new Set()) => { if (visited.has(parentId)) return false; visited.add(parentId); const children = group.filter(c => c.parent_id === parentId); return children.some(c => (c.name && c.name.toLowerCase().includes(query)) || hasMatchingDescendant(c.id, visited) ); }; return hasMatchingDescendant(g.id); }) .map(g => g.id.toString()); setExpandedKeys(keysToExpand); } }, [searchQuery, group]); const handleAccordionSelect = (eventKey) => { if (!eventKey) return; const selectedKeys = Array.isArray(eventKey) ? eventKey : [eventKey.toString()]; setExpandedKeys(selectedKeys); }; const handleExpandAll = () => { const parentKeys = group .filter(g => group.some(child => child.parent_id === g.id)) .map(g => g.id.toString()); setExpandedKeys(parentKeys); }; const handleCollapseAll = () => { setExpandedKeys([]); }; const gotoLink = (path) => { if (path) window.location.href = path; }; const renderIcon = (iconSource, isRoot) => { const isBase64 = iconSource && (iconSource.startsWith('data:image') || iconSource.length > 50); if (isBase64) { return ( icon ); } const defaultClass = isRoot ? "fa fa-th-large" : "fa fa-cube"; return ( ); }; const renderTree = (parentId, isRoot = false) => { if (!Array.isArray(filteredGroup)) return null; const children = filteredGroup.filter(g => g.parent_id === parentId); return children.map(g => { const hasChildren = filteredGroup.some(item => item.parent_id === g.id); const gIdStr = g.id.toString(); const isExpanded = Array.isArray(expandedKeys) && expandedKeys.includes(gIdStr); if (!hasChildren) { return (
gotoLink(g.path)} > {renderIcon(g.icon, isRoot)} {g.name} {isRoot && }
); } else { return (
{renderIcon(g.icon, true)} {g.name}
{renderTree(g.id, false)}
); } }); }; const styles = { container: { background: 'linear-gradient(135deg, #070410 0%, #100a20 100%)', padding: '24px 16px 40px 16px', color: '#f8fafc', width: '100%', fontFamily: '"Outfit", "Inter", -apple-system, sans-serif' } }; return (
{/* Header section */}
{/* Search & Action Controls */}
setSearchQuery(e.target.value)} style={{ paddingLeft: '40px', background: 'rgba(30, 27, 75, 0.3)', border: '1px solid rgba(99, 102, 241, 0.2)', borderRadius: '12px', color: '#ffffff', height: '42px', fontSize: '14px', transition: 'all 0.3s ease' }} /> {searchQuery && ( setSearchQuery('')} style={{ cursor: 'pointer' }}> )}
{renderTree(null, true)} {/* Premium Logout Button at the bottom of the menu tree */}
{isVi ? "Đăng xuất" : "Logout"}
); };