import { useState, useEffect, useMemo } from 'react' import { Search, ChevronDown, ChevronUp, Loader2 } from 'lucide-react' import { cn } from '@/lib/utils' import { stepsApi } from '@/api/steps' import { stepCategoriesApi } from '@/api/stepCategories' import { StepCard } from './StepCard' import { StepDetailModal } from './StepDetailModal' import type { Step, StepListItem, StepCategory, PopularTag, StepListParams } from '@/types/step' interface StepLibraryBrowserProps { onInsert?: (step: Step) => void onCreateNew?: () => void showCreateButton?: boolean onEdit?: (step: StepListItem) => void onDelete?: (step: StepListItem) => void onSave?: (step: StepListItem) => void currentUserId?: string refreshKey?: number } export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = false, onEdit, onDelete, onSave, currentUserId, refreshKey }: StepLibraryBrowserProps) { // State const [steps, setSteps] = useState([]) const [categories, setCategories] = useState([]) const [popularTags, setPopularTags] = useState([]) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) // Filters const [searchQuery, setSearchQuery] = useState('') const [selectedCategoryId, setSelectedCategoryId] = useState() const [selectedStepType, setSelectedStepType] = useState<'decision' | 'action' | 'solution' | undefined>() const [minRating, setMinRating] = useState() const [sortBy, setSortBy] = useState<'recent' | 'popular' | 'highest_rated' | 'most_used'>('recent') const [selectedTag, setSelectedTag] = useState() // UI state const [previewStepId, setPreviewStepId] = useState(null) const [collapsedSections, setCollapsedSections] = useState>({}) const [retryCount, setRetryCount] = useState(0) // Load initial data useEffect(() => { const loadInitialData = async () => { setIsLoading(true) setError(null) try { const [categoriesData, tagsData] = await Promise.all([ stepCategoriesApi.list(), stepsApi.getPopularTags() ]) setCategories(categoriesData.filter(c => c.is_active)) setPopularTags(tagsData.slice(0, 10)) // Show top 10 tags } catch (err) { console.error('Failed to load initial data:', err) setError('Failed to load categories and tags') } finally { setIsLoading(false) } } loadInitialData() }, [retryCount]) // Load steps when filters change useEffect(() => { const loadSteps = async () => { setIsLoading(true) setError(null) try { const params: StepListParams = { category_id: selectedCategoryId, step_type: selectedStepType, min_rating: minRating, sort_by: sortBy, tags: selectedTag ? [selectedTag] : undefined } let stepsData: StepListItem[] if (searchQuery.trim()) { stepsData = await stepsApi.search(searchQuery) } else { stepsData = await stepsApi.list(params) } setSteps(stepsData) } catch (err) { console.error('Failed to load steps:', err) setError('Failed to load steps') } finally { setIsLoading(false) } } loadSteps() }, [searchQuery, selectedCategoryId, selectedStepType, minRating, sortBy, selectedTag, refreshKey, retryCount]) // Group steps by visibility const groupedSteps = useMemo(() => { return { private: steps.filter(s => s.visibility === 'private'), team: steps.filter(s => s.visibility === 'team'), public: steps.filter(s => s.visibility === 'public') } }, [steps]) const toggleSection = (section: string) => { setCollapsedSections(prev => ({ ...prev, [section]: !prev[section] })) } const handlePreview = (step: StepListItem) => { setPreviewStepId(step.id) } const handleInsertFromPreview = (step: Step) => { setPreviewStepId(null) if (onInsert) { onInsert(step) } } const handleInsertFromCard = (stepItem: StepListItem) => { if (onInsert) { stepsApi.get(stepItem.id).then(onInsert) } } const handleTagClick = (tag: string) => { setSelectedTag(selectedTag === tag ? undefined : tag) } const clearFilters = () => { setSearchQuery('') setSelectedCategoryId(undefined) setSelectedStepType(undefined) setMinRating(undefined) setSelectedTag(undefined) } const hasActiveFilters = searchQuery || selectedCategoryId || selectedStepType || minRating || selectedTag return (
{/* Header - Filters */}
{/* Search */}
setSearchQuery(e.target.value)} className="w-full rounded-md border border-border bg-card py-2 pl-10 pr-4 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20" />
{/* Filter Row */}
{/* Category Filter */} {/* Type Filter */} {/* Min Rating Filter */} {/* Sort By */}
{/* Popular Tags */} {popularTags.length > 0 && (
Popular Tags:
{popularTags.map(tag => ( ))}
)} {/* Clear Filters */} {hasActiveFilters && ( )}
{/* Body - Step List */}
{isLoading ? (
) : error ? (

{error}

) : steps.length === 0 ? (

No steps found

{hasActiveFilters ? 'Try adjusting your filters' : 'Create your first step to get started!'}

) : (
{/* My Steps */} {groupedSteps.private.length > 0 && (
{!collapsedSections.private && (
{groupedSteps.private.map(step => ( ))}
)}
)} {/* Team Steps */} {groupedSteps.team.length > 0 && (
{!collapsedSections.team && (
{groupedSteps.team.map(step => ( ))}
)}
)} {/* Community Steps */} {groupedSteps.public.length > 0 && (
{!collapsedSections.public && (
{groupedSteps.public.map(step => ( ))}
)}
)}
)}
{/* Footer - Optional Create Button */} {showCreateButton && onCreateNew && (
)} {/* Preview Modal */} {previewStepId && ( setPreviewStepId(null)} onInsert={handleInsertFromPreview} /> )}
) }