Files
resolutionflow/frontend/src/components/library/TreeGridView.tsx
chihlasm 8441a8dbaf feat: maintenance flows frontend foundation - types, API clients, sidebar, library filter
- Add maintenance.ts types: TargetEntry, TargetList, MaintenanceSchedule, BatchLaunch
- Add targetListsApi and maintenanceSchedulesApi/batchLaunchApi clients
- Extend TreeType union with 'maintenance' in tree.ts
- Update getTreeNavigatePath/getTreeEditorPath in routing.ts for maintenance
- Sidebar: track maintenance count and add Maintenance sub-nav item
- TreeLibraryPage: add maintenance to typeFilter state, URL sync, and tab buttons
- TreeGridView, TreeListView, TreeTableView: add amber Wrench maintenance badge

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-17 14:06:36 -05:00

143 lines
5.4 KiB
TypeScript

import { Link } from 'react-router-dom'
import { Pencil, Globe, Lock, Trash2, GitBranch, FileText, Wrench } from 'lucide-react'
import type { TreeListItem } from '@/types'
import { TagBadges } from '@/components/common/TagBadges'
import { AddToFolderMenu } from './AddToFolderMenu'
import { cn } from '@/lib/utils'
import { usePermissions } from '@/hooks/usePermissions'
interface TreeGridViewProps {
trees: TreeListItem[]
onStartSession: (treeId: string, treeType?: string) => void
onTagClick: (tag: string) => void
onFolderCreated: (parentId?: string | null) => void
onDeleteTree: (tree: TreeListItem) => void
onForkTree?: (treeId: string) => void
}
export function TreeGridView({
trees,
onStartSession,
onTagClick,
onFolderCreated,
onDeleteTree,
onForkTree,
}: TreeGridViewProps) {
const { canEditTree, canDeleteTree } = usePermissions()
return (
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{trees.map((tree) => (
<div
key={tree.id}
className="bg-card border border-border rounded-2xl p-4 transition-all hover:-translate-y-0.5 hover:border-primary/30 hover:shadow-md sm:p-6"
>
<div className="mb-2 flex items-start justify-between gap-2">
<div className="flex items-center gap-2">
<h3 className="font-semibold text-foreground">{tree.name}</h3>
{tree.status === 'draft' && (
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400">
<FileText className="h-3 w-3" />
Draft
</span>
)}
{tree.tree_type === 'maintenance' && (
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wide text-amber-400">
<Wrench className="h-3 w-3" />
Maintenance
</span>
)}
</div>
<div className="flex items-center gap-2">
{tree.is_public ? (
<span title="Public tree">
<Globe className="h-4 w-4 text-muted-foreground" />
</span>
) : (
<span title="Private tree">
<Lock className="h-4 w-4 text-muted-foreground" />
</span>
)}
{tree.category_info && (
<span className="rounded-full bg-accent px-2 py-0.5 text-xs text-muted-foreground">
{tree.category_info.name}
</span>
)}
</div>
</div>
<p className="mb-3 text-sm text-muted-foreground line-clamp-2">
{tree.description || 'No description available'}
</p>
{/* Tags */}
{tree.tags && tree.tags.length > 0 && (
<div className="mb-3">
<TagBadges tags={tree.tags} maxVisible={3} onTagClick={onTagClick} />
</div>
)}
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">
v{tree.version} · {tree.usage_count} uses
</span>
<div className="flex items-center gap-2">
<AddToFolderMenu treeId={tree.id} onFolderCreated={onFolderCreated} />
{onForkTree && (
<button
type="button"
onClick={() => onForkTree(tree.id)}
className={cn(
'rounded-md border border-border p-2 text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
title="Fork tree"
aria-label="Fork tree"
>
<GitBranch className="h-4 w-4" />
</button>
)}
{canEditTree({ author_id: tree.author_id, account_id: tree.account_id }) && (
<Link
to={`/trees/${tree.id}/edit`}
className={cn(
'rounded-md border border-border p-2 text-muted-foreground',
'hover:bg-accent hover:text-foreground'
)}
title="Edit tree"
aria-label="Edit tree"
>
<Pencil className="h-4 w-4" />
</Link>
)}
{canDeleteTree() && (
<button
type="button"
onClick={() => onDeleteTree(tree)}
className={cn(
'rounded-md border border-border p-1.5 text-muted-foreground',
'hover:bg-red-400/10 hover:text-red-400'
)}
title="Delete tree"
aria-label="Delete tree"
>
<Trash2 className="h-4 w-4" />
</button>
)}
<button
type="button"
onClick={() => onStartSession(tree.id, tree.tree_type)}
className={cn(
'rounded-md bg-gradient-brand px-3 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
'hover:opacity-90'
)}
>
Start Session
</button>
</div>
</div>
</div>
))}
</div>
)
}