Add tree organization system with categories, tags, and folders

Features:
- Categories: Global and team-specific tree categorization (admin-managed)
- Tags: Flexible tree tagging with autocomplete (author + admin)
- User folders: Personal tree collections with subfolder support
  - Hierarchical structure (max 3 levels deep)
  - Right-click context menu for folder management
  - Cascade delete for subfolders
- Filter trees by category, tags, and folder in library view

Backend:
- New models: Category, Tag, UserFolder with relationships
- New API endpoints for categories, tags, and folders
- Tree organization migrations (005, 006)

Frontend:
- FolderSidebar with hierarchical folder tree
- FolderEditModal for create/edit with color picker
- AddToFolderMenu for quick tree organization
- TagInput with autocomplete and TagBadges display
- Updated TreeMetadataForm and TreeLibraryPage

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-02 01:31:13 -05:00
parent 2d99c52025
commit fafdaa50a5
41 changed files with 5006 additions and 221 deletions

View File

@@ -1,23 +1,8 @@
import apiClient from './client'
import type { Tree, TreeListItem, TreeCreate, TreeUpdate } from '@/types'
export interface TreeListParams {
page?: number
size?: number
category?: string
include_inactive?: boolean
}
export interface TreeListResponse {
items: TreeListItem[]
total: number
page: number
size: number
pages: number
}
import type { Tree, TreeListItem, TreeCreate, TreeUpdate, TreeFilters } from '@/types'
export const treesApi = {
async list(params?: TreeListParams): Promise<TreeListItem[]> {
async list(params?: TreeFilters): Promise<TreeListItem[]> {
const response = await apiClient.get<TreeListItem[]>('/trees', { params })
return response.data
},
@@ -41,14 +26,15 @@ export const treesApi = {
await apiClient.delete(`/trees/${id}`)
},
async categories(): Promise<string[]> {
// Legacy categories endpoint (returns string categories)
async legacyCategories(): Promise<string[]> {
const response = await apiClient.get<string[]>('/trees/categories')
return response.data
},
async search(query: string, category?: string): Promise<TreeListItem[]> {
async search(query: string, limit?: number): Promise<TreeListItem[]> {
const response = await apiClient.get<TreeListItem[]>('/trees/search', {
params: { q: query, category },
params: { q: query, limit },
})
return response.data
},