Initial commit - WhyRating Engine (Google Reviews Scraper)
This commit is contained in:
199
web/components/taxonomy/TaxonomyTree.tsx
Normal file
199
web/components/taxonomy/TaxonomyTree.tsx
Normal file
@@ -0,0 +1,199 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import TreeNode from './TreeNode';
|
||||
import { taxonomy, getDomainSubcodeCount, getSubcodeCount } from '@/lib/taxonomy/data';
|
||||
import type { SelectedSubcode } from '@/lib/taxonomy/types';
|
||||
|
||||
interface TaxonomyTreeProps {
|
||||
searchQuery: string;
|
||||
searchResults: {
|
||||
domains: string[];
|
||||
categories: string[];
|
||||
subcodes: string[];
|
||||
};
|
||||
selectedSubcode: SelectedSubcode | null;
|
||||
onSelectSubcode: (subcode: SelectedSubcode | null) => void;
|
||||
}
|
||||
|
||||
export default function TaxonomyTree({
|
||||
searchQuery,
|
||||
searchResults,
|
||||
selectedSubcode,
|
||||
onSelectSubcode,
|
||||
}: TaxonomyTreeProps) {
|
||||
const [expandedDomains, setExpandedDomains] = useState<Set<string>>(new Set());
|
||||
const [expandedCategories, setExpandedCategories] = useState<Set<string>>(new Set());
|
||||
|
||||
// Auto-expand nodes when search results change
|
||||
useEffect(() => {
|
||||
if (searchQuery) {
|
||||
const domainsToExpand = new Set<string>();
|
||||
const categoriesToExpand = new Set<string>();
|
||||
|
||||
// Expand domains that have matches
|
||||
searchResults.domains.forEach((d) => domainsToExpand.add(d));
|
||||
|
||||
// Expand parent domains for matched categories
|
||||
searchResults.categories.forEach((c) => {
|
||||
domainsToExpand.add(c[0]);
|
||||
categoriesToExpand.add(c);
|
||||
});
|
||||
|
||||
// Expand parent domains and categories for matched subcodes
|
||||
searchResults.subcodes.forEach((s) => {
|
||||
const domainKey = s[0];
|
||||
const categoryKey = s.slice(0, 2);
|
||||
domainsToExpand.add(domainKey);
|
||||
categoriesToExpand.add(categoryKey);
|
||||
});
|
||||
|
||||
setExpandedDomains(domainsToExpand);
|
||||
setExpandedCategories(categoriesToExpand);
|
||||
}
|
||||
}, [searchQuery, searchResults]);
|
||||
|
||||
const toggleDomain = useCallback((domainKey: string) => {
|
||||
setExpandedDomains((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(domainKey)) {
|
||||
next.delete(domainKey);
|
||||
} else {
|
||||
next.add(domainKey);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const toggleCategory = useCallback((categoryKey: string) => {
|
||||
setExpandedCategories((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(categoryKey)) {
|
||||
next.delete(categoryKey);
|
||||
} else {
|
||||
next.add(categoryKey);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleSelectSubcode = (
|
||||
subcodeKey: string,
|
||||
domainKey: string,
|
||||
domainName: string,
|
||||
categoryKey: string,
|
||||
categoryName: string,
|
||||
subcode: SelectedSubcode['subcode']
|
||||
) => {
|
||||
onSelectSubcode({
|
||||
code: subcodeKey,
|
||||
domainKey,
|
||||
domainName,
|
||||
categoryKey,
|
||||
categoryName,
|
||||
subcode,
|
||||
});
|
||||
};
|
||||
|
||||
const isSubcodeSelected = (subcodeKey: string) => {
|
||||
return selectedSubcode?.code === subcodeKey;
|
||||
};
|
||||
|
||||
const isSearchMatch = (key: string) => {
|
||||
if (!searchQuery) return false;
|
||||
return (
|
||||
searchResults.domains.includes(key) ||
|
||||
searchResults.categories.includes(key) ||
|
||||
searchResults.subcodes.includes(key)
|
||||
);
|
||||
};
|
||||
|
||||
// Filter to show only matches when searching
|
||||
const shouldShowDomain = (domainKey: string) => {
|
||||
if (!searchQuery) return true;
|
||||
// Show domain if it matches or any of its children match
|
||||
if (searchResults.domains.includes(domainKey)) return true;
|
||||
if (searchResults.categories.some((c) => c.startsWith(domainKey))) return true;
|
||||
if (searchResults.subcodes.some((s) => s.startsWith(domainKey))) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const shouldShowCategory = (categoryKey: string) => {
|
||||
if (!searchQuery) return true;
|
||||
if (searchResults.categories.includes(categoryKey)) return true;
|
||||
if (searchResults.subcodes.some((s) => s.startsWith(categoryKey))) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const shouldShowSubcode = (subcodeKey: string) => {
|
||||
if (!searchQuery) return true;
|
||||
return searchResults.subcodes.includes(subcodeKey);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{Object.entries(taxonomy.domains).map(([domainKey, domain]) => {
|
||||
if (!shouldShowDomain(domainKey)) return null;
|
||||
|
||||
return (
|
||||
<TreeNode
|
||||
key={domainKey}
|
||||
code={domainKey}
|
||||
name={domain.name}
|
||||
count={getDomainSubcodeCount(domainKey)}
|
||||
isExpanded={expandedDomains.has(domainKey)}
|
||||
level="domain"
|
||||
domainKey={domainKey}
|
||||
onToggle={() => toggleDomain(domainKey)}
|
||||
searchMatch={isSearchMatch(domainKey)}
|
||||
>
|
||||
{Object.entries(domain.categories).map(([categoryKey, category]) => {
|
||||
if (!shouldShowCategory(categoryKey)) return null;
|
||||
|
||||
return (
|
||||
<TreeNode
|
||||
key={categoryKey}
|
||||
code={categoryKey}
|
||||
name={category.name}
|
||||
count={getSubcodeCount(categoryKey)}
|
||||
isExpanded={expandedCategories.has(categoryKey)}
|
||||
level="category"
|
||||
domainKey={domainKey}
|
||||
onToggle={() => toggleCategory(categoryKey)}
|
||||
searchMatch={isSearchMatch(categoryKey)}
|
||||
>
|
||||
{Object.entries(category.subcodes).map(([subcodeKey, subcode]) => {
|
||||
if (!shouldShowSubcode(subcodeKey)) return null;
|
||||
|
||||
return (
|
||||
<TreeNode
|
||||
key={subcodeKey}
|
||||
code={subcodeKey}
|
||||
name={subcode.name}
|
||||
isLeaf
|
||||
isSelected={isSubcodeSelected(subcodeKey)}
|
||||
level="subcode"
|
||||
domainKey={domainKey}
|
||||
onClick={() =>
|
||||
handleSelectSubcode(
|
||||
subcodeKey,
|
||||
domainKey,
|
||||
domain.name,
|
||||
categoryKey,
|
||||
category.name,
|
||||
subcode
|
||||
)
|
||||
}
|
||||
searchMatch={isSearchMatch(subcodeKey)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TreeNode>
|
||||
);
|
||||
})}
|
||||
</TreeNode>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user