diff --git a/pages/_meta.en.json b/pages/_meta.en.json
index 13f69fa..b6405bb 100644
--- a/pages/_meta.en.json
+++ b/pages/_meta.en.json
@@ -7,5 +7,6 @@
"newWindow": true
},
"lesson-1": "Lesson 1: Hello World!",
- "lesson-2": "Lesson 2: Forms"
+ "lesson-2": "Lesson 2: Forms",
+ "lesson-3": "Lesson 3: Tables"
}
diff --git a/pages/_meta.ru.json b/pages/_meta.ru.json
index 95163ba..4d52b7e 100644
--- a/pages/_meta.ru.json
+++ b/pages/_meta.ru.json
@@ -7,5 +7,6 @@
"newWindow": true
},
"lesson-1": "Урок 1",
- "lesson-2": "Урок 2: Формы"
+ "lesson-2": "Урок 2: Формы",
+ "lesson-3": "Урок 3: Таблицы"
}
diff --git a/pages/lesson-2/_meta.ru.json b/pages/lesson-2/_meta.ru.json
index 971b213..54d12b3 100644
--- a/pages/lesson-2/_meta.ru.json
+++ b/pages/lesson-2/_meta.ru.json
@@ -1,7 +1,4 @@
{
- "chapter-1": "Chapter 1",
- "chapter-2": "Chapter 2",
- "chapter-3": "Chapter 3",
- "chapter-4": "Chapter 4",
- "chapter-5": "Chapter 5"
+ "chapter-1": "Глава 1. React-hook-form",
+ "chapter-2": "Глава 2. Валидация форм"
}
diff --git a/pages/lesson-2/chapter-2.ru.mdx b/pages/lesson-2/chapter-2.ru.mdx
new file mode 100644
index 0000000..922266a
--- /dev/null
+++ b/pages/lesson-2/chapter-2.ru.mdx
@@ -0,0 +1 @@
+# Under construction
diff --git a/pages/lesson-3/_meta.en.json b/pages/lesson-3/_meta.en.json
new file mode 100644
index 0000000..5bbe580
--- /dev/null
+++ b/pages/lesson-3/_meta.en.json
@@ -0,0 +1,3 @@
+{
+ "chapter-1": "Chapter 1"
+}
diff --git a/pages/lesson-3/_meta.ru.json b/pages/lesson-3/_meta.ru.json
new file mode 100644
index 0000000..894d4e8
--- /dev/null
+++ b/pages/lesson-3/_meta.ru.json
@@ -0,0 +1,3 @@
+{
+ "chapter-1": "Глава 1.Таблицы"
+}
diff --git a/pages/lesson-3/chapter-1.en.mdx b/pages/lesson-3/chapter-1.en.mdx
new file mode 100644
index 0000000..922266a
--- /dev/null
+++ b/pages/lesson-3/chapter-1.en.mdx
@@ -0,0 +1 @@
+# Under construction
diff --git a/pages/lesson-3/chapter-1.ru.mdx b/pages/lesson-3/chapter-1.ru.mdx
new file mode 100644
index 0000000..5c41905
--- /dev/null
+++ b/pages/lesson-3/chapter-1.ru.mdx
@@ -0,0 +1,254 @@
+# Таблицы
+
+## Сортировка
+
+Наш бэкэнд будет принимать параметр sort формата `name-asc` где `name` -
+название поля, а `asc` - направление сортировки. Возможные направления сортировки:
+`asc` и `desc`.
+
+Добавим возможность сортировки для таблиц, для этого:
+
+- Создадим историю в сторибуке:
+
+```tsx
+const data = [
+ {
+ title: 'Project A',
+ cardsCount: 10,
+ updated: '2023-07-07',
+ createdBy: 'John Doe',
+ },
+ {
+ title: 'Project B',
+ cardsCount: 5,
+ updated: '2023-07-06',
+ createdBy: 'Jane Smith',
+ },
+ {
+ title: 'Project C',
+ cardsCount: 8,
+ updated: '2023-07-05',
+ createdBy: 'Alice Johnson',
+ },
+ {
+ title: 'Project D',
+ cardsCount: 3,
+ updated: '2023-07-07',
+ createdBy: 'Bob Anderson',
+ },
+ {
+ title: 'Project E',
+ cardsCount: 12,
+ updated: '2023-07-04',
+ createdBy: 'Emma Davis',
+ },
+]
+export const WithSort = {
+ render: () => {
+ return (
+
+
+
+ | Name |
+ Cards |
+ Last Updated |
+ Created by |
+ |
+
+
+
+ {data.map(item => (
+
+ | {item.title} |
+ {item.cardsCount} |
+ {item.updated} |
+ {item.createdBy} |
+ icons... |
+
+ ))}
+
+
+ )
+ },
+}
+```
+
+Получим просто таблицу, которая пока не сортируемая.
+
+- Создадим стейт для сортировки:
+
+```tsx
+type Sort = {
+ key: string
+ direction: 'asc' | 'desc'
+} | null
+
+const [sort, setSort] = useState(null)
+```
+
+- Добавим обработчик клика на заголовок таблицы:
+
+```tsx
+const handleSort = (key: string) => {
+ if (sort && sort.key === key) {
+ setSort({
+ key,
+ direction: sort.direction === 'asc' ? 'desc' : 'asc',
+ })
+ } else {
+ setSort({
+ key,
+ direction: 'asc',
+ })
+ }
+}
+```
+
+и используем его в таблице:
+
+```tsx
+
+ | handleSort('name')}>Name |
+ handleSort('cardsCount')}>Cards |
+ handleSort('updated')}>Last Updated |
+ handleSort('createdBy')}>Created by |
+ |
+
+```
+
+- Добавим иконки в ячейки заголовка:
+
+```tsx
+ handleSort('name')}>
+ Name
+ {sort && sort.key === 'name' && {sort.direction === 'asc' ? '▲' : '▼'}}
+ |
+```
+
+- Добавим `console.log()` для проверки стейта:
+
+```tsx
+console.log(sort)
+```
+
+Проверяем, при клике должна меняться иконка и в консоли должен появляться правильный объект.
+
+
+
+## Рефакторинг
+
+Мы повторяем слишком много кода в обработчиках, из-за чего мы оставляем слишком много шансов для ошибок.
+
+Одним из возможных решений в данной ситуации будет создание массива с заголовками таблицы и использование его для отрисовки заголовков и обработчиков:
+
+```tsx
+const columns = [
+ {
+ key: 'name',
+ title: 'Name',
+ },
+ {
+ key: 'cardsCount',
+ title: 'Cards',
+ },
+ {
+ key: 'updated',
+ title: 'Last Updated',
+ },
+ {
+ key: 'createdBy',
+ title: 'Created by',
+ },
+]
+
+// ...
+
+{
+ columns.map(column => (
+ handleSort(column.key)}>
+ {column.title}
+ {sort && sort.key === column.key && {sort.direction === 'asc' ? '▲' : '▼'}}
+ |
+ ))
+}
+
+// ...
+```
+
+Протипизируем columns:
+
+```tsx
+type Column = {
+ key: string
+ title: string
+}
+const columns: Array = ...
+```
+
+У нас будет несколько таблиц, поэтому имеет смысл вынести часть логики в отдельный компонент:
+
+```tsx
+export const Header: FC<
+ Omit<
+ ComponentPropsWithoutRef & {
+ columns: Column[]
+ sort?: Sort
+ onSort?: (sort: Sort) => void
+ },
+ 'children'
+ >
+> = ({ columns, sort, onSort, ...restProps }) => {
+ const handleSort = (key: string, sortable?: boolean) => () => {
+ if (!onSort || !sortable) return
+
+ if (sort?.key !== key) return onSort({ key, direction: 'asc' })
+
+ if (sort.direction === 'desc') return onSort(null)
+
+ return onSort({
+ key,
+ direction: sort?.direction === 'asc' ? 'desc' : 'asc',
+ })
+ }
+
+ return (
+
+
+ {columns.map(({ title, key, sortable }) => (
+ |
+ {title}
+ {sort && sort.key === key && {sort.direction === 'asc' ? '▲' : '▼'}}
+ |
+ ))}
+
+
+ )
+}
+```
+
+Обратите внимание, что состоянием мы будем управлять снаружи, а не внутри компонента.
+
+Используем новый компонент:
+
+```tsx
+
+```
+
+И, наконец, создадим нужную нам строку для бэкэнда:
+
+```tsx
+const sortedString = useMemo(() => {
+ if (!sort) return null
+
+ return `${sort.key}-${sort.direction}`
+}, [sort])
+
+console.log(sortedString)
+```
+
+## Самостоятельная работа:
+
+- Добавить сортировку по умолчанию (при третьем клике по заголовку таблицы сортировка должна сбрасываться (null))
diff --git a/pages/lesson-3/images/table-with-basic-sort.png b/pages/lesson-3/images/table-with-basic-sort.png
new file mode 100644
index 0000000..a87799f
Binary files /dev/null and b/pages/lesson-3/images/table-with-basic-sort.png differ