'use client'; import React, { useState, useMemo } from 'react'; import { X, MessageCircle, Clock, Users, AlertCircle, CheckCircle, Eye, Plus, Filter } from 'lucide-react'; import { useSidebarStore } from '@/stores/sidebar-store'; import useFlowStore, { useFlowStoreActions } from '@/stores/flow-store'; interface CommentData { id: string; title: string; status: 'Open' | 'In Progress' | 'Resolved' | 'Closed'; priority: 'Low' | 'Medium' | 'High' | 'Critical'; category: 'Question' | 'Suggestion' | 'Issue' | 'Approval' | 'General'; comments: Array<{ id: string; author: string; content: string; timestamp: string; parentId?: string; type?: 'user' | 'system'; }>; createdAt: string; updatedAt: string; } function classNames(...classes: any) { return classes.filter(Boolean).join(' '); } const FeedbackPanel = () => { const { showFeedbackPanel, setFeedbackPanelOpen } = useSidebarStore(); const { nodes } = useFlowStore(); const { addNode } = useFlowStoreActions(); const [selectedThreadId, setSelectedThreadId] = useState(null); const [selectedPriorities, setSelectedPriorities] = useState>(new Set(['Low', 'Medium', 'High', 'Critical'])); const [selectedCategories, setSelectedCategories] = useState>(new Set(['Question', 'Suggestion', 'Issue', 'Approval', 'General'])); const [showFilters, setShowFilters] = useState(false); // Extract and sort comment threads const commentThreads = useMemo(() => { const threads = nodes .filter(node => node.type === 'comment') .map(node => { const data = node.data as CommentData; return { nodeId: node.id, id: data?.id || node.id, title: data?.title || 'Untitled Thread', status: (data?.status || 'Open') as 'Open' | 'In Progress' | 'Resolved' | 'Closed', priority: (data?.priority || 'Medium') as 'Low' | 'Medium' | 'High' | 'Critical', category: (data?.category || 'General') as 'Question' | 'Suggestion' | 'Issue' | 'Approval' | 'General', comments: data?.comments || [], createdAt: data?.createdAt || new Date().toISOString(), updatedAt: data?.updatedAt || new Date().toISOString() }; }) .filter(thread => selectedPriorities.has(thread.priority) && selectedCategories.has(thread.category) ) .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); return threads; }, [nodes, selectedPriorities, selectedCategories]); const statusColors = { Open: 'bg-blue-100 text-blue-800 border-blue-300', 'In Progress': 'bg-yellow-100 text-yellow-800 border-yellow-300', Resolved: 'bg-green-100 text-green-800 border-green-300', Closed: 'bg-gray-100 text-gray-600 border-gray-300' }; const priorityColors = { Low: 'text-gray-600', Medium: 'text-blue-700', High: 'text-orange-700', Critical: 'text-red-700' }; const categoryIcons = { Question: '❓', Suggestion: '💡', Issue: '⚠️', Approval: '✅', General: '💬' }; const statusIcons = { Open: AlertCircle, 'In Progress': Clock, Resolved: CheckCircle, Closed: Eye }; const formatTimeAgo = (timestamp: string) => { const date = new Date(timestamp); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); const diffDays = Math.floor(diffHours / 24); if (diffDays > 0) return `${diffDays}d ago`; if (diffHours > 0) return `${diffHours}h ago`; return 'Just now'; }; const handleThreadClick = (threadId: string) => { // Find the corresponding node and zoom to it const node = nodes.find(n => n.id === threadId); if (node) { setSelectedThreadId(threadId); // Get ReactFlow instance from the store const { reactFlowInstance } = useFlowStore.getState(); if (reactFlowInstance) { // Zoom to the specific node with some padding reactFlowInstance.fitView({ nodes: [{ id: threadId }], duration: 800, // Smooth animation padding: 0.3, // 30% padding around the node }); } } }; const togglePriorityFilter = (priority: string) => { const newSelected = new Set(selectedPriorities); if (newSelected.has(priority)) { newSelected.delete(priority); } else { newSelected.add(priority); } setSelectedPriorities(newSelected); }; const toggleCategoryFilter = (category: string) => { const newSelected = new Set(selectedCategories); if (newSelected.has(category)) { newSelected.delete(category); } else { newSelected.add(category); } setSelectedCategories(newSelected); }; const handleCreateNewThread = () => { const newThreadId = `comment-thread-${Date.now()}`; const newThread = { id: newThreadId, type: 'comment', position: { x: 100, y: 100 }, data: { id: newThreadId, title: 'New Discussion', status: 'Open' as const, priority: 'Medium' as const, category: 'General' as const, comments: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() } }; addNode(newThread); setSelectedThreadId(newThreadId); }; if (!showFeedbackPanel) return null; return (
{/* Header */}

Feedback

{/* Action buttons */}
{/* Filter controls */} {showFilters && (
{/* Priority filters */}

Priority

{['Low', 'Medium', 'High', 'Critical'].map(priority => ( ))}
{/* Category filters */}

Category

{['Question', 'Suggestion', 'Issue', 'Approval', 'General'].map(category => ( ))}
)}

{commentThreads.length} {commentThreads.length === 1 ? 'thread' : 'threads'} {(selectedPriorities.size < 4 || selectedCategories.size < 5) && ( (filtered) )}

{/* Thread List */}
{commentThreads.length === 0 ? (

No feedback threads yet

Add comment threads to your design to start collecting feedback

) : (
{commentThreads.map((thread) => { const StatusIcon = statusIcons[thread.status] || AlertCircle; const isSelected = selectedThreadId === thread.nodeId; return (
handleThreadClick(thread.nodeId)} className={classNames( "p-3 mb-2 rounded-lg border cursor-pointer transition-all hover:shadow-md", isSelected ? "border-orange-300 bg-orange-50 shadow-sm" : "border-gray-200 bg-white hover:border-gray-300" )} > {/* Thread Header */}
{categoryIcons[thread.category]}

{thread.title}

{thread.category} • {formatTimeAgo(thread.createdAt)}

{thread.comments.length > 0 && ( {thread.comments.length} )}
{/* Thread Footer */}
{thread.priority}
{thread.status}
); })}
)}
{/* Footer Stats */} {commentThreads.length > 0 && (
{commentThreads.filter(t => t.status === 'Open' || t.status === 'In Progress').length}
Active
{commentThreads.filter(t => t.status === 'Resolved').length}
Resolved
)}
); }; export default FeedbackPanel;