fix: fixed category filtering problem

This commit is contained in:
poyrazavsever
2026-03-11 13:29:52 +03:00
parent 875ea7f70e
commit b3e2b42de5
10 changed files with 82 additions and 31 deletions
+7 -3
View File
@@ -2,14 +2,18 @@ import { BlogContent } from "@/components/blog-content";
import { getBlogPageData } from "@/data/blog"; import { getBlogPageData } from "@/data/blog";
type BlogPageProps = { type BlogPageProps = {
searchParams?: Promise<{ page?: string }>; searchParams?: Promise<{ page?: string | string[]; category?: string | string[] }>;
}; };
export default async function BlogPage({ searchParams }: BlogPageProps) { export default async function BlogPage({ searchParams }: BlogPageProps) {
const resolved = searchParams ? await searchParams : undefined; const resolved = searchParams ? await searchParams : undefined;
const page = Number(resolved?.page ?? "1"); const pageParam = Array.isArray(resolved?.page) ? resolved?.page[0] : resolved?.page;
const categoryParam = Array.isArray(resolved?.category)
? resolved?.category[0]
: resolved?.category;
const page = Number(pageParam ?? "1");
const currentPage = Number.isFinite(page) && page > 0 ? Math.floor(page) : 1; const currentPage = Number.isFinite(page) && page > 0 ? Math.floor(page) : 1;
const data = await getBlogPageData(currentPage, 12); const data = await getBlogPageData(currentPage, 12, categoryParam);
return <BlogContent data={data} />; return <BlogContent data={data} />;
} }
+42 -14
View File
@@ -16,8 +16,30 @@ type BlogContentProps = {
data: BlogPageData; data: BlogPageData;
}; };
function pageHref(page: number) { function pageHref(page: number, category: string) {
return page <= 1 ? "/blog" : `/blog?page=${page}`; const params = new URLSearchParams();
if (page > 1) {
params.set("page", String(page));
}
if (category !== "All") {
params.set("category", category);
}
const query = params.toString();
return query ? `/blog?${query}` : "/blog";
}
function categoryHref(category: string) {
if (category === "All") {
return "/blog";
}
const params = new URLSearchParams();
params.set("category", category);
return `/blog?${params.toString()}`;
} }
export function BlogContent({ data }: BlogContentProps) { export function BlogContent({ data }: BlogContentProps) {
@@ -45,14 +67,15 @@ export function BlogContent({ data }: BlogContentProps) {
<Card className="rounded-sm border-border p-4"> <Card className="rounded-sm border-border p-4">
<Typography variant="large">Kategoriler</Typography> <Typography variant="large">Kategoriler</Typography>
<div className="mt-3 flex flex-wrap gap-2"> <div className="mt-3 flex flex-wrap gap-2">
{data.categories.map((category, index) => ( {data.categories.map((category) => (
<Badge <Link key={category} href={categoryHref(category)}>
key={category} <Badge
variant={index === 0 ? "default" : "outline"} variant={category === data.selectedCategory ? "default" : "outline"}
className="rounded-sm" className="cursor-pointer rounded-sm"
> >
{category} {category}
</Badge> </Badge>
</Link>
))} ))}
</div> </div>
</Card> </Card>
@@ -114,7 +137,9 @@ export function BlogContent({ data }: BlogContentProps) {
) : ( ) : (
<Card className="rounded-sm border-border p-5"> <Card className="rounded-sm border-border p-5">
<Typography variant="p" className="text-muted-foreground"> <Typography variant="p" className="text-muted-foreground">
Henüz blog yazısı bulunmuyor. {data.selectedCategory === "All"
? "Henüz blog yazısı bulunmuyor."
: `"${data.selectedCategory}" kategorisinde henüz blog yazısı bulunmuyor.`}
</Typography> </Typography>
</Card> </Card>
)} )}
@@ -123,14 +148,17 @@ export function BlogContent({ data }: BlogContentProps) {
<PaginationContent className="justify-start"> <PaginationContent className="justify-start">
<PaginationItem> <PaginationItem>
<PaginationPrevious <PaginationPrevious
href={pageHref(Math.max(1, data.currentPage - 1))} href={pageHref(Math.max(1, data.currentPage - 1), data.selectedCategory)}
aria-disabled={data.currentPage <= 1} aria-disabled={data.currentPage <= 1}
/> />
</PaginationItem> </PaginationItem>
{pageNumbers.map((page) => ( {pageNumbers.map((page) => (
<PaginationItem key={page}> <PaginationItem key={page}>
<PaginationLink href={pageHref(page)} isActive={page === data.currentPage}> <PaginationLink
href={pageHref(page, data.selectedCategory)}
isActive={page === data.currentPage}
>
{page} {page}
</PaginationLink> </PaginationLink>
</PaginationItem> </PaginationItem>
@@ -138,7 +166,7 @@ export function BlogContent({ data }: BlogContentProps) {
<PaginationItem> <PaginationItem>
<PaginationNext <PaginationNext
href={pageHref(Math.min(data.totalPages, data.currentPage + 1))} href={pageHref(Math.min(data.totalPages, data.currentPage + 1), data.selectedCategory)}
aria-disabled={data.currentPage >= data.totalPages} aria-disabled={data.currentPage >= data.totalPages}
/> />
</PaginationItem> </PaginationItem>
+1 -1
View File
@@ -4,7 +4,7 @@ category: "Yazilim"
date: "2026-02-01" date: "2026-02-01"
readTime: "8 min read" readTime: "8 min read"
author: "Poyraz Avsever" author: "Poyraz Avsever"
excerpt: "Bu yazida temel kavramlari, pratik ornekleri ve uygulayabileceginiz ipuclarini sade bir dille bulabilirsiniz." excerpt: "Yazılım geliştirme sürecinde yalnızca kod yazmıyoruz; aynı zamanda takım iletişimini ve proje geçmişini...."
coverImage: "https://miro.medium.com/v2/resize:fit:1400/format:webp/1*WQOgajPE2m4aYuVC3SZoFQ.jpeg" coverImage: "https://miro.medium.com/v2/resize:fit:1400/format:webp/1*WQOgajPE2m4aYuVC3SZoFQ.jpeg"
--- ---
@@ -4,7 +4,7 @@ category: "Testing"
date: "2026-02-22" date: "2026-02-22"
readTime: "9 min read" readTime: "9 min read"
author: "Poyraz Avsever" author: "Poyraz Avsever"
excerpt: "Bu yazida temel kavramlari, pratik ornekleri ve uygulayabileceginiz ipuclarini sade bir dille bulabilirsiniz." excerpt: "Frontend tarafında kaliteyi korumanın en sağlam yolu testtir. Bu yazıda, farklı amaçlara hizmet eden..."
coverImage: "https://miro.medium.com/v2/resize:fit:1400/format:webp/0*uNpkjRqVlaB2WbDl.png" coverImage: "https://miro.medium.com/v2/resize:fit:1400/format:webp/0*uNpkjRqVlaB2WbDl.png"
--- ---
+1 -1
View File
@@ -4,7 +4,7 @@ category: "TypeScript"
date: "2026-02-18" date: "2026-02-18"
readTime: "8 min read" readTime: "8 min read"
author: "Poyraz Avsever" author: "Poyraz Avsever"
excerpt: "Bu yazida temel kavramlari, pratik ornekleri ve uygulayabileceginiz ipuclarini sade bir dille bulabilirsiniz." excerpt: "JavaScript'ten TypeScript'e geçiş, özellikle React projelerinde kod kalitesini gözle görülür şekilde artırıyor..."
coverImage: "https://miro.medium.com/v2/resize:fit:1400/format:webp/0*yRK5eL_pqabAhrn7" coverImage: "https://miro.medium.com/v2/resize:fit:1400/format:webp/0*yRK5eL_pqabAhrn7"
--- ---
@@ -4,7 +4,7 @@ category: "UX"
date: "2026-02-05" date: "2026-02-05"
readTime: "6 min read" readTime: "6 min read"
author: "Poyraz Avsever" author: "Poyraz Avsever"
excerpt: "Bu yazida temel kavramlari, pratik ornekleri ve uygulayabileceginiz ipuclarini sade bir dille bulabilirsiniz." excerpt: "Tasarım dünyasında, ürünlerimizin kullanıcılarımızın ihtiyaçlarını en iyi şekilde karşılamasını istemek..."
coverImage: "https://miro.medium.com/v2/resize:fit:1400/format:webp/0*BEVhh2Et54wytkaA.jpg" coverImage: "https://miro.medium.com/v2/resize:fit:1400/format:webp/0*BEVhh2Et54wytkaA.jpg"
--- ---
@@ -4,7 +4,7 @@ category: "Frontend"
date: "2026-02-14" date: "2026-02-14"
readTime: "11 min read" readTime: "11 min read"
author: "Poyraz Avsever" author: "Poyraz Avsever"
excerpt: "Bu yazida temel kavramlari, pratik ornekleri ve uygulayabileceginiz ipuclarini sade bir dille bulabilirsiniz." excerpt: "Frontend dünyasında React, Vue ve Angular gibi güçlü seçenekler var. Son yıllarda bu listeye güçlü bir alternatif..."
coverImage: "https://miro.medium.com/v2/resize:fit:1400/format:webp/0*Dx04pr9plejiCcWI.png" coverImage: "https://miro.medium.com/v2/resize:fit:1400/format:webp/0*Dx04pr9plejiCcWI.png"
--- ---
@@ -4,7 +4,7 @@ category: "Frontend"
date: "2026-02-10" date: "2026-02-10"
readTime: "10 min read" readTime: "10 min read"
author: "Poyraz Avsever" author: "Poyraz Avsever"
excerpt: "Bu yazida temel kavramlari, pratik ornekleri ve uygulayabileceginiz ipuclarini sade bir dille bulabilirsiniz." excerpt: "Tailwind CSS, son dönemde front-end geliştiriciler arasında çok popüler hale geldi. Klasik CSS..."
coverImage: "/news/performance.svg" coverImage: "/news/performance.svg"
--- ---
@@ -4,7 +4,7 @@ category: "UX"
date: "2026-02-26" date: "2026-02-26"
readTime: "12 min read" readTime: "12 min read"
author: "Poyraz Avsever" author: "Poyraz Avsever"
excerpt: "Bu yazida temel kavramlari, pratik ornekleri ve uygulayabileceginiz ipuclarini sade bir dille bulabilirsiniz." excerpt: "UX (Kullanıcı Deneyimi) tasarımı, teknolojiyle etkileşime geçen herkesin deneyimini iyileştirmeye..."
coverImage: "https://miro.medium.com/v2/resize:fit:1400/format:webp/0*5OUFz00g3w5ee2lV.jpg" coverImage: "https://miro.medium.com/v2/resize:fit:1400/format:webp/0*5OUFz00g3w5ee2lV.jpg"
--- ---
+26 -7
View File
@@ -44,6 +44,7 @@ export type BlogPageData = {
news: BlogNewsItem[]; news: BlogNewsItem[];
articles: BlogArticleItem[]; articles: BlogArticleItem[];
categories: string[]; categories: string[];
selectedCategory: string;
podcastGroups: BlogPodcastGroup[]; podcastGroups: BlogPodcastGroup[];
totalPages: number; totalPages: number;
currentPage: number; currentPage: number;
@@ -74,6 +75,10 @@ function sortByDateDesc<T extends { date: string; id: string }>(items: T[]) {
}); });
} }
function normalizeCategory(value: string) {
return value.trim().toLocaleLowerCase();
}
function mapEpisodeToPodcastItem(episode: PodcastEpisode): BlogPodcastItem { function mapEpisodeToPodcastItem(episode: PodcastEpisode): BlogPodcastItem {
return { return {
id: `${episode.podcast}-${episode.slug}`, id: `${episode.podcast}-${episode.slug}`,
@@ -115,13 +120,12 @@ export async function getHomeBlogNews(limit = 3): Promise<BlogNewsItem[]> {
})); }));
} }
export async function getBlogPageData(page = 1, pageSize = 12): Promise<BlogPageData> { export async function getBlogPageData(
page = 1,
pageSize = 12,
selectedCategoryParam?: string,
): Promise<BlogPageData> {
const articles = await getAllBlogArticles(); const articles = await getAllBlogArticles();
const totalPages = Math.max(1, Math.ceil(Math.max(articles.length, 1) / pageSize));
const currentPage = Math.min(Math.max(1, page), totalPages);
const start = (currentPage - 1) * pageSize;
const paginated = articles.slice(start, start + pageSize);
const categories = [ const categories = [
"All", "All",
...new Set( ...new Set(
@@ -130,11 +134,25 @@ export async function getBlogPageData(page = 1, pageSize = 12): Promise<BlogPage
.filter(Boolean), .filter(Boolean),
), ),
]; ];
const categoryByNormalized = new Map(
categories.map((category) => [normalizeCategory(category), category]),
);
const requestedCategory = selectedCategoryParam?.trim();
const selectedCategory =
(requestedCategory && categoryByNormalized.get(normalizeCategory(requestedCategory))) || "All";
const filteredArticles =
selectedCategory === "All"
? articles
: articles.filter((item) => normalizeCategory(item.category) === normalizeCategory(selectedCategory));
const totalPages = Math.max(1, Math.ceil(Math.max(filteredArticles.length, 1) / pageSize));
const currentPage = Math.min(Math.max(1, page), totalPages);
const start = (currentPage - 1) * pageSize;
const paginated = filteredArticles.slice(start, start + pageSize);
const podcastCollections = await getPodcastCollections(); const podcastCollections = await getPodcastCollections();
return { return {
news: articles.slice(0, 4).map((item) => ({ news: filteredArticles.slice(0, 4).map((item) => ({
id: `blog-news-${item.slug}`, id: `blog-news-${item.slug}`,
title: item.title, title: item.title,
category: item.category, category: item.category,
@@ -144,6 +162,7 @@ export async function getBlogPageData(page = 1, pageSize = 12): Promise<BlogPage
})), })),
articles: paginated, articles: paginated,
categories, categories,
selectedCategory,
totalPages, totalPages,
currentPage, currentPage,
podcastGroups: [ podcastGroups: [