import { useState, useEffect, useRef } from 'react' import Image from 'next/image' import type { Article } from '@/types/nostr' import { useAuthorsProfiles } from '@/hooks/useAuthorsProfiles' import { generateMnemonicIcons } from '@/lib/mnemonicIcons' import { t } from '@/lib/i18n' export type SortOption = 'newest' | 'oldest' export interface ArticleFilters { authorPubkey: string | null sortBy: SortOption category: 'science-fiction' | 'scientific-research' | 'all' | null } interface ArticleFiltersProps { filters: ArticleFilters onFiltersChange: (filters: ArticleFilters) => void articles: Article[] } interface FiltersData { authors: string[] } function useFiltersData(articles: Article[]): FiltersData { const authorArticleCount = new Map() articles.forEach((article) => { if (!article.isPresentation) { const count = authorArticleCount.get(article.pubkey) ?? 0 authorArticleCount.set(article.pubkey, count + 1) } }) const authors = Array.from(authorArticleCount.keys()).filter((pubkey) => { const count = authorArticleCount.get(pubkey) ?? 0 return count > 0 }) return { authors, } } function FiltersGrid({ data, filters, onFiltersChange, }: { data: FiltersData filters: ArticleFilters onFiltersChange: (filters: ArticleFilters) => void }) { const update = (patch: Partial) => onFiltersChange({ ...filters, ...patch }) return (
update({ authorPubkey: value })} /> update({ sortBy: value })} />
) } function FiltersHeader({ hasActiveFilters, onClear, }: { hasActiveFilters: boolean onClear: () => void }) { return (

{t('filters.sort')}

{hasActiveFilters && ( )}
) } function AuthorFilter({ authors, value, onChange, }: { authors: string[] value: string | null onChange: (value: string | null) => void }) { const { profiles, loading } = useAuthorsProfiles(authors) const [isOpen, setIsOpen] = useState(false) const dropdownRef = useRef(null) const buttonRef = useRef(null) useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( dropdownRef.current && buttonRef.current && !dropdownRef.current.contains(event.target as Node) && !buttonRef.current.contains(event.target as Node) ) { setIsOpen(false) } } const handleEscape = (event: KeyboardEvent) => { if (event.key === 'Escape') { setIsOpen(false) buttonRef.current?.focus() } } if (isOpen) { document.addEventListener('mousedown', handleClickOutside) document.addEventListener('keydown', handleEscape) } return () => { document.removeEventListener('mousedown', handleClickOutside) document.removeEventListener('keydown', handleEscape) } }, [isOpen]) const getDisplayName = (pubkey: string): string => { const profile = profiles.get(pubkey) return profile?.name ?? `${pubkey.substring(0, 8)}...${pubkey.substring(pubkey.length - 8)}` } const getPicture = (pubkey: string): string | undefined => { return profiles.get(pubkey)?.picture } const getMnemonicIcons = (pubkey: string): string[] => { return generateMnemonicIcons(pubkey) } const selectedAuthor = value ? profiles.get(value) : null const selectedDisplayName = value ? getDisplayName(value) : t('filters.author') return (
{isOpen && (
{loading ? (
{t('filters.loading')}
) : ( authors.map((pubkey) => { const displayName = getDisplayName(pubkey) const picture = getPicture(pubkey) const mnemonicIcons = getMnemonicIcons(pubkey) const isSelected = value === pubkey return ( ) }) )}
)}
) } function SortFilter({ value, onChange, }: { value: SortOption onChange: (value: SortOption) => void }) { return (
) } export function ArticleFiltersComponent({ filters, onFiltersChange, articles, }: ArticleFiltersProps) { const data = useFiltersData(articles) const handleClearFilters = () => { onFiltersChange({ authorPubkey: null, sortBy: 'newest', category: null, }) } const hasActiveFilters = filters.authorPubkey !== null || filters.sortBy !== 'newest' || filters.category !== null return (
) }