diff --git a/src/components/ComponentTreeViewer.tsx b/src/components/ComponentTreeViewer.tsx index ef9d3ac..6f2fea2 100644 --- a/src/components/ComponentTreeViewer.tsx +++ b/src/components/ComponentTreeViewer.tsx @@ -7,15 +7,55 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { ScrollArea } from '@/components/ui/scroll-area' import { Separator } from '@/components/ui/separator' import { toast } from 'sonner' -import { - Cube, - TreeStructure, - ArrowsClockwise, - CheckCircle, +import { + Cube, + TreeStructure, + ArrowsClockwise, + CheckCircle, Warning, Package, - Stack + Stack, } from '@phosphor-icons/react' +import componentTreeCopy from '@/data/component-tree-viewer.json' +import { ComponentNode, ComponentTree } from '@/types/project' + +type ComponentTreeCategory = 'molecule' | 'organism' + +type ComponentTreeWithCategory = ComponentTree & { + category?: ComponentTreeCategory +} + +type ComponentTreeHeaderProps = { + isLoaded: boolean + isLoading: boolean + totalTrees: number + onReload: () => void +} + +type ComponentTreeStatusProps = { + error: Error | null +} + +type ComponentTreeListProps = { + trees: ComponentTreeWithCategory[] + selectedTreeId: string | null + onSelect: (id: string) => void + variant: 'molecules' | 'organisms' | 'all' +} + +type ComponentTreeDetailProps = { + tree?: ComponentTree +} + +const formatDate = (timestamp: number) => new Date(timestamp).toLocaleDateString() + +const getCategoryLabel = (category?: ComponentTreeCategory) => { + if (!category) { + return '' + } + + return componentTreeCopy.categories[category] ?? category +} export function ComponentTreeViewer() { const { @@ -33,9 +73,9 @@ export function ComponentTreeViewer() { const handleReload = async () => { try { await reloadFromJSON() - toast.success('Component trees reloaded from JSON') + toast.success(componentTreeCopy.toast.reloadSuccess) } catch (err) { - toast.error('Failed to reload component trees') + toast.error(componentTreeCopy.toast.reloadError) } } @@ -43,64 +83,33 @@ export function ComponentTreeViewer() { return (
-
-
- -
-

Component Trees

-

- Molecules and organisms loaded from JSON -

-
-
-
- {isLoaded && ( - - - {allTrees.length} trees loaded - - )} - -
-
- - {error && ( -
- -
-

Error loading component trees

-

{error.message}

-
-
- )} + + - Molecules + {componentTreeCopy.tabs.molecules} {moleculeTrees.length} - Organisms + {componentTreeCopy.tabs.organisms} {organismTrees.length} - All + {componentTreeCopy.tabs.all} {allTrees.length} @@ -109,161 +118,37 @@ export function ComponentTreeViewer() {
- -
- {moleculeTrees.map(tree => ( - setSelectedTreeId(tree.id)} - > - - - - {tree.name} - - - {tree.description} - - - -
- {tree.rootNodes.length} root nodes - - - {new Date(tree.updatedAt).toLocaleDateString()} - -
-
-
- ))} -
-
- -
- {selectedTree ? ( - - ) : ( -
-
- -

Select a tree to view details

-
-
- )} -
+ +
- -
- {organismTrees.map(tree => ( - setSelectedTreeId(tree.id)} - > - - - - {tree.name} - - - {tree.description} - - - -
- {tree.rootNodes.length} root nodes - - - {new Date(tree.updatedAt).toLocaleDateString()} - -
-
-
- ))} -
-
- -
- {selectedTree ? ( - - ) : ( -
-
- -

Select a tree to view details

-
-
- )} -
+ +
- -
- {allTrees.map(tree => { - const category = (tree as any).category - return ( - setSelectedTreeId(tree.id)} - > - - - {category === 'molecule' ? ( - - ) : ( - - )} - {tree.name} - - {category} - - - - {tree.description} - - - -
- {tree.rootNodes.length} root nodes - - - {new Date(tree.updatedAt).toLocaleDateString()} - -
-
-
- ) - })} -
-
- -
- {selectedTree ? ( - - ) : ( -
-
- -

Select a tree to view details

-
-
- )} -
+ +
@@ -271,53 +156,187 @@ export function ComponentTreeViewer() { ) } -function TreeDetails({ tree }: { tree: any }) { - const renderNode = (node: any, depth = 0) => { - return ( -
-
-
- {node.name || node.type} - - {node.type} - -
- {node.props && Object.keys(node.props).length > 0 && ( -
- Props: {Object.keys(node.props).length} -
- )} +function ComponentTreeHeader({ + isLoaded, + isLoading, + totalTrees, + onReload, +}: ComponentTreeHeaderProps) { + return ( +
+
+ +
+

{componentTreeCopy.header.title}

+

+ {componentTreeCopy.header.subtitle} +

+
+
+
+ {isLoaded && ( + + + {totalTrees} {componentTreeCopy.header.loadedLabel} + + )} + +
+
+ ) +} + +function ComponentTreeStatus({ error }: ComponentTreeStatusProps) { + if (!error) { + return null + } + + return ( +
+ +
+

{componentTreeCopy.status.errorTitle}

+

{error.message}

+
+
+ ) +} + +function ComponentTreeList({ + trees, + selectedTreeId, + onSelect, + variant, +}: ComponentTreeListProps) { + return ( + +
+ {trees.map(tree => { + const categoryLabel = variant === 'all' ? getCategoryLabel(tree.category) : '' + const treeIcon = + variant === 'molecules' + ? 'molecule' + : variant === 'organisms' + ? 'organism' + : tree.category + + return ( + onSelect(tree.id)} + > + + + {treeIcon === 'molecule' ? ( + + ) : ( + + )} + {tree.name} + {categoryLabel ? ( + + {categoryLabel} + + ) : null} + + {tree.description} + + +
+ + {tree.rootNodes.length} {componentTreeCopy.labels.rootNodes} + + + {formatDate(tree.updatedAt)} +
+
+
+ ) + })} +
+
+ ) +} + +function ComponentTreeDetails({ tree }: ComponentTreeDetailProps) { + if (!tree) { + return ( +
+
+
+ +

{componentTreeCopy.status.selectPrompt}

+
- {node.children && node.children.map((child: any) => renderNode(child, depth + 1))}
) } return ( - -
-
-

{tree.name}

-

{tree.description}

-
- - {tree.rootNodes.length} root nodes - - - ID: {tree.id} - +
+ +
+
+

{tree.name}

+

{tree.description}

+
+ + {tree.rootNodes.length} {componentTreeCopy.labels.rootNodes} + + + {componentTreeCopy.labels.id}: {tree.id} + +
+
- -
-
-

Component Tree Structure

- {tree.rootNodes.map((node: any) => renderNode(node))} +
+

+ {componentTreeCopy.labels.structureTitle} +

+ {tree.rootNodes.map(node => ( + + ))} +
-
- + +
+ ) +} + +type ComponentTreeNodeProps = { + node: ComponentNode + depth?: number +} + +function ComponentTreeNode({ node, depth = 0 }: ComponentTreeNodeProps) { + return ( +
+
+
+ {node.name || node.type} + + {node.type} + +
+ {Object.keys(node.props).length > 0 && ( +
+ {componentTreeCopy.labels.props}: {Object.keys(node.props).length} +
+ )} +
+ {node.children.map(child => ( + + ))} +
) } diff --git a/src/data/component-tree-viewer.json b/src/data/component-tree-viewer.json new file mode 100644 index 0000000..d52cafb --- /dev/null +++ b/src/data/component-tree-viewer.json @@ -0,0 +1,31 @@ +{ + "header": { + "title": "Component Trees", + "subtitle": "Molecules and organisms loaded from JSON", + "loadedLabel": "trees loaded", + "reloadLabel": "Reload" + }, + "tabs": { + "molecules": "Molecules", + "organisms": "Organisms", + "all": "All" + }, + "status": { + "errorTitle": "Error loading component trees", + "selectPrompt": "Select a tree to view details" + }, + "labels": { + "rootNodes": "root nodes", + "id": "ID", + "structureTitle": "Component Tree Structure", + "props": "Props" + }, + "categories": { + "molecule": "molecule", + "organism": "organism" + }, + "toast": { + "reloadSuccess": "Component trees reloaded from JSON", + "reloadError": "Failed to reload component trees" + } +}