mirror of
https://github.com/R0m1k3/FlowReader.git
synced 2026-10-20 21:58:11 +02:00
feat: implement DashboardPage with infinite scrolling, search, and article management features
This commit is contained in:
1 parent
908d907629
commit
3a7f7787df
1 file changed
+24
-12
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||||
import { useInfiniteQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useInfiniteQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import Masonry from 'react-masonry-css';
|
import Masonry from 'react-masonry-css';
|
||||||
import { articlesApi } from '../api/articles';
|
import { articlesApi } from '../api/articles';
|
||||||
@@ -86,6 +86,18 @@ export function DashboardPage({ selectedFeedId, onEnterFocus }: DashboardPagePro
|
|||||||
});
|
});
|
||||||
|
|
||||||
const articles = data?.pages.flat() || [];
|
const articles = data?.pages.flat() || [];
|
||||||
|
const sortedArticles = useMemo(() => {
|
||||||
|
return [...articles].sort((a, b) => {
|
||||||
|
// First, sort by read status: unread (false) first
|
||||||
|
if (a.is_read !== b.is_read) {
|
||||||
|
return a.is_read ? 1 : -1;
|
||||||
|
}
|
||||||
|
// If same read status, sort by date descending
|
||||||
|
const dateA = a.published_at || a.created_at;
|
||||||
|
const dateB = b.published_at || b.created_at;
|
||||||
|
return new Date(dateB) - new Date(dateA);
|
||||||
|
});
|
||||||
|
}, [articles]);
|
||||||
|
|
||||||
// Intersection Observer for Infinite Scroll
|
// Intersection Observer for Infinite Scroll
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -249,7 +261,7 @@ export function DashboardPage({ selectedFeedId, onEnterFocus }: DashboardPagePro
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Focus Mode CTA Widget - Compact Version */}
|
{/* Focus Mode CTA Widget - Compact Version */}
|
||||||
{!selectedFeedId && articles && articles.length > 0 && (
|
{!selectedFeedId && sortedArticles && sortedArticles.length > 0 && (
|
||||||
<div className="mb-12 relative overflow-hidden rounded-xl bg-gradient-to-r from-nature to-nature-light shadow-xl items-center flex flex-col md:flex-row p-6 hover:scale-[1.01] transition-transform cursor-pointer group" onClick={onEnterFocus}>
|
<div className="mb-12 relative overflow-hidden rounded-xl bg-gradient-to-r from-nature to-nature-light shadow-xl items-center flex flex-col md:flex-row p-6 hover:scale-[1.01] transition-transform cursor-pointer group" onClick={onEnterFocus}>
|
||||||
{/* decorative circles */}
|
{/* decorative circles */}
|
||||||
<div className="absolute -top-12 -right-12 w-48 h-48 bg-white/10 rounded-full blur-2xl group-hover:bg-white/20 transition-colors pointer-events-none"></div>
|
<div className="absolute -top-12 -right-12 w-48 h-48 bg-white/10 rounded-full blur-2xl group-hover:bg-white/20 transition-colors pointer-events-none"></div>
|
||||||
@@ -265,7 +277,7 @@ export function DashboardPage({ selectedFeedId, onEnterFocus }: DashboardPagePro
|
|||||||
Votre session de lecture
|
Votre session de lecture
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-white/80 font-medium text-xs max-w-lg">
|
<p className="text-white/80 font-medium text-xs max-w-lg">
|
||||||
{articles.filter(a => !a.is_read).length} articles non lus. Passez en mode Focus.
|
{sortedArticles.filter(a => !a.is_read).length} articles non lus. Passez en mode Focus.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -284,14 +296,14 @@ export function DashboardPage({ selectedFeedId, onEnterFocus }: DashboardPagePro
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Grid */}
|
{/* Grid */}
|
||||||
{articles.length > 0 ? (
|
{sortedArticles.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<Masonry
|
<Masonry
|
||||||
breakpointCols={breakpointColumnsObj}
|
breakpointCols={breakpointColumnsObj}
|
||||||
className="flex w-auto -ml-6"
|
className="flex w-auto -ml-6"
|
||||||
columnClassName="pl-6 bg-clip-padding"
|
columnClassName="pl-6 bg-clip-padding"
|
||||||
>
|
>
|
||||||
{articles.map((article, index) => (
|
{sortedArticles.map((article, index) => (
|
||||||
<div key={`${article.id}-${index}`} className="mb-6">
|
<div key={`${article.id}-${index}`} className="mb-6">
|
||||||
<ArticleCard
|
<ArticleCard
|
||||||
article={article}
|
article={article}
|
||||||
@@ -345,10 +357,10 @@ export function DashboardPage({ selectedFeedId, onEnterFocus }: DashboardPagePro
|
|||||||
onClose={() => setSelectedArticle(null)}
|
onClose={() => setSelectedArticle(null)}
|
||||||
onToggleFavorite={(id) => toggleFavoriteMutation.mutate(id)}
|
onToggleFavorite={(id) => toggleFavoriteMutation.mutate(id)}
|
||||||
onNext={() => {
|
onNext={() => {
|
||||||
if (!articles) return;
|
if (!sortedArticles) return;
|
||||||
const idx = articles.findIndex(a => a.id === selectedArticle.id);
|
const idx = sortedArticles.findIndex(a => a.id === selectedArticle.id);
|
||||||
if (idx < articles.length - 1) {
|
if (idx < sortedArticles.length - 1) {
|
||||||
const nextArticle = articles[idx + 1];
|
const nextArticle = sortedArticles[idx + 1];
|
||||||
setSelectedArticle(nextArticle);
|
setSelectedArticle(nextArticle);
|
||||||
if (!nextArticle.is_read) {
|
if (!nextArticle.is_read) {
|
||||||
toggleReadMutation.mutate({ id: nextArticle.id, is_read: true });
|
toggleReadMutation.mutate({ id: nextArticle.id, is_read: true });
|
||||||
@@ -360,10 +372,10 @@ export function DashboardPage({ selectedFeedId, onEnterFocus }: DashboardPagePro
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onPrev={() => {
|
onPrev={() => {
|
||||||
if (!articles) return;
|
if (!sortedArticles) return;
|
||||||
const idx = articles.findIndex(a => a.id === selectedArticle.id);
|
const idx = sortedArticles.findIndex(a => a.id === selectedArticle.id);
|
||||||
if (idx > 0) {
|
if (idx > 0) {
|
||||||
const prevArticle = articles[idx - 1];
|
const prevArticle = sortedArticles[idx - 1];
|
||||||
setSelectedArticle(prevArticle);
|
setSelectedArticle(prevArticle);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in new issue
Block a user