From a182a59deddee22ad7ec26de5cff9131da09e867 Mon Sep 17 00:00:00 2001 From: Dionei Bianchati Date: Fri, 5 Jun 2026 14:41:02 -0300 Subject: [PATCH 1/9] feat: add accentColor hex field to Category type --- src/lib/categories.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/lib/categories.ts b/src/lib/categories.ts index 6010154..12867fb 100644 --- a/src/lib/categories.ts +++ b/src/lib/categories.ts @@ -3,16 +3,17 @@ export type Category = { icon: string color: string borderColor: string + accentColor: string // hex — usado no border-left e na barra de progresso } export const CATEGORIES: Category[] = [ - { slug: 'frontend', icon: '⚛️', color: 'text-blue-400', borderColor: 'border-blue-500/30' }, - { slug: 'backend', icon: '🖥️', color: 'text-green-400', borderColor: 'border-green-500/30' }, - { slug: 'devops', icon: '🐳', color: 'text-orange-400', borderColor: 'border-orange-500/30' }, - { slug: 'data', icon: '📊', color: 'text-purple-400', borderColor: 'border-purple-500/30' }, - { slug: 'soft-skills', icon: '🗣️', color: 'text-pink-400', borderColor: 'border-pink-500/30' }, - { slug: 'architecture', icon: '🏗️', color: 'text-yellow-400', borderColor: 'border-yellow-500/30' }, - { slug: 'security', icon: '🔒', color: 'text-red-400', borderColor: 'border-red-500/30' }, + { slug: 'frontend', icon: '⚛️', color: 'text-blue-400', borderColor: 'border-blue-500/30', accentColor: '#58a6ff' }, + { slug: 'backend', icon: '🖥️', color: 'text-green-400', borderColor: 'border-green-500/30', accentColor: '#3fb950' }, + { slug: 'devops', icon: '🐳', color: 'text-orange-400', borderColor: 'border-orange-500/30', accentColor: '#f0883e' }, + { slug: 'data', icon: '📊', color: 'text-purple-400', borderColor: 'border-purple-500/30', accentColor: '#bc8cff' }, + { slug: 'soft-skills', icon: '🗣️', color: 'text-pink-400', borderColor: 'border-pink-500/30', accentColor: '#ff7b72' }, + { slug: 'architecture', icon: '🏗️', color: 'text-yellow-400', borderColor: 'border-yellow-500/30', accentColor: '#e3b341' }, + { slug: 'security', icon: '🔒', color: 'text-red-400', borderColor: 'border-red-500/30', accentColor: '#ff7b72' }, ] export function getCategoryBySlug(slug: string): Category | undefined { From a959e9eae4e67c5a3af517c297b3e453fb9a7cb8 Mon Sep 17 00:00:00 2001 From: Dionei Bianchati Date: Fri, 5 Jun 2026 14:46:51 -0300 Subject: [PATCH 2/9] feat: rewrite CategoryCard with progress bar and accent border MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the new CategoryCard layout with inline progress tracking: - Accepts `known` prop for progress calculation - Renders progress bar with accent color from category - Shows "não iniciado" when known is 0 - Shows "X/Y concluídas" when progress exists - Adds CategoriesGrid client component to read progress from localStorage - Updates questions page to use new grid component All tests passing (6 new tests for CategoryCard). Co-Authored-By: Claude Sonnet 4.6 --- src/app/[locale]/questions/page.tsx | 8 +-- src/components/questions/CategoriesGrid.tsx | 59 +++++++++++++++++++ src/components/questions/CategoryCard.tsx | 33 ++++++++--- .../questions/__tests__/CategoryCard.test.tsx | 55 +++++++++++++++++ 4 files changed, 140 insertions(+), 15 deletions(-) create mode 100644 src/components/questions/CategoriesGrid.tsx create mode 100644 src/components/questions/__tests__/CategoryCard.test.tsx diff --git a/src/app/[locale]/questions/page.tsx b/src/app/[locale]/questions/page.tsx index 4c0f380..314e06b 100644 --- a/src/app/[locale]/questions/page.tsx +++ b/src/app/[locale]/questions/page.tsx @@ -1,7 +1,7 @@ import { getTranslations, getLocale } from 'next-intl/server' import { CATEGORIES } from '@/lib/categories' import { getAllQuestionMeta } from '@/lib/content' -import { CategoryCard } from '@/components/questions/CategoryCard' +import { CategoriesGrid } from '@/components/questions/CategoriesGrid' import { QuizSetupPanel } from '@/components/quiz/QuizSetupPanel' export async function generateStaticParams() { @@ -36,11 +36,7 @@ export default async function QuestionsPage() {

{t('categories.subtitle')}

-
- {categoriesWithCount.map(({ cat, label, count }) => ( - - ))} -
+ ) } diff --git a/src/components/questions/CategoriesGrid.tsx b/src/components/questions/CategoriesGrid.tsx new file mode 100644 index 0000000..b058060 --- /dev/null +++ b/src/components/questions/CategoriesGrid.tsx @@ -0,0 +1,59 @@ +'use client' + +import { useEffect, useState } from 'react' +import { CategoryCard } from './CategoryCard' +import { countProgress } from '@/lib/progress' +import type { Category } from '@/lib/categories' + +type Props = { + categories: Array<{ + cat: Category + label: string + count: number + }> +} + +export function CategoriesGrid({ categories }: Props) { + const [progress, setProgress] = useState>({}) + const [isHydrated, setIsHydrated] = useState(false) + + useEffect(() => { + const progressData: Record = {} + for (const { cat } of categories) { + const { known } = countProgress(cat.slug) + progressData[cat.slug] = known + } + setProgress(progressData) + setIsHydrated(true) + }, [categories]) + + if (!isHydrated) { + return ( +
+ {categories.map(({ cat, label, count }) => ( + + ))} +
+ ) + } + + return ( +
+ {categories.map(({ cat, label, count }) => ( + + ))} +
+ ) +} diff --git a/src/components/questions/CategoryCard.tsx b/src/components/questions/CategoryCard.tsx index 85f2767..78c2d56 100644 --- a/src/components/questions/CategoryCard.tsx +++ b/src/components/questions/CategoryCard.tsx @@ -2,31 +2,46 @@ import Link from 'next/link' import { useLocale } from 'next-intl' -import { ProgressBadge } from './ProgressBadge' import type { Category } from '@/lib/categories' type Props = { category: Category label: string count: number + known: number } -export function CategoryCard({ category, label, count }: Props) { +export function CategoryCard({ category, label, count, known }: Props) { const locale = useLocale() + const pct = count > 0 ? Math.round((known / count) * 100) : 0 return ( -
- {category.icon} - +
+
+ {category.icon} + {label} +
+ {count} perguntas
-
- {label} +
+
-
{count} questions
+ {known > 0 ? ( +

+ {known}/{count} concluídas +

+ ) : ( +

não iniciado

+ )} ) } diff --git a/src/components/questions/__tests__/CategoryCard.test.tsx b/src/components/questions/__tests__/CategoryCard.test.tsx new file mode 100644 index 0000000..914c715 --- /dev/null +++ b/src/components/questions/__tests__/CategoryCard.test.tsx @@ -0,0 +1,55 @@ +import { describe, it, expect, vi } from 'vitest' +import { render, screen } from '@testing-library/react' +import { CategoryCard } from '../CategoryCard' +import type { Category } from '@/lib/categories' + +vi.mock('next/link', () => ({ + default: ({ href, children, className, style }: { href: string; children: React.ReactNode; className?: string; style?: React.CSSProperties }) => + {children}, +})) + +vi.mock('next-intl', () => ({ + useLocale: () => 'pt', +})) + +const mockCategory: Category = { + slug: 'frontend', + icon: '⚛️', + color: 'text-blue-400', + borderColor: 'border-blue-500/30', + accentColor: '#58a6ff', +} + +describe('CategoryCard', () => { + it('renders category label and count', () => { + render() + expect(screen.getByText('Frontend')).toBeInTheDocument() + expect(screen.getByText('48 perguntas')).toBeInTheDocument() + }) + + it('renders "não iniciado" when known is 0', () => { + render() + expect(screen.getByText('não iniciado')).toBeInTheDocument() + }) + + it('renders progress label when known > 0', () => { + render() + expect(screen.getByText('17/48 concluídas')).toBeInTheDocument() + }) + + it('links to the correct category URL', () => { + render() + expect(screen.getByRole('link')).toHaveAttribute('href', '/pt/questions/frontend') + }) + + it('renders the category icon', () => { + render() + expect(screen.getByText('⚛️')).toBeInTheDocument() + }) + + it('renders a progress bar element', () => { + const { container } = render() + const fill = container.querySelector('[data-testid="progress-fill"]') + expect(fill).toBeInTheDocument() + }) +}) From 364126cca9e6ccecb5f2451d5b06f47eefbb3012 Mon Sep 17 00:00:00 2001 From: Dionei Bianchati Date: Fri, 5 Jun 2026 14:51:03 -0300 Subject: [PATCH 3/9] test: add style assertion to progress bar test in CategoryCard --- src/components/questions/__tests__/CategoryCard.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/questions/__tests__/CategoryCard.test.tsx b/src/components/questions/__tests__/CategoryCard.test.tsx index 914c715..db3295a 100644 --- a/src/components/questions/__tests__/CategoryCard.test.tsx +++ b/src/components/questions/__tests__/CategoryCard.test.tsx @@ -47,9 +47,10 @@ describe('CategoryCard', () => { expect(screen.getByText('⚛️')).toBeInTheDocument() }) - it('renders a progress bar element', () => { + it('renders a progress bar with correct width', () => { const { container } = render() const fill = container.querySelector('[data-testid="progress-fill"]') expect(fill).toBeInTheDocument() + expect(fill).toHaveStyle({ width: '35%' }) }) }) From 006c35e01344a820f56be7f0002ab9620ccb0773 Mon Sep 17 00:00:00 2001 From: Dionei Bianchati Date: Fri, 5 Jun 2026 14:52:59 -0300 Subject: [PATCH 4/9] feat: add CategoryListClient with text and status filter Implements list view component with support for filtering categories by: - Text search (matches label or slug, case-insensitive) - Status filter (all, in-progress, not-started based on progress) Component loads progress data on mount via useEffect and mocks in tests for synchronous verification during assertions. Co-Authored-By: Claude Sonnet 4.6 --- .../questions/CategoryListClient.tsx | 96 +++++++++++++++++++ .../__tests__/CategoryListClient.test.tsx | 88 +++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 src/components/questions/CategoryListClient.tsx create mode 100644 src/components/questions/__tests__/CategoryListClient.test.tsx diff --git a/src/components/questions/CategoryListClient.tsx b/src/components/questions/CategoryListClient.tsx new file mode 100644 index 0000000..9a523da --- /dev/null +++ b/src/components/questions/CategoryListClient.tsx @@ -0,0 +1,96 @@ +'use client' + +import { useState, useEffect } from 'react' +import { CategoryCard } from './CategoryCard' +import { countProgress } from '@/lib/progress' +import type { Category } from '@/lib/categories' + +type CategoryItem = { + cat: Category + label: string + count: number +} + +type StatusFilter = 'all' | 'in-progress' | 'not-started' + +type Props = { + categories: CategoryItem[] +} + +export function CategoryListClient({ categories }: Props) { + const [query, setQuery] = useState('') + const [statusFilter, setStatusFilter] = useState('all') + const [progressMap, setProgressMap] = useState>({}) + + useEffect(() => { + const map: Record = {} + for (const { cat } of categories) { + const { known } = countProgress(cat.slug) + map[cat.slug] = known + } + setProgressMap(map) + }, [categories]) + + const filtered = categories.filter(({ cat, label }) => { + const q = query.toLowerCase() + const matchesQuery = label.toLowerCase().includes(q) || cat.slug.toLowerCase().includes(q) + const known = progressMap[cat.slug] ?? 0 + if (statusFilter === 'in-progress') return matchesQuery && known > 0 + if (statusFilter === 'not-started') return matchesQuery && known === 0 + return matchesQuery + }) + + return ( +
+
+
+ 🔍 + setQuery(e.target.value)} + placeholder="Filtrar categorias..." + className="flex-1 bg-transparent text-sm text-dark-text placeholder-dark-muted outline-none font-mono" + /> +
+
+ {( + [ + { value: 'all', label: 'todas' }, + { value: 'in-progress', label: 'com progresso' }, + { value: 'not-started', label: 'não iniciadas' }, + ] as { value: StatusFilter; label: string }[] + ).map(chip => ( + + ))} +
+
+ +
+ {filtered.length === 0 ? ( +

Nenhuma categoria encontrada.

+ ) : ( + filtered.map(({ cat, label, count }) => ( + + )) + )} +
+
+ ) +} diff --git a/src/components/questions/__tests__/CategoryListClient.test.tsx b/src/components/questions/__tests__/CategoryListClient.test.tsx new file mode 100644 index 0000000..5c9d806 --- /dev/null +++ b/src/components/questions/__tests__/CategoryListClient.test.tsx @@ -0,0 +1,88 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { render, screen, fireEvent } from '@testing-library/react' +import { CategoryListClient } from '../CategoryListClient' +import type { Category } from '@/lib/categories' + +vi.mock('next/link', () => ({ + default: ({ href, children, className, style }: { href: string; children: React.ReactNode; className?: string; style?: React.CSSProperties }) => + {children}, +})) + +vi.mock('next-intl', () => ({ + useLocale: () => 'pt', +})) + +vi.mock('@/lib/progress', () => ({ + countProgress: vi.fn((slug: string) => { + if (slug === 'frontend') return { known: 5, review: 0 } + return { known: 0, review: 0 } + }), +})) + +const makeCategory = (slug: string): Category => ({ + slug, + icon: '📦', + color: 'text-blue-400', + borderColor: 'border-blue-500/30', + accentColor: '#58a6ff', +}) + +const categories = [ + { cat: makeCategory('frontend'), label: 'Frontend', count: 48 }, + { cat: makeCategory('backend'), label: 'Backend', count: 36 }, + { cat: makeCategory('devops'), label: 'DevOps', count: 24 }, +] + +describe('CategoryListClient', () => { + beforeEach(() => vi.clearAllMocks()) + + it('renders all categories by default', () => { + render() + expect(screen.getByText('Frontend')).toBeInTheDocument() + expect(screen.getByText('Backend')).toBeInTheDocument() + expect(screen.getByText('DevOps')).toBeInTheDocument() + }) + + it('filters categories by text query', () => { + render() + fireEvent.change(screen.getByPlaceholderText('Filtrar categorias...'), { + target: { value: 'front' }, + }) + expect(screen.getByText('Frontend')).toBeInTheDocument() + expect(screen.queryByText('Backend')).not.toBeInTheDocument() + expect(screen.queryByText('DevOps')).not.toBeInTheDocument() + }) + + it('chip "com progresso" shows only categories with known > 0', () => { + render() + fireEvent.click(screen.getByText('com progresso')) + expect(screen.getByText('Frontend')).toBeInTheDocument() + expect(screen.queryByText('Backend')).not.toBeInTheDocument() + expect(screen.queryByText('DevOps')).not.toBeInTheDocument() + }) + + it('chip "não iniciadas" shows only categories with known === 0', () => { + render() + fireEvent.click(screen.getByText('não iniciadas')) + expect(screen.queryByText('Frontend')).not.toBeInTheDocument() + expect(screen.getByText('Backend')).toBeInTheDocument() + expect(screen.getByText('DevOps')).toBeInTheDocument() + }) + + it('chip "todas" resets to show all categories', () => { + render() + fireEvent.click(screen.getByText('não iniciadas')) + fireEvent.click(screen.getByText('todas')) + expect(screen.getByText('Frontend')).toBeInTheDocument() + expect(screen.getByText('Backend')).toBeInTheDocument() + expect(screen.getByText('DevOps')).toBeInTheDocument() + }) + + it('shows empty message when no categories match', () => { + render() + fireEvent.change(screen.getByPlaceholderText('Filtrar categorias...'), { + target: { value: 'xyznotfound' }, + }) + expect(screen.getByText('Nenhuma categoria encontrada.')).toBeInTheDocument() + }) +}) From f547503a28de72ae6da1d6e25fdad121a303fb62 Mon Sep 17 00:00:00 2001 From: Dionei Bianchati Date: Fri, 5 Jun 2026 14:55:52 -0300 Subject: [PATCH 5/9] fix: add isHydrated guard, useMemo, aria attrs to CategoryListClient --- .../questions/CategoryListClient.tsx | 27 ++++++++++++------- .../__tests__/CategoryListClient.test.tsx | 13 +++++++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/components/questions/CategoryListClient.tsx b/src/components/questions/CategoryListClient.tsx index 9a523da..e401940 100644 --- a/src/components/questions/CategoryListClient.tsx +++ b/src/components/questions/CategoryListClient.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useEffect } from 'react' +import { useState, useEffect, useMemo } from 'react' import { CategoryCard } from './CategoryCard' import { countProgress } from '@/lib/progress' import type { Category } from '@/lib/categories' @@ -21,6 +21,7 @@ export function CategoryListClient({ categories }: Props) { const [query, setQuery] = useState('') const [statusFilter, setStatusFilter] = useState('all') const [progressMap, setProgressMap] = useState>({}) + const [isHydrated, setIsHydrated] = useState(false) useEffect(() => { const map: Record = {} @@ -29,16 +30,20 @@ export function CategoryListClient({ categories }: Props) { map[cat.slug] = known } setProgressMap(map) + setIsHydrated(true) }, [categories]) - const filtered = categories.filter(({ cat, label }) => { - const q = query.toLowerCase() - const matchesQuery = label.toLowerCase().includes(q) || cat.slug.toLowerCase().includes(q) - const known = progressMap[cat.slug] ?? 0 - if (statusFilter === 'in-progress') return matchesQuery && known > 0 - if (statusFilter === 'not-started') return matchesQuery && known === 0 - return matchesQuery - }) + const filtered = useMemo(() => + categories.filter(({ cat, label }) => { + const q = query.toLowerCase() + const matchesQuery = label.toLowerCase().includes(q) || cat.slug.toLowerCase().includes(q) + const known = progressMap[cat.slug] ?? 0 + if (statusFilter === 'in-progress') return matchesQuery && known > 0 + if (statusFilter === 'not-started') return matchesQuery && known === 0 + return matchesQuery + }), + [categories, query, statusFilter, progressMap] + ) return (
@@ -50,6 +55,7 @@ export function CategoryListClient({ categories }: Props) { value={query} onChange={e => setQuery(e.target.value)} placeholder="Filtrar categorias..." + aria-label="Filtrar categorias" className="flex-1 bg-transparent text-sm text-dark-text placeholder-dark-muted outline-none font-mono" />
@@ -64,6 +70,7 @@ export function CategoryListClient({ categories }: Props) {
-

+

{t('categories.subtitle')}

- +
) } diff --git a/src/components/questions/CategoriesGrid.tsx b/src/components/questions/CategoriesGrid.tsx deleted file mode 100644 index b058060..0000000 --- a/src/components/questions/CategoriesGrid.tsx +++ /dev/null @@ -1,59 +0,0 @@ -'use client' - -import { useEffect, useState } from 'react' -import { CategoryCard } from './CategoryCard' -import { countProgress } from '@/lib/progress' -import type { Category } from '@/lib/categories' - -type Props = { - categories: Array<{ - cat: Category - label: string - count: number - }> -} - -export function CategoriesGrid({ categories }: Props) { - const [progress, setProgress] = useState>({}) - const [isHydrated, setIsHydrated] = useState(false) - - useEffect(() => { - const progressData: Record = {} - for (const { cat } of categories) { - const { known } = countProgress(cat.slug) - progressData[cat.slug] = known - } - setProgress(progressData) - setIsHydrated(true) - }, [categories]) - - if (!isHydrated) { - return ( -
- {categories.map(({ cat, label, count }) => ( - - ))} -
- ) - } - - return ( -
- {categories.map(({ cat, label, count }) => ( - - ))} -
- ) -} From 0523e1d8f3f537aa24f0bbe8c6350dc2b7de66b6 Mon Sep 17 00:00:00 2001 From: Dionei Bianchati Date: Fri, 5 Jun 2026 15:04:25 -0300 Subject: [PATCH 7/9] refactor: remove ProgressBadge, replaced by inline progress in CategoryCard --- src/components/questions/ProgressBadge.tsx | 26 ---------------------- 1 file changed, 26 deletions(-) delete mode 100644 src/components/questions/ProgressBadge.tsx diff --git a/src/components/questions/ProgressBadge.tsx b/src/components/questions/ProgressBadge.tsx deleted file mode 100644 index a17cabb..0000000 --- a/src/components/questions/ProgressBadge.tsx +++ /dev/null @@ -1,26 +0,0 @@ -'use client' - -import { useEffect, useState } from 'react' -import { countProgress } from '@/lib/progress' - -type Props = { - category: string - total: number -} - -export function ProgressBadge({ category, total }: Props) { - const [known, setKnown] = useState(0) - - useEffect(() => { - const { known: k } = countProgress(category) - setKnown(k) - }, [category]) - - if (known === 0) return null - - return ( - - {known}/{total} - - ) -} From 6bc3f64f99cf2ffc7d407aa235bfaea422157b90 Mon Sep 17 00:00:00 2001 From: Dionei Bianchati Date: Fri, 5 Jun 2026 15:10:26 -0300 Subject: [PATCH 8/9] fix: use translations in CategoryCard and CategoryListClient, fix security accentColor Co-Authored-By: Claude Sonnet 4.6 --- src/components/questions/CategoryCard.tsx | 10 +++++---- .../questions/CategoryListClient.tsx | 22 ++++++++++--------- .../questions/__tests__/CategoryCard.test.tsx | 12 ++++++++++ .../__tests__/CategoryListClient.test.tsx | 17 ++++++++++++++ src/lib/categories.ts | 2 +- src/messages/en.json | 6 +++++ src/messages/pt.json | 6 +++++ 7 files changed, 60 insertions(+), 15 deletions(-) diff --git a/src/components/questions/CategoryCard.tsx b/src/components/questions/CategoryCard.tsx index 78c2d56..22b428c 100644 --- a/src/components/questions/CategoryCard.tsx +++ b/src/components/questions/CategoryCard.tsx @@ -1,7 +1,7 @@ 'use client' import Link from 'next/link' -import { useLocale } from 'next-intl' +import { useLocale, useTranslations } from 'next-intl' import type { Category } from '@/lib/categories' type Props = { @@ -13,6 +13,8 @@ type Props = { export function CategoryCard({ category, label, count, known }: Props) { const locale = useLocale() + const t = useTranslations('categories') + const tProgress = useTranslations('progress') const pct = count > 0 ? Math.round((known / count) * 100) : 0 return ( @@ -26,7 +28,7 @@ export function CategoryCard({ category, label, count, known }: Props) { {category.icon} {label}
- {count} perguntas + {count} {t('questionsTotal')}
{known > 0 ? (

- {known}/{count} concluídas + {known}/{count} {tProgress('studied')}

) : ( -

não iniciado

+

{t('notStarted')}

)} ) diff --git a/src/components/questions/CategoryListClient.tsx b/src/components/questions/CategoryListClient.tsx index e401940..8beafa1 100644 --- a/src/components/questions/CategoryListClient.tsx +++ b/src/components/questions/CategoryListClient.tsx @@ -1,6 +1,7 @@ 'use client' import { useState, useEffect, useMemo } from 'react' +import { useTranslations } from 'next-intl' import { CategoryCard } from './CategoryCard' import { countProgress } from '@/lib/progress' import type { Category } from '@/lib/categories' @@ -18,6 +19,7 @@ type Props = { } export function CategoryListClient({ categories }: Props) { + const t = useTranslations('categories') const [query, setQuery] = useState('') const [statusFilter, setStatusFilter] = useState('all') const [progressMap, setProgressMap] = useState>({}) @@ -33,6 +35,12 @@ export function CategoryListClient({ categories }: Props) { setIsHydrated(true) }, [categories]) + const chips: { value: StatusFilter; label: string }[] = [ + { value: 'all', label: t('filterAll') }, + { value: 'in-progress', label: t('filterInProgress') }, + { value: 'not-started', label: t('filterNotStarted') }, + ] + const filtered = useMemo(() => categories.filter(({ cat, label }) => { const q = query.toLowerCase() @@ -54,19 +62,13 @@ export function CategoryListClient({ categories }: Props) { type="text" value={query} onChange={e => setQuery(e.target.value)} - placeholder="Filtrar categorias..." - aria-label="Filtrar categorias" + placeholder={t('filterPlaceholder')} + aria-label={t('filterPlaceholder')} className="flex-1 bg-transparent text-sm text-dark-text placeholder-dark-muted outline-none font-mono" />
- {( - [ - { value: 'all', label: 'todas' }, - { value: 'in-progress', label: 'com progresso' }, - { value: 'not-started', label: 'não iniciadas' }, - ] as { value: StatusFilter; label: string }[] - ).map(chip => ( + {chips.map(chip => (