From 221c628e71a299825e30079ebdc961644e8feaf8 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sun, 5 Oct 2025 20:51:19 +0200 Subject: [PATCH] grouped topics --- .../primary/DiscussionsPage/ViewToggle.tsx | 67 +++++++++++ src/pages/primary/DiscussionsPage/index.tsx | 109 +++++++++++++++++- 2 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 src/pages/primary/DiscussionsPage/ViewToggle.tsx diff --git a/src/pages/primary/DiscussionsPage/ViewToggle.tsx b/src/pages/primary/DiscussionsPage/ViewToggle.tsx new file mode 100644 index 0000000..8ace7fb --- /dev/null +++ b/src/pages/primary/DiscussionsPage/ViewToggle.tsx @@ -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 ( + + + + + + {viewOptions.map(option => ( + onViewModeChange(option.id)} + className="flex items-start gap-3 p-3" + > + +
+
{option.label}
+
+ {option.description} +
+
+ {option.id === viewMode && ( + + )} +
+ ))} +
+
+ ) +} diff --git a/src/pages/primary/DiscussionsPage/index.tsx b/src/pages/primary/DiscussionsPage/index.tsx index e257ef9..18ebd17 100644 --- a/src/pages/primary/DiscussionsPage/index.tsx +++ b/src/pages/primary/DiscussionsPage/index.tsx @@ -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) => { const [showCreateThread, setShowCreateThread] = useState(false) const [statsLoaded, setStatsLoaded] = useState(false) const [customVoteStats, setCustomVoteStats] = useState>({}) + const [viewMode, setViewMode] = useState<'flat' | 'grouped'>('flat') + const [groupedThreads, setGroupedThreads] = useState>({}) // 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) => { 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) => { 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) + + // 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) => { {t('Discussions')} - {selectedTopic === 'all' ? t('All Topics') : DISCUSSION_TOPICS.find(t => t.id === selectedTopic)?.label}
+ {selectedTopic === 'all' && ( + + )} {
{t('Loading threads...')}
- ) : threads.length === 0 ? ( + ) : (viewMode === 'grouped' && selectedTopic === 'all' ? + Object.keys(groupedThreads).length === 0 : + threads.length === 0) ? ( @@ -497,6 +563,41 @@ const DiscussionsPage = forwardRef((_, ref) => {
+ ) : viewMode === 'grouped' && selectedTopic === 'all' ? ( +
+ {Object.entries(groupedThreads).length === 0 && ( +
+ Debug: No grouped threads found. groupedThreads keys: {Object.keys(groupedThreads).join(', ')} +
+ )} + {Object.entries(groupedThreads).map(([topicId, topicThreads]) => { + const topicInfo = DISCUSSION_TOPICS.find(t => t.id === topicId) + if (!topicInfo || topicThreads.length === 0) return null + + return ( +
+
+ +

{topicInfo.label}

+ + ({topicThreads.length} {topicThreads.length === 1 ? t('thread') : t('threads')}) + +
+
+ {topicThreads.map(thread => ( + { + push(toNote(thread)) + }} + /> + ))} +
+
+ ) + })} +
) : (
{threads.map(thread => (