Skip to content

01. Kanban Board

This content is not available in your language yet.

Zbudujesz aplikację do zarządzania zadaniami w stylu Trello — z kolumnami reprezentującymi etapy pracy (np. “Do zrobienia”, “W trakcie”, “Gotowe”) i kartami zadań, które można przenosić między kolumnami.

Czego się nauczycie?

  • Zarządzanie złożonym stanem (tablice w tablicach) z useReducer
  • Implementacja drag & drop bez zewnętrznych bibliotek (wariant A/B) lub z nimi (wariant C)
  • Persystencja danych w localStorage
  • Podział aplikacji na wiele komponentów z czystym przepływem propsów
  • Praca zespołowa — podział na moduły (jeden robi kolumny, drugi karty)

W prawdziwej firmie...

Kanbany są wszędzie — Trello, Jira, GitHub Projects, Notion. Każdy developer pracuje z tego typu narzędziami na co dzień. Zbudowanie własnego to najlepszy sposób na zrozumienie jak działają.

Umiejętności rynkowe

  • Złożone zarządzanie stanem
  • Drag & drop — pytany na rozmowach kwalifikacyjnych
  • Komponenty generyczne i reusable
  • Persystencja danych lokalnych
  1. Wyświetlanie tablicy z kolumnami (minimum 3: “Do zrobienia”, “W trakcie”, “Gotowe”)

  2. Dodawanie kart do dowolnej kolumny — tytuł, opis, priorytet

  3. Przenoszenie kart między kolumnami (kliknięciem lub drag & drop)

  4. Usuwanie kart i ewentualnie kolumn

  5. Zapis stanu do localStorage — po odświeżeniu strony dane nie giną

Widok tablicy

Główny widok — wszystkie kolumny i karty. Tutaj się dzieje cała akcja: dodawanie, przenoszenie, usuwanie.

Widok szczegółów karty

Modal lub osobna strona z pełnym opisem karty — tytuł, opis, priorytet, data dodania, historia zmian (wariant C).

{
"columns": [
{
"id": "col-1",
"title": "Do zrobienia",
"cards": [
{
"id": "card-1",
"title": "Zaprojektuj UI aplikacji",
"description": "Stwórz mockup w Figma lub na kartce",
"priority": "high",
"createdAt": "2025-03-01T10:00:00Z"
}
]
},
{
"id": "col-2",
"title": "W trakcie",
"cards": []
},
{
"id": "col-3",
"title": "Gotowe",
"cards": []
}
]
}

Minimalne wymagania:

  • Trzy statyczne kolumny (nie można dodawać kolumn)
  • Dodawanie kart z tytułem do wybranej kolumny
  • Usuwanie kart
  • Przenoszenie kart przez przyciski (“Przenieś do →”)
  • Zapis w localStorage
  • Minimum 3 komponenty: Board, Column, Card
  • Directorysrc/
    • Directorycomponents/
      • Board.jsx
      • Column.jsx
      • Card.jsx
      • AddCardForm.jsx
    • App.jsx
Ocena: 3.0

Scenariusz 1: Zarządzanie projektem szkolnym

  1. Użytkownik otwiera tablicę — widzi kolumny “Do zrobienia”, “W trakcie”, “Gotowe”
  2. Dodaje kartę “Napisać wstęp do prezentacji” z priorytetem High
  3. Po rozpoczęciu pracy przenosi kartę do “W trakcie”
  4. Po ukończeniu — do “Gotowe”
  5. Odświeża stronę — dane nadal są (localStorage)

Scenariusz 2: Dodanie szczegółów

  1. Użytkownik klika na kartę
  2. Otwiera się modal z tytułem, opisem, priorytetem
  3. Może edytować opis
  4. Zamknięcie modalu przywraca główny widok
src/hooks/useKanban.js
import { useReducer, useEffect } from 'react';
const initialState = {
columns: [
{ id: 'todo', title: 'Do zrobienia', cards: [] },
{ id: 'in-progress', title: 'W trakcie', cards: [] },
{ id: 'done', title: 'Gotowe', cards: [] },
],
};
function kanbanReducer(state, action) {
switch (action.type) {
case 'ADD_CARD': {
return {
...state,
columns: state.columns.map((col) =>
col.id === action.columnId
? { ...col, cards: [...col.cards, action.card] }
: col
),
};
}
case 'MOVE_CARD': {
const { cardId, fromColumnId, toColumnId } = action;
const fromColumn = state.columns.find((c) => c.id === fromColumnId);
const card = fromColumn.cards.find((c) => c.id === cardId);
return {
...state,
columns: state.columns.map((col) => {
if (col.id === fromColumnId) {
return { ...col, cards: col.cards.filter((c) => c.id !== cardId) };
}
if (col.id === toColumnId) {
return { ...col, cards: [...col.cards, card] };
}
return col;
}),
};
}
case 'DELETE_CARD': {
return {
...state,
columns: state.columns.map((col) => ({
...col,
cards: col.cards.filter((c) => c.id !== action.cardId),
})),
};
}
default:
return state;
}
}
function useKanban() {
const [state, dispatch] = useReducer(kanbanReducer, initialState, (init) => {
const saved = localStorage.getItem('kanban-board');
return saved ? JSON.parse(saved) : init;
});
useEffect(() => {
localStorage.setItem('kanban-board', JSON.stringify(state));
}, [state]);
return { state, dispatch };
}
export default useKanban;
src/components/Card.jsx
function Card({ card, columnId, onMove }) {
const handleDragStart = (e) => {
e.dataTransfer.setData('cardId', card.id);
e.dataTransfer.setData('fromColumnId', columnId);
};
return (
<div
draggable
onDragStart={handleDragStart}
className="card"
>
<h4>{card.title}</h4>
<span className={`priority priority--${card.priority}`}>
{card.priority}
</span>
</div>
);
}
// src/components/Column.jsx
function Column({ column, dispatch }) {
const handleDrop = (e) => {
e.preventDefault();
const cardId = e.dataTransfer.getData('cardId');
const fromColumnId = e.dataTransfer.getData('fromColumnId');
if (fromColumnId !== column.id) {
dispatch({ type: 'MOVE_CARD', cardId, fromColumnId, toColumnId: column.id });
}
};
return (
<div
className="column"
onDragOver={(e) => e.preventDefault()}
onDrop={handleDrop}
>
<h3>{column.title} ({column.cards.length})</h3>
{column.cards.map((card) => (
<Card key={card.id} card={card} columnId={column.id} dispatch={dispatch} />
))}
</div>
);
}

Powodzenia!

Kanban Board to klasyczny projekt portfolio. Dobrze zrobiony, z drag & drop i czystym kodem, robi wrażenie na rekruterach. Zacznijcie od wariantu A — od razu zobaczysz jak złożone jest zarządzanie stanem kiedy masz tablice w tablicach!