Files
pn-new-crm/src/components/files/folder-tree.tsx

140 lines
3.9 KiB
TypeScript
Raw Normal View History

'use client';
import { useState } from 'react';
import { ChevronDown, ChevronRight, Folder, FolderOpen } from 'lucide-react';
import { cn } from '@/lib/utils';
import type { FileRow } from '@/components/files/file-grid';
interface FolderNode {
name: string;
fullPath: string;
children: Record<string, FolderNode>;
}
function buildFolderTree(files: FileRow[]): FolderNode {
const root: FolderNode = { name: '', fullPath: '', children: {} };
for (const file of files) {
const parts = file.storagePath ? file.storagePath.split('/').slice(0, -1) : [];
if (parts.length <= 1) continue; // skip files directly in root/port folder
let node = root;
let accumulated = '';
for (const part of parts.slice(1)) { // skip portSlug prefix
accumulated = accumulated ? `${accumulated}/${part}` : part;
if (!node.children[part]) {
node.children[part] = { name: part, fullPath: accumulated, children: {} };
}
node = node.children[part]!;
}
}
return root;
}
interface FolderNodeComponentProps {
node: FolderNode;
currentFolder: string;
onFolderSelect: (path: string) => void;
depth?: number;
}
function FolderNodeComponent({
node,
currentFolder,
onFolderSelect,
depth = 0,
}: FolderNodeComponentProps) {
const [expanded, setExpanded] = useState(true);
const hasChildren = Object.keys(node.children).length > 0;
const isSelected = currentFolder === node.fullPath;
return (
<div>
<button
type="button"
onClick={() => {
onFolderSelect(node.fullPath);
if (hasChildren) setExpanded((v) => !v);
}}
className={cn(
'flex w-full items-center gap-1.5 rounded px-2 py-1 text-left text-sm hover:bg-muted/60 transition-colors',
isSelected && 'bg-muted font-medium',
)}
style={{ paddingLeft: `${depth * 12 + 8}px` }}
>
{hasChildren ? (
expanded ? (
<ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
) : (
<ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
)
) : (
<span className="w-3.5" />
)}
{isSelected ? (
<FolderOpen className="h-4 w-4 shrink-0 text-primary" />
) : (
<Folder className="h-4 w-4 shrink-0 text-muted-foreground" />
)}
<span className="truncate">{node.name}</span>
</button>
{hasChildren && expanded && (
<div>
{Object.values(node.children).map((child) => (
<FolderNodeComponent
key={child.fullPath}
node={child}
currentFolder={currentFolder}
onFolderSelect={onFolderSelect}
depth={depth + 1}
/>
))}
</div>
)}
</div>
);
}
interface FolderTreeProps {
files: (FileRow & { storagePath: string })[];
currentFolder: string;
onFolderSelect: (path: string) => void;
}
export function FolderTree({ files, currentFolder, onFolderSelect }: FolderTreeProps) {
const tree = buildFolderTree(files);
return (
<div className="space-y-0.5">
<button
type="button"
onClick={() => onFolderSelect('')}
className={cn(
'flex w-full items-center gap-1.5 rounded px-2 py-1 text-left text-sm hover:bg-muted/60 transition-colors',
currentFolder === '' && 'bg-muted font-medium',
)}
>
<span className="w-3.5" />
{currentFolder === '' ? (
<FolderOpen className="h-4 w-4 shrink-0 text-primary" />
) : (
<Folder className="h-4 w-4 shrink-0 text-muted-foreground" />
)}
<span>All Files</span>
</button>
{Object.values(tree.children).map((child) => (
<FolderNodeComponent
key={child.fullPath}
node={child}
currentFolder={currentFolder}
onFolderSelect={onFolderSelect}
/>
))}
</div>
);
}