mirror of
https://github.com/dergigi/boris.git
synced 2026-01-27 02:34:33 +01:00
feat: add filter buttons to Archive tab
- Create ArchiveFilters component with 5 filter options - All: Show all archived articles - To Read: Articles with 0% progress (not started) - Reading: Articles with progress between 0-95% - Completed: Articles with 95%+ reading progress - Marked: Manually marked as read (no position data) - Filter logic based on reading position data - Show empty state when no articles match filter - Matches BookmarkFilters styling and UX pattern
This commit is contained in:
39
src/components/ArchiveFilters.tsx
Normal file
39
src/components/ArchiveFilters.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React from 'react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faBookOpen, faBookmark, faCheckCircle } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faBooks } from '../icons/customIcons'
|
||||
|
||||
export type ArchiveFilterType = 'all' | 'to-read' | 'reading' | 'completed' | 'marked'
|
||||
|
||||
interface ArchiveFiltersProps {
|
||||
selectedFilter: ArchiveFilterType
|
||||
onFilterChange: (filter: ArchiveFilterType) => void
|
||||
}
|
||||
|
||||
const ArchiveFilters: React.FC<ArchiveFiltersProps> = ({ selectedFilter, onFilterChange }) => {
|
||||
const filters: { id: ArchiveFilterType; label: string; icon: typeof faBookOpen }[] = [
|
||||
{ id: 'all', label: 'All', icon: faBooks },
|
||||
{ id: 'to-read', label: 'To Read', icon: faBookmark },
|
||||
{ id: 'reading', label: 'Reading', icon: faBookOpen },
|
||||
{ id: 'completed', label: 'Completed', icon: faCheckCircle },
|
||||
{ id: 'marked', label: 'Marked', icon: faCheckCircle }
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="bookmark-filters">
|
||||
{filters.map((filter) => (
|
||||
<button
|
||||
key={filter.id}
|
||||
className={`bookmark-filter-btn ${selectedFilter === filter.id ? 'active' : ''}`}
|
||||
onClick={() => onFilterChange(filter.id)}
|
||||
>
|
||||
<FontAwesomeIcon icon={filter.icon} />
|
||||
<span>{filter.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ArchiveFilters
|
||||
|
||||
@@ -27,6 +27,7 @@ import { groupIndividualBookmarks, hasContent } from '../utils/bookmarkUtils'
|
||||
import BookmarkFilters, { BookmarkFilterType } from './BookmarkFilters'
|
||||
import { filterBookmarksByType } from '../utils/bookmarkTypeClassifier'
|
||||
import { generateArticleIdentifier, loadReadingPosition } from '../services/readingPositionService'
|
||||
import ArchiveFilters, { ArchiveFilterType } from './ArchiveFilters'
|
||||
|
||||
interface MeProps {
|
||||
relayPool: RelayPool
|
||||
@@ -53,6 +54,7 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
|
||||
const [viewMode, setViewMode] = useState<ViewMode>('cards')
|
||||
const [refreshTrigger, setRefreshTrigger] = useState(0)
|
||||
const [bookmarkFilter, setBookmarkFilter] = useState<BookmarkFilterType>('all')
|
||||
const [archiveFilter, setArchiveFilter] = useState<ArchiveFilterType>('all')
|
||||
const [readingPositions, setReadingPositions] = useState<Map<string, number>>(new Map())
|
||||
|
||||
// Update local state when prop changes
|
||||
@@ -238,10 +240,34 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
|
||||
const allIndividualBookmarks = bookmarks.flatMap(b => b.individualBookmarks || [])
|
||||
.filter(hasContent)
|
||||
|
||||
// Apply filter
|
||||
// Apply bookmark filter
|
||||
const filteredBookmarks = filterBookmarksByType(allIndividualBookmarks, bookmarkFilter)
|
||||
|
||||
const groups = groupIndividualBookmarks(filteredBookmarks)
|
||||
|
||||
// Apply archive filter
|
||||
const filteredReadArticles = readArticles.filter(post => {
|
||||
const position = readingPositions.get(post.event.id)
|
||||
|
||||
switch (archiveFilter) {
|
||||
case 'to-read':
|
||||
// No position or 0% progress
|
||||
return !position || position === 0
|
||||
case 'reading':
|
||||
// Has some progress but not completed (0 < position < 1)
|
||||
return position !== undefined && position > 0 && position < 0.95
|
||||
case 'completed':
|
||||
// 95% or more read (we consider 95%+ as completed)
|
||||
return position !== undefined && position >= 0.95
|
||||
case 'marked':
|
||||
// Manually marked as read (in archive but no reading position data)
|
||||
// These are articles that were marked via the emoji reaction
|
||||
return !position || position === 0
|
||||
case 'all':
|
||||
default:
|
||||
return true
|
||||
}
|
||||
})
|
||||
const sections: Array<{ key: string; title: string; items: IndividualBookmark[] }> = [
|
||||
{ key: 'private', title: 'Private Bookmarks', items: groups.privateItems },
|
||||
{ key: 'public', title: 'Public Bookmarks', items: groups.publicItems },
|
||||
@@ -375,16 +401,30 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
|
||||
<FontAwesomeIcon icon={faSpinner} spin size="2x" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="explore-grid">
|
||||
{readArticles.map((post) => (
|
||||
<BlogPostCard
|
||||
key={post.event.id}
|
||||
post={post}
|
||||
href={getPostUrl(post)}
|
||||
readingProgress={readingPositions.get(post.event.id)}
|
||||
<>
|
||||
{readArticles.length > 0 && (
|
||||
<ArchiveFilters
|
||||
selectedFilter={archiveFilter}
|
||||
onFilterChange={setArchiveFilter}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{filteredReadArticles.length === 0 ? (
|
||||
<div className="explore-loading" style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', padding: '4rem', color: 'var(--text-secondary)' }}>
|
||||
No articles match this filter.
|
||||
</div>
|
||||
) : (
|
||||
<div className="explore-grid">
|
||||
{filteredReadArticles.map((post) => (
|
||||
<BlogPostCard
|
||||
key={post.event.id}
|
||||
post={post}
|
||||
href={getPostUrl(post)}
|
||||
readingProgress={readingPositions.get(post.event.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
case 'writings':
|
||||
|
||||
Reference in New Issue
Block a user