Skip to content

Commit d69b329

Browse files
committed
add tabs translation, refactoring tab filter
1 parent 380db5d commit d69b329

7 files changed

Lines changed: 54 additions & 42 deletions

File tree

public/locales/de/translation.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
"delete": "Löschen",
77
"edit": "Bearbeiten",
88
"done": "Fertig",
9+
"allTab": "Alle",
10+
"activeTab": "Offen",
11+
"completedTab": "Abgeschlossen",
912
"todoNameLabel": "Todo Name",
1013
"todoNamePlaceholder": "Gib einen Todo Namen ein",
1114
"todoDescriptionLabel": "Beschreibung",

public/locales/en/translation.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
"delete": "Delete",
77
"edit": "Edit",
88
"done": "Done",
9+
"allTab": "All",
10+
"activeTab": "Active",
11+
"completedTab": "Completed",
912
"todoNameLabel": "Todo name",
1013
"todoNamePlaceholder": "Enter todo name",
1114
"todoDescriptionLabel": "Description",

src/lib/todo-filter.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { Todo } from '@/store/todo-store';
2+
3+
type TabValue = 'all' | 'active' | 'completed';
4+
5+
const tabFilter: Record<TabValue, (todo: Todo) => boolean> = {
6+
all: () => true,
7+
active: (todo) => !todo.completed,
8+
completed: (todo) => todo.completed,
9+
};
10+
11+
function getTodosByTab(todos: Todo[], tab: TabValue): Todo[] {
12+
return todos.filter(tabFilter[tab]);
13+
}
14+
15+
export { getTodosByTab, type TabValue };

src/routes/index.tsx

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
2+
import { type TabValue, getTodosByTab } from '@/lib/todo-filter';
23
import { useTodoStore } from '@/store/todo-store';
34
import { ButtonLink, Header, SearchBar, TodoList } from '@/ui';
45
import { createFileRoute } from '@tanstack/react-router';
@@ -10,12 +11,11 @@ export const Route = createFileRoute('/')({
1011
});
1112

1213
function App() {
14+
const { t } = useTranslation();
1315
const navigate = Route.useNavigate();
1416
const [searchQuery, setSearchQuery] = useState('');
17+
const [tab, setTab] = useState<TabValue>('all');
1518
const todos = useTodoStore((state) => state.todos);
16-
const deleteTodos = useTodoStore((state) => state.delete);
17-
18-
const { t } = useTranslation();
1919

2020
const onSearchChange = (e: ChangeEvent<HTMLInputElement>) => {
2121
setSearchQuery(e.target.value);
@@ -27,12 +27,13 @@ function App() {
2727

2828
const lowerSearchQuery = searchQuery.toLowerCase();
2929
const filteredTodos = useMemo(() => {
30-
return todos.filter(
30+
const matchingTodos = todos.filter(
3131
(todo) =>
3232
todo.title.toLowerCase().includes(lowerSearchQuery) ||
3333
todo.description?.toLowerCase().includes(lowerSearchQuery),
3434
);
35-
}, [todos, lowerSearchQuery]);
35+
return getTodosByTab(matchingTodos, tab);
36+
}, [todos, lowerSearchQuery, tab]);
3637

3738
return (
3839
<div className="min-h-screen bg-gray-50 pt-16 space-y-8">
@@ -64,40 +65,18 @@ function App() {
6465
</div>
6566
<Tabs defaultValue="all" className="mt-4">
6667
<TabsList className="grid grid-cols-3 w-full max-w-md">
67-
<TabsTrigger value="all">All</TabsTrigger>
68-
<TabsTrigger
69-
value="active"
70-
onClick={() => filteredTodos.filter((todo) => !todo.completed)}
71-
>
72-
Active
68+
<TabsTrigger value="all" onClick={() => setTab('all')}>
69+
{t('allTab')}
7370
</TabsTrigger>
74-
<TabsTrigger
75-
value="completed"
76-
onClick={() => filteredTodos.filter((todo) => todo.completed)}
77-
>
78-
Completed
71+
<TabsTrigger value="active" onClick={() => setTab('active')}>
72+
{t('activeTab')}
73+
</TabsTrigger>
74+
<TabsTrigger value="completed" onClick={() => setTab('completed')}>
75+
{t('completedTab')}
7976
</TabsTrigger>
8077
</TabsList>
81-
<TabsContent value="all">
82-
<TodoList
83-
todos={filteredTodos}
84-
onDelete={deleteTodos}
85-
onEdit={onEdit}
86-
/>
87-
</TabsContent>
88-
<TabsContent value="active">
89-
<TodoList
90-
todos={filteredTodos.filter((todo) => !todo.completed)}
91-
onDelete={deleteTodos}
92-
onEdit={onEdit}
93-
/>
94-
</TabsContent>
95-
<TabsContent value="completed">
96-
<TodoList
97-
todos={filteredTodos.filter((todo) => todo.completed)}
98-
onDelete={deleteTodos}
99-
onEdit={onEdit}
100-
/>
78+
<TabsContent value={tab}>
79+
<TodoList todos={filteredTodos} onEdit={onEdit} />
10180
</TabsContent>
10281
</Tabs>
10382
</div>

src/store/todo-store.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type TodoAction = {
1717
add: (todo: Omit<Todo, 'id'>) => void;
1818
delete: (id: string) => void;
1919
update: (id: string, updatedTodo: Omit<Todo, 'id'>) => void;
20+
changeStatus: (id: string, isCompleted: boolean) => void;
2021
};
2122

2223
const useTodoStore = create<TodoState & TodoAction>((set) => ({
@@ -41,6 +42,12 @@ const useTodoStore = create<TodoState & TodoAction>((set) => ({
4142
todo.id === id ? { ...updatedTodo, id } : todo,
4243
),
4344
})),
45+
changeStatus: (id: string, isCompleted: boolean) =>
46+
set((state: TodoState) => ({
47+
todos: state.todos.map((todo) =>
48+
todo.id === id ? { ...todo, id, completed: isCompleted } : todo,
49+
),
50+
})),
4451
}));
4552

4653
const todoStore = useTodoStore;

src/ui/todos/todo-card.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ import { CheckIcon, ClockIcon, PriorityIcon } from '../icons';
1111

1212
interface TodoCardProp {
1313
todo: Todo;
14-
onDelete: (id: string) => void;
1514
onEdit: (id: string) => void;
15+
onDelete: (id: string) => void;
16+
onChangeState: (id: string, state: boolean) => void;
1617
}
1718

18-
function TodoCard({ todo, onDelete, onEdit }: TodoCardProp) {
19+
function TodoCard({ todo, onDelete, onEdit, onChangeState }: TodoCardProp) {
1920
const { t } = useTranslation();
21+
2022
const colorClasses = {
2123
high: 'bg-red-100 text-red-800',
2224
medium: 'bg-yellow-100 text-yellow-800',
@@ -40,7 +42,7 @@ function TodoCard({ todo, onDelete, onEdit }: TodoCardProp) {
4042
</svg>
4143
</DropdownMenuTrigger>
4244
<DropdownMenuContent>
43-
<DropdownMenuItem>
45+
<DropdownMenuItem onClick={() => onChangeState(todo.id, true)}>
4446
<CheckIcon className="h-4 w-4 mr-2" />
4547
<span>{t('done')}</span>
4648
</DropdownMenuItem>

src/ui/todos/todo-list.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import type { Todo } from '@/store/todo-store';
1+
import { useTodoStore, type Todo } from '@/store/todo-store';
22
import { EmptyTodoList, TodoCard } from '@/ui/todos';
33

44
interface TodoListProps {
55
todos: Todo[];
6-
onDelete: (id: string) => void;
76
onEdit: (id: string) => void;
87
}
98

10-
function TodoList({ todos, onDelete, onEdit }: TodoListProps) {
9+
function TodoList({ todos, onEdit }: TodoListProps) {
10+
const onDelete = useTodoStore((state) => state.delete);
11+
const onChangeState = useTodoStore((state) => state.changeStatus);
12+
1113
if (todos.length === 0) {
1214
return <EmptyTodoList />;
1315
}
@@ -23,6 +25,7 @@ function TodoList({ todos, onDelete, onEdit }: TodoListProps) {
2325
todo={todo}
2426
onDelete={onDelete}
2527
onEdit={onEdit}
28+
onChangeState={onChangeState}
2629
/>
2730
))}
2831
</div>

0 commit comments

Comments
 (0)