Skip to main content

Admin-gui development guide

Создание страниц

В директории pages могут лежать только файлики, которые формируют URL страниц. Почитайте об этой особенности внимательно в документации Basic Features: Pages | Next.js

Для того, чтобы рядом с файлом страницы можно было располагать другие файлы: компоненты, скрипты, типы, что-то еще, что относится только к этой странице, нужно

  1. в директории ./views создать страницу
  2. в ./pages создать link-file типа
export {default, getServerSideProps} from '@views/home'

Это позволит нам иметь любую структуру в категории ./views/Home, например. Будет удобно искать файлы, соответствующие конкретной странице

  1. Все страницы в ./pages должны представлять из себя “link-file” к ./views/page

Установка зависимостей

Обязательно указывать exact версию пакета yarn add package -E или yarn add package -DE

Это сделано для того, чтобы при развороте на другой машине не подтянулась последняя версия какого-нибудь пакета. Особенно актуально, если разработчики этого пакета не придерживаются принципов семантического версионирования.

Добавление переменных окружения

Раздел о работе переменных окружения в next есть в официальной документации Basic Features: Environment Variables | Next.js. Стоит отметить, что переменные окружения нельзя использовать в клиентском коде, потому что он намертво вшивается при сборке (можно только в очень исключительных случаях, когда точно понимаешь, что делаешь). Если возникла необходимость, лучше посоветуйся с коллегами. Возможно, что-то делаешь не так.

Вывод сообщений об ошибках, информационных сообщений

В проекте реализованы кастомные алерты (выплывающие справа модальные окна). Их следует использовать для информирования пользователя об ошибках или успехе при обновлении данных.

Screenshot Screenshot

Для удобства подготовлены хуки useError, useSuccess с предустановленными темами модалки, тянутся они из @context/modals либо можно использовать метод appendModal из хука useModalsContext.

import { useError,useSuccess } from '@context/modal';

const Page = () => {
const { appendModal } = useModalsContext();
const { data: menuData, isLoading, error } = useMenu();
const addFile = useOrdersAddFile();

// обработаем ошибку метода получения меню
useError(error);
// обработаем ошибку метода добавления файла
useError(addFile.error);
// покажем, что файл добавлен успешно
useSuccess(addFile.status === 'success' ? ModalMessages.SUCCESS_UPDATE : '');

return <>
<Button onClick={() =>
// просто на клик по кнопке выведем модальное окно-предупреждение
appendModal({ message: 'просто уведомление, theme: 'warning' })
}>Текст</Button>
</>
}

Учтите, для каждой ошибки и успеха нужен свой собственный хук.

Их нельзя комбинировать вот так:

💩

 useError(addFile.error || error);
useSuccess(addFile.status === 'success' ? ModalMessages.SUCCESS_UPDATE: '')

Работа с фильтрами

Все фильтры и табы (первого уровня вложенности) должны сохранять get-параметр в URL. Для этого предусмотрен хук useFilterHelper для фильтров и useTabs для табов.

Пример использования хука useFilterHelper

Строго говоря initialValues, возвращамые хуком, - это не обязательно начальные значения. При отправке формы фильтров изменится url, и изменятся выходные значения из хука useFilterHelper, поэтому initialValues можно использовать в качестве values в местах, где нужно получить значения формы.

import { useFilterHelper } from '@scripts/hooks';

const emptyInitialValues = {
number_like: '',
created_at: '',
status: [],
payment_method: [],
price_from: '',
price_to: '',
seller_id: [],
store_id: [],
delivery_type: [],
delivery_service: [],
is_canceled: '',
is_problem: '',
is_require_check: '',
manager_comment_like: '',
};

const Page = () => {
const { initialValues, URLHelper, filtersActive } = useFilterHelper(emptyInitialValues);

return (<>
{filtersActive ? 'Фильтры активны' : 'Ни один фильтр не выбран'}
<Form initialValues={initialValues} onSubmit={URLHelper}>
/** children */
</Form>
</>)
}

Пример использования хука useTabs

import { useTabs } from '@scripts/hooks';

const Page = () => {
const { getTabsProps } = useTabs();

return (
<Tabs {...getTabsProps()}>
<Tabs.List>
<Tabs.Tab>Информация</Tabs.Tab>
<Tabs.Tab>Адреса</Tabs.Tab>
</Tabs.List>

<Tabs.Panel>
<Information />
</Tabs.Panel>
<Tabs.Panel>
<Addresses />
</Tabs.Panel>
</Tabs>)
}

Внимание. Для того, чтобы query-параметры из урла попадали в хук useFiltersHelper или useTabs ,при первом рендере страницы с фильтрами или табами, нужно описать ф-цию getServerSideProps. Без нее объект query из useRouter будет пустым. Подробнее об этой ф-ции читай в документации NextJS. Не забудьте ее экспортировать на уровне страницы.

export async function getServerSideProps() {
return {
props: {},
};
}

Адаптивность интерфейсов

На момент написания этого руководства адаптивность проработана для общих элементов интерфейса страницы: шапки, меню.

Если при создании страницы/компонента имеется макет адаптива, то учитывать его.

Если макета адаптива нет, то стараться перестраивать интерфейс самостоятельно, учитывая следующее:

  • интерфейсом должно быть возможно пользоваться с мобильных устройств,
  • страница не должна зумиться,
  • для таблиц допустимо использовать горизонтальный скролл,
  • поля формы просто перестраивать друг под друга.

Доступность интерфейсов

Разработку интерфейсов вести с "accessibility in mind". Держать в голове, как минимум, управление с клавиатуры и восприятие разметки скринридерами, для поддержки которых должна соблюдаться семантичность разметки и проставляться нейминги.

Ключевые моменты для семантики:

  • использование landmarks - разметки блоков через теги main, aside, nav, article и пр. вместо нейтральных div
  • использование каскадной структуры заголовков без пропусков уровней - скринридеры строят меню из этих заголовков
  • соблюдение нормативов, определённых спецификацией, при построении базовых элементов, в том числе это включает добавление aria-атрибутов

Нейминги

Нейминги важны, т.к. скринридеру нужен текст, чтобы озвучить его пользователю. По этой причине всем изображениям прописывается атрибут alt, для svg, используемых внутри кнопок, присваивается title, инпуты связываются с label и т.д.

Имена могут указываться напрямую вложением текста внутрь тега, через атрибуты title и alt (для изображений), а также через aria-label и aria-labelledby. Проверить присваемое элементу имя можно через инструменты разработчика в Chrome: F12 -> Elements -> Accessibility.

VisuallyHidden

Бывает необходимо скрыть текст визуально, когда он и так понятен из контекста, но оставить его доступным для AT. Или предоставить пользователям AT дополнительную информацию, которая для этого должна присутствовать в разметке, но визуально показываться не должна. Для этих целей служит компонент <VisuallyHidden /> из GDS, в который нужно оборачивать такие элементы вместо display: none или visibility: hidden.

Контроль качества

A11Y включает в себя множества правил, которым необходимо следовать при разработке и проектировании интерфейсов. В этом документе затронута лишь часть, наиболее важная для фронтенда. Эти правила не взяты с неба, а строго определяются спецификацией. Для их контроля существуют чеклисты, наглядно их перечисляющие: WCAG Checklist.

Для компонентов вне Storybook, например целых страниц, можно для тех же целей использовать браузерные расширения. Тот же axe может быть встроен в качестве chrome extension.

Написание тестов

В настоящее время написание тестов не является обязательным, но возможность такая есть. К проекту подлючены связка Jest + React Testing Library, так что при желании разработчик может покрыть тестами свой код.

Покрытие тестами админ-панели есть в планах развития платформы.

Управление состоянием (state management)

В admin-gui нет стейт-менеджера в привычном виде. На проекте не использует Redux, MobX или какой-то другой стейт-менеджер.

Вместо этого используется другой подход, лишенный boilerplate-кода, простой в освоении и удобный в использовании React Query. Ему посвящен отдельный раздел.

Однако, несмотря на все преимущества ReactQuery иногда возникает необходимость хранить какие-то данные глобально. На нашем проекте это в основном состояния открыт/закрыт для глобальных попапов. Для этого используется обычный react-контекст и провайдер c говорящим названием CommonProvider.