Видеоуроки, интерактивный редактор и сохранение прогресса — бесплатно, сразу после входа.
ВойтиСоздать аккаунт — бесплатноЗакончили урок?
Войдите, чтобы отмечать прогресс
В этом решении мы разбиваем панель управления на множество мелких компонентов, создавая глубокую иерархию.
Компонент Header выделен отдельно, потому что шапка - это независимая часть интерфейса, которая может использоваться на всех страницах админки. Он принимает функцию onLogout для обработки выхода, но не содержит логику подтверждения - это остаётся в родительском компоненте. Имя пользователя пока захардкожено, но в будущем его можно передать через props.
StatCard - это переиспользуемый компонент для отображения одного числового показателя. Мы выделили его отдельно, потому что карточки имеют одинаковую структуру, но разное содержимое. Компонент получает уже отформатированное значение value - форматирование происходит снаружи, а карточка просто отображает результат.
Компонент Stats принимает объект со всей статистикой и создаёт три карточки. Мы вынесли его отдельно, чтобы изолировать логику отображения статистики - какие карточки показывать, как форматировать значения, какой текст изменений использовать. Форматирование чисел с toLocaleString() происходит здесь, а не в App, потому что это деталь отображения.
StatusBadge инкапсулирует логику отображения статусов. Функции getStatusColor и getStatusText находятся внутри компонента, потому что они нужны только здесь. Компонент принимает сырое значение статуса ('completed', 'pending') и сам решает, какой цвет и текст показать. Это делает использование бейджа простым - достаточно передать статус, не думая о цветах и переводе.
SortableHeader - это переиспользуемый заголовок колонки таблицы с сортировкой. Мы выделили его, потому что три колонки имеют одинаковую логику - клик для сортировки и стрелка для активной колонки. Компонент использует children для текста заголовка - это позволяет писать <SortableHeader>ID заказа</SortableHeader> вместо <SortableHeader text="ID заказа" />. Проверка isActive определяет, показывать ли стрелку.
OrderRow отвечает за отображение одной строки заказа. Мы вынесли строку в компонент, потому что это упрощает код таблицы - вместо длинного map с множеством <td> мы видим простой список <OrderRow>. Компонент использует StatusBadge для статуса, демонстрируя композицию компонентов - строка состоит из ячеек, одна из которых содержит бейдж.
RecentOrdersTable объединяет заголовки и строки таблицы. Таблица получает уже отсортированные заказы и параметры сортировки (sortBy, sortOrder), но не выполняет сортировку сама - она только пробрасывает функцию onSort в заголовки. Колонка "Статус" не использует SortableHeader, потому что по статусу не сортируем - она обычный <th>.
Функция sortOrders вынесена отдельно в App, хотя могла бы быть внутри компонента. Мы выделили её, чтобы чётко отделить бизнес-логику (как сортировать) от логики отображения (как показать таблицу). Функция создаёт копию массива через [...orders], потому что sort мутирует оригинал. Разные типы полей требуют разного сравнения - суммы преобразуются в числа, даты в объекты Date.
При клике на заголовок колонки проверяем, активна ли уже эта колонка. Если да - меняем направление сортировки с возрастания на убывание или наоборот. Если нет - устанавливаем новое поле и начинаем с возрастающей сортировки. Эта логика остаётся в App, потому что она управляет состоянием.
Эффект создаёт интервал, который каждые 5 секунд увеличивает показатели статистики на случайное значение. Мы используем функциональную форму setStats(prevStats => ...), чтобы работать с актуальным состоянием внутри callback интервала. Cleanup функция очищает интервал при размонтировании компонента. Пустой массив зависимостей означает, что эффект выполнится один раз при монтировании.
Компонент App теперь очень простой - три больших блока собраны вместе. Вся сложность скрыта внутри компонентов. App управляет состоянием и бизнес-логикой (сортировка, обновление статистики), а визуальные детали делегированы дочерним компонентам. Это делает код читаемым - структура страницы видна сразу.