//////////////////////////////////////////////////////////// const useQuery = () => { return new URLSearchParams(useLocation().search); }; const ProductCategoriesMaster =(props)=>{ const { } =props const [categories, setCategories] = useState([]) const [categorySelected, setCategorySelected] = useState() const user_lang = commonFunc.getCookies('user_lang') || 'vi'; const [reloadPage, setReloadPage] = useState(_reload_page) // true : good for SEO const [isMobile, setIsMobile] = useState(false); const [loading, setLoading] = useState(false) const [userInfo, setUserInfo] = useState() const configDefault = { token: _token_encode, lang: user_lang, branch_no:(userInfo?.branch_no |0) }; const [config, setConfig] = useState(configDefault) const scriptUrls = [ '/static/components/POS/product_categories/index.js', ]; useEffect(()=>{ if (window?.ProductCategoriesMaster ==null && _extension=='js'){ loadScripts(scriptUrls) .then(() => { console.log("All scripts loaded successfully."); }) .catch(err => { console.error(err); // Xử lý lỗi nếu có }); } const categoriesDefault = JSON.parse(document.getElementById('categories-data').textContent); setCategories(categoriesDefault); const handleResize = () => { setIsMobile(window.innerWidth < 1280); }; window.addEventListener('resize', handleResize); handleResize(); // Gọi ngay một lần để thiết lập trạng thái ban đầu fetchUserInfo() return () => { window.removeEventListener('resize', handleResize); }; },[]) const fetchUserInfo = async () => { try { setLoading(true) const url =`/api/get-user-info` const resultObj = await commonApi.APIGETByURL(url, config) if (resultObj.status == 200){ let dataTmp = resultObj?.data setUserInfo(dataTmp) const configDefault = { token: _token_encode, lang: user_lang, branch_no:(dataTmp?.branch_no |0) }; setConfig(configDefault) } //console.log(dataInit ) } catch (e) { setUserInfo(null) console.log("ERROR get-user-info" ) }finally{ setLoading(false) } }; const containerStyle = { display: 'flex', flexDirection: 'column', width: '100%', }; const productCategoryStyle = { flex: isMobile ? '0 0 100%' : '0 0 60%', // 100% width on mobile, 60% on larger screens padding: '0px 0px 0px 0px', }; return (
); } const CategoryTreeComponent = (props) => { const { categories } = props; const { ListGroup, Tabs, Tab } = ReactBootstrap; // Lấy type từ URL (ví dụ: ?type=product_tag) để xác định Tab đang active const urlParams = new URLSearchParams(window.location.search); const activeType = urlParams.get('type') || 'product_cat'; const rootCategories = useMemo(() => { if (!categories) return []; const allIds = new Set(categories.map(cat => cat.id?.toString())); const roots = categories.filter(cat => !allIds.has(cat.parent_id?.toString())); return [...roots].sort((a, b) => (a.name || "").localeCompare((b.name || ""), 'vi', { sensitivity: 'base', numeric: true }) ); }, [categories]); const isOnlyOneRoot = rootCategories?.length === 1; // Hàm xử lý chuyển Tab và Redirect const handleTabSelect = (key) => { // Redirect sang URL mới với query type tương ứng window.location.href = `/categories?type=${key}`; }; if (!categories || categories.length === 0) { return (
{/* Vẫn hiện Tabs ngay cả khi không có data để người dùng chuyển đổi lại */}
Không tìm thấy dữ liệu.
); } return (
{/* Hệ thống Tabs bao quanh ListGroup */} Category}>
{rootCategories.map((cat) => ( ))}
Tags}>
{/* Nội dung ListGroup cho Tags sẽ hiển thị ở đây. Vì cùng dùng chung cấu trúc đệ quy, logic map sẽ tương tự như trên. */} {rootCategories.map((cat) => ( ))}
); }; const CategoryNode = ({ category, allCategories, level = 0, isOnlyRoot = false }) => { const [open, setOpen] = useState(isOnlyRoot); const { ListGroup, Collapse } = ReactBootstrap; // Lấy slug từ URL để highlight const urlParams = new URLSearchParams(window.location.search); const currentSlug = urlParams.get('slug'); const isActive = currentSlug === category.slug; // Tìm và SORT các con đồng cấp theo tên const children = useMemo(() => { return allCategories .filter(item => item.parent_id?.toString() === category.id?.toString()) .sort((a, b) => (a.name || "").localeCompare((b.name || ""), 'vi', { sensitivity: 'base', numeric: true }) ); }, [category.id, allCategories]); const hasChildren = children.length > 0; const paddingLeft = level * 20; // Tự động mở cấp cha nếu có con đang active (Tùy chọn) useEffect(() => { const hasActiveChild = children.some(child => child.slug === currentSlug); if (hasActiveChild) setOpen(true); }, [currentSlug, children]); return (
hasChildren && setOpen(!open)} >
{hasChildren ? ( ) : ( )}
{hasChildren ? ( {category.name} ) : ( {category.name} )}
{isActive && }
{hasChildren && (
{children.map((child) => ( ))}
)}
); };