Browse Source

grouped topics

imwald
Silberengel 5 months ago
parent
commit
221c628e71
  1. 67
      src/pages/primary/DiscussionsPage/ViewToggle.tsx
  2. 109
      src/pages/primary/DiscussionsPage/index.tsx

67
src/pages/primary/DiscussionsPage/ViewToggle.tsx

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
import { Button } from '@/components/ui/button'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
import { ChevronDown, List, Grid3X3 } from 'lucide-react'
import { useTranslation } from 'react-i18next'
interface ViewToggleProps {
viewMode: 'flat' | 'grouped'
onViewModeChange: (mode: 'flat' | 'grouped') => void
disabled?: boolean
}
export default function ViewToggle({ viewMode, onViewModeChange, disabled = false }: ViewToggleProps) {
const { t } = useTranslation()
const viewOptions = [
{
id: 'flat' as const,
label: t('Flat View'),
icon: List,
description: t('Show all discussions in a single list')
},
{
id: 'grouped' as const,
label: t('Grouped View'),
icon: Grid3X3,
description: t('Group discussions by topic')
}
]
const selectedOption = viewOptions.find(option => option.id === viewMode) || viewOptions[0]
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="flex items-center gap-2 h-10 px-3 min-w-32"
disabled={disabled}
>
<selectedOption.icon className="w-4 h-4" />
<span className="flex-1 text-left">{selectedOption.label}</span>
<ChevronDown className="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-64">
{viewOptions.map(option => (
<DropdownMenuItem
key={option.id}
onClick={() => onViewModeChange(option.id)}
className="flex items-start gap-3 p-3"
>
<option.icon className="w-4 h-4 mt-0.5" />
<div className="flex-1">
<div className="font-medium">{option.label}</div>
<div className="text-xs text-muted-foreground mt-1">
{option.description}
</div>
</div>
{option.id === viewMode && (
<span className="text-primary"></span>
)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)
}

109
src/pages/primary/DiscussionsPage/index.tsx

@ -11,6 +11,7 @@ import ThreadCard from '@/pages/primary/DiscussionsPage/ThreadCard' @@ -11,6 +11,7 @@ import ThreadCard from '@/pages/primary/DiscussionsPage/ThreadCard'
import TopicFilter from '@/pages/primary/DiscussionsPage/TopicFilter'
import ThreadSort, { SortOption } from '@/pages/primary/DiscussionsPage/ThreadSort'
import CreateThreadDialog, { DISCUSSION_TOPICS } from '@/pages/primary/DiscussionsPage/CreateThreadDialog'
import ViewToggle from '@/pages/primary/DiscussionsPage/ViewToggle'
import { NostrEvent } from 'nostr-tools'
import client from '@/services/client.service'
import noteStatsService from '@/services/note-stats.service'
@ -32,6 +33,8 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -32,6 +33,8 @@ const DiscussionsPage = forwardRef((_, ref) => {
const [showCreateThread, setShowCreateThread] = useState(false)
const [statsLoaded, setStatsLoaded] = useState(false)
const [customVoteStats, setCustomVoteStats] = useState<Record<string, { upvotes: number; downvotes: number; score: number; controversy: number }>>({})
const [viewMode, setViewMode] = useState<'flat' | 'grouped'>('flat')
const [groupedThreads, setGroupedThreads] = useState<Record<string, NostrEvent[]>>({})
// Use DEFAULT_FAVORITE_RELAYS for logged-out users, or user's favorite relays for logged-in users
const availableRelays = pubkey && favoriteRelays.length > 0 ? favoriteRelays : DEFAULT_FAVORITE_RELAYS
@ -136,9 +139,9 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -136,9 +139,9 @@ const DiscussionsPage = forwardRef((_, ref) => {
console.log('Waiting for stats to load before sorting...')
return
}
console.log('Running filterThreadsByTopic with selectedSort:', selectedSort, 'statsLoaded:', statsLoaded)
console.log('Running filterThreadsByTopic with selectedSort:', selectedSort, 'statsLoaded:', statsLoaded, 'viewMode:', viewMode, 'selectedTopic:', selectedTopic)
filterThreadsByTopic()
}, [allThreads, selectedTopic, selectedSort, statsLoaded])
}, [allThreads, selectedTopic, selectedSort, statsLoaded, viewMode])
// Fetch stats when sort changes to top/controversial
useEffect(() => {
@ -403,7 +406,62 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -403,7 +406,62 @@ const DiscussionsPage = forwardRef((_, ref) => {
console.log('Sorted by default (newest)')
}
setThreads(threadsForTopic)
// If grouped view and showing all topics, group threads by topic
if (viewMode === 'grouped' && selectedTopic === 'all') {
// Group threads by topic
const groupedThreads = categorizedThreads.reduce((groups, thread) => {
const topic = thread._categorizedTopic
if (!groups[topic]) {
groups[topic] = []
}
// Remove the temporary categorization property but keep relay source
const { _categorizedTopic, ...cleanThread } = thread
groups[topic].push(cleanThread)
return groups
}, {} as Record<string, NostrEvent[]>)
// Sort threads within each group
Object.keys(groupedThreads).forEach(topic => {
groupedThreads[topic] = sortThreads(groupedThreads[topic])
})
// Store grouped data in a different state
console.log('Setting grouped threads:', groupedThreads)
setGroupedThreads(groupedThreads)
setThreads([]) // Clear flat threads
} else {
// Flat view or specific topic selected
setThreads(threadsForTopic)
setGroupedThreads({}) // Clear grouped threads
}
}
// Helper function to sort threads
const sortThreads = (threadsToSort: NostrEvent[]) => {
const sortedThreads = [...threadsToSort]
switch (selectedSort) {
case 'newest':
return sortedThreads.sort((a, b) => b.created_at - a.created_at)
case 'oldest':
return sortedThreads.sort((a, b) => a.created_at - b.created_at)
case 'top':
return sortedThreads.sort((a, b) => {
const scoreA = getThreadVoteScore(a)
const scoreB = getThreadVoteScore(b)
if (scoreA !== scoreB) return scoreB - scoreA
return b.created_at - a.created_at
})
case 'controversial':
return sortedThreads.sort((a, b) => {
const controversyA = getThreadControversyScore(a)
const controversyB = getThreadControversyScore(b)
if (controversyA !== controversyB) return controversyB - controversyA
return b.created_at - a.created_at
})
default:
return sortedThreads.sort((a, b) => b.created_at - a.created_at)
}
}
const handleCreateThread = () => {
@ -464,6 +522,12 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -464,6 +522,12 @@ const DiscussionsPage = forwardRef((_, ref) => {
{t('Discussions')} - {selectedTopic === 'all' ? t('All Topics') : DISCUSSION_TOPICS.find(t => t.id === selectedTopic)?.label}
</h1>
<div className="flex items-center gap-2">
{selectedTopic === 'all' && (
<ViewToggle
viewMode={viewMode}
onViewModeChange={setViewMode}
/>
)}
<ThreadSort
selectedSort={selectedSort}
onSortChange={setSelectedSort}
@ -475,7 +539,9 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -475,7 +539,9 @@ const DiscussionsPage = forwardRef((_, ref) => {
<div className="flex justify-center py-8">
<div className="text-muted-foreground">{t('Loading threads...')}</div>
</div>
) : threads.length === 0 ? (
) : (viewMode === 'grouped' && selectedTopic === 'all' ?
Object.keys(groupedThreads).length === 0 :
threads.length === 0) ? (
<Card>
<CardContent className="p-8 text-center">
<MessageSquarePlus className="w-12 h-12 mx-auto mb-4 text-muted-foreground" />
@ -497,6 +563,41 @@ const DiscussionsPage = forwardRef((_, ref) => { @@ -497,6 +563,41 @@ const DiscussionsPage = forwardRef((_, ref) => {
</div>
</CardContent>
</Card>
) : viewMode === 'grouped' && selectedTopic === 'all' ? (
<div className="space-y-6">
{Object.entries(groupedThreads).length === 0 && (
<div className="text-center py-8 text-muted-foreground">
Debug: No grouped threads found. groupedThreads keys: {Object.keys(groupedThreads).join(', ')}
</div>
)}
{Object.entries(groupedThreads).map(([topicId, topicThreads]) => {
const topicInfo = DISCUSSION_TOPICS.find(t => t.id === topicId)
if (!topicInfo || topicThreads.length === 0) return null
return (
<div key={topicId} className="space-y-3">
<div className="flex items-center gap-2 pb-2 border-b">
<topicInfo.icon className="w-5 h-5 text-primary" />
<h2 className="text-lg font-semibold">{topicInfo.label}</h2>
<span className="text-sm text-muted-foreground">
({topicThreads.length} {topicThreads.length === 1 ? t('thread') : t('threads')})
</span>
</div>
<div className="space-y-3">
{topicThreads.map(thread => (
<ThreadCard
key={thread.id}
thread={thread}
onThreadClick={() => {
push(toNote(thread))
}}
/>
))}
</div>
</div>
)
})}
</div>
) : (
<div className="space-y-3">
{threads.map(thread => (

Loading…
Cancel
Save