Найти в Дзене

SUREcrm Описание фронтенд-части (React JS) проекта (часть 2)

В качестве frontend используется React 17 (typescript), Recoil, Material-UI 5 Репозиторий: https://github.com/levtrilev/surecrm - проект является открытым. Вы можете свободно копировать и использовать его. Данное описание поможет разобраться в коде. Соглашение об именовании объектов: переменные и папки именуются, начиная с маленькой латинской буквы. Объекты, компоненты и типы данных именуются, начиная с большой латинской буквы, с использованием нотации camelCase. (исключения – объекты, одноименные с объектами БД, например, поля JSON-объектов, где_используется_кебаб_нотация.) Структура папок React JS проекта После создания проекта командой «yarn create react-app surecrm --template typescript» в выбранной папке создана папка проекта «surecrm», внутри которой в папке \\surecrm\src созданы папки проекта: «components», «shared», «state». Из стандартных файлов после создания проекта в корне проекта (то есть в папке \\surecrm\src), разработка происходит в файлах App.tsx, index.tsx и types.d.t
Оглавление

В качестве frontend используется React 17 (typescript), Recoil, Material-UI 5

Репозиторий: https://github.com/levtrilev/surecrm - проект является открытым. Вы можете свободно копировать и использовать его. Данное описание поможет разобраться в коде.

Выбор из справочника категорий при заполнении карточки товара
Выбор из справочника категорий при заполнении карточки товара

Соглашение об именовании объектов: переменные и папки именуются, начиная с маленькой латинской буквы. Объекты, компоненты и типы данных именуются, начиная с большой латинской буквы, с использованием нотации camelCase. (исключения – объекты, одноименные с объектами БД, например, поля JSON-объектов, где_используется_кебаб_нотация.)

Структура папок React JS проекта

После создания проекта командой «yarn create react-app surecrm --template typescript» в выбранной папке создана папка проекта «surecrm», внутри которой в папке \\surecrm\src созданы папки проекта: «components», «shared», «state». Из стандартных файлов после создания проекта в корне проекта (то есть в папке \\surecrm\src), разработка происходит в файлах App.tsx, index.tsx и types.d.ts. В основном, интенсивно пополняется файл types.d.ts – здесь объявляются все новые типы данных, например, ProductType, OrderType.

Некоторые изменения внесены в \\surecrm\public\index.html. Остальные разработки происходят в папках \\surecrm\src\components, \\surecrm\src\shared и \\surecrm\src\state (далее упоминаю их как components, shared и state).

В папке shared добавляются объекты, общие для всех бизнес-компонентов. Например объект (подпапка) navigation, файл appConsts.ts – общие для всего приложения константы..

Для каждой бизнес-сущности создается папка в \\surecrm\src\components, например: components\customer или components\customerCategory.

Работа с данными - подпапка data. Внутри папки бизнес-компонента находится подпапка data, где располагаются два файла: имяDao.ts и имяState.ts, например, productDao.ts – здесь собраны функции доступа к данным (Dao – data access objects) и productState.ts – здесь собраны объявления Recoil state объектов (atom, selector, – см getting started по Recoil https://recoiljs.org/docs/introduction/getting-started ) и их дефолтные значения.

Основные файлы-компоненты бизнес-объекта (на примере Product):

ProductGrig.tsx

Компонент – списочное представление товаров

ProductEdit.tsx

Компонент-обертка над формой редактирования карточки товара, содержащий основные бизнес-функции сохранения, удаления, обновления

ProductFormDialog.tsx

Компонент-форма редактирования карточки товара с техническими функциями «при изменении поля», debounce итд

ProductSelector.tsx

Компонент – окно для выбора и поиска товаров в случаях, когда в формах редактирования других сущностей (заказ) нужно выбрать товар из справочника

Объекты состояния – файл data\productState.ts

Замечание: Здесь подробно описывается реализация объектов для одной бизнес-сущности Product, но другие бизнес-сущности обслуживаются такими же объектами. Например, достаточно скопировать папку бизнес-сущности, аккуратно переименовать все переменные и имена файлов, типов и объектов, например, currentProductIdState на currentCustomerIdState и все заработает. Естественно с разницей на то, что у каждой бизнес-сущности есть свои (в соответствии со структурой БД их надо добавить в данные и в верстку формы) специфические поля кроме id и name.

Про Recoil: atomFamilyотличается от atom и selectorFamily отличается от selector тем, что в случае Family можно инициализировать одновременно несколько одноименных объектов (открыть несколько товаров с разными id, например). Поэтому для единичных объектов предусмотрены Family, а для списочных (список товаров) инициализировать одновременно два и более списков товаров не предусмотрено.

Атомы:

currentProductIdState – atomFamily – id товара, выбранного пользователем для открытия на редактирование

newProductState – atom, технический, для использования непосредственно в верстке, использующийся для создания нового или редактирования единичного объекта (товара в данном случае). Если создается новый объект, то используется дефолтное значение атома (см. newProductDefault). Если редактируется имеющийся объект в этот атом загружается имеющееся значение из селектора. Об открытии на редактирование подробнее см. ниже ProductEdit.

Селекторы:

productsQuery – selector – список товаров - возвращается результат запроса из одной таблицы, например такого: const response = await fetch(«https://surecrm.org/view_products», …);

productsFullQuery - selector – список товаров - возвращается результат запроса из группы таблиц, например такого: const response = await fetch(«https://surecrm.org/view_products?select=*,product_categories:view_product_categories(id,name)», …). Таблица товаров содержит «голый» id категории товара, а такой запрос возвращает не только id категории товара, но и название категории товара в случае, если в таблице товаров id категории товара определен как Foreign Key

productQuery – selectorFamily – возвращает данные товара, имеющего id, значение которого установлено в атоме currentProductIdState

Компонент основного представления списка – файл ProductGrig.tsx

Определен функциональный компонент ProductsGrid() – без пропсов.

Вёрстка:

Используется компонент DataGrid из библиотеки MaterialUI (см. https://mui.com/x/react-data-grid/getting-started/ ), используются общие компаненты (из папки shared) TopDocsButtons

и YesCancelDialog. Это простые компоненты. TopDocsButtons – кнопки над списком Обновить список, Создать новый (элемент), Удалить(выделенные элементы). YesCancelDialog – при удалении и при выходе без сохранения введенной информации запрашивается подтверждение.

Также здесь вызывается сложный (будет описан отдельно) компонент для создания/редактирования товара ProductEdit.

Основные функции:

editProductAction, copyProductAction, deleteProductAction – назнначение функций понятно по названию.

Прокомментирую функцию editProductAction (остальные проще). Функция вызывается по нажатию кнопки Редактировать в списке товаров. В этом случае в нее передается id товара. Либо по кнопке Создать над списком. В этом случае в нее передается id товара = 0.

const editProductAction= (id: number) => {

if(id=== 0) { // если создается новый товар

setNewProduct(newProductDefault); // создаем новый пустой товар

setCurrentProductId(0); // обнуляем текущий id товара, чтобы поле название товара тоже было пустым

setCurrentProdCategId(0); // обнуляем текущий id категории товара, чтобы поле название категории товара тоже было пустым

editmodeText= 'создание нового'; // в карточке товара пользователь видит в каком режиме он работает – редактирование или создание

} else {

editmodeText = 'редактирование';

setCurrentProductId(id); // чтобы по id «подтянуть» в селектор productQuery название товара

const product= products.find(x => x.id=== id) as ProductFullType; // в массиве товаров найти элемент с указанным id

setNewProduct(fullProductToProduct(product)); // найденный объект «загружается» в переменную, где он будет редактироваться

setCurrentProdCategId(product.category_id); // чтобы по id «подтянуть» в соответствующий селектор название категории товара

}

setOpenEditModal(true); // устанавливается «флаг», по которому открывается модальное окно редактирования

};

Компонент редактирования Товара – файл ProductEdit.tsx

Определен функциональный компонент ProductEdit со следующими пропсами:

interface Props {

modalState: boolean; // сейчас открывается только модально. Оставил для развития – если понадобится открывать несколько

setFromParrent: SetOpenModal; // Флаг открытия/закрытия окна

editmodeText: string; // строка для вывода пользователю о режиме работы редактирование/новый

outerEditContext: string; // контекст редактирования для селекторов и атомов – на случай, если будут открыты несколько аналогичных объектов

}

Вёрстка: вызывается компонент с формой редактирования ProductFormDialog и диалог подтверждения YesNoCancelDialog

Основные функции:

updateProduct – основное: в зависимости от режима создание/новый вызывается функция работы с данными , postNewProduct(newProduct) – для вставки новой записи в таблицу БД или putUpdatedProduct(newProduct) – для обновления записи

И дополнительно устанавливается setIsModified(false) в знак того, что сохранение не требуется. В этом случае при выходе из окна редактирования пользователю не будет задан вопрос о подтверждении выхода с потерей результатов ввода данных

useEffect – зависящая от currentProdCategId, – в результате выбора пользователя при вызове справочника категорий, устанавливает в карточке товара id категории товара;

useEffect – зависящая от yesNoCancel, – срабатывает по результату взаимодействия с пользователем при выводе диалога подтверждения удаления или выхода без сохранения введенных данных.

handleClose - обрабатывается действие закрытия(выхода). Если данные пользователем вводились/изменялись – выводится диалог подтверждения выхода без сохранения. Если не вводились – закрывается модальное окно.

Компонент - форма редактирования Товара – файл ProductFormDialog.tsx

Определен функциональный компонент ProductFormDialog. Назначение пропсов соответствует их названиям.

const isInitialMount = useRef(-2);

используется прием, когда в функциям UseEffect() отслеживается первичный mount – в этот момент значение isInitialMount инкрементируется. Таких функций здесь две. Когда значение достигает 0, значит это уже целевое срабатывание «эффекта».

Еще прокомментирую применение debounce – приёма, предотвращающего срабатывание обработки onchange при каждом вводе очередного символа в полях ввода. Конструкция получилась громоздкая, поэтому для каждого поля код заключен в

// #region onProductNameChange

// #endregion onProductNameChange

для возможности коллапса строк кода. Каждый такой фрагмент включает 1)собственно обработчик события ввода, который формирует переменную с введенным значением 2)функцию-вызов хука обновления целевого объекта (обернутого в debounce 1 раз в секунду), 3) useEffect для запуска хука по изменению вводимого значения. Собственную функцию debounce написал и оставил на всякий случай в shared. Но позже обнаружил эту функцию в библиотеке MUI и использую её.

Модальное окно реализовано как Druggable (можно перетаскивать мышкой). Я не уверен, что следует оставить этот вариант, поскольку пропала возможность в полях текстового ввода, например, мышкой выделить фрагмент текста, - вместо этого начинается перетаскивание окна. Для Druggable реализации служат объекты:

const paperComponentEnabledRef = useRef(PaperComponentEnabled);

const paperComponentDisabledRef = useRef(PaperComponentDisabled);

const paperComponentRef = useRef(PaperComponentEnabled);

const enableDruggableParent = () => {

paperComponentRef.current = paperComponentEnabledRef.current;

};

Остальные понятны из наименований.

Компонент – окно поиска и выбора Товара из справочника – файл ProductSelector.tsx

Определен функциональный компонент ProductSelector. Назначение пропсов соответствует их названиям.

Компонент является оберткой, т.е. готовит данные для пропсов и вызывает единый для всех бизнес-сущностей компонент SelectorBodySearch, находящийся в папке shared – его копию не надо реализовывать при создании объектов новой бизнес-сущности.

Вызывается также компонент ProductEdit в случае, если пользователь в процессе работы со справочником товаров нажмет кнопку Редактировать.

Напоминаю, что проект является открытым - Вы можете свободно копировать и использовать его. Данное описание поможет разобраться в коде. Продолжение следует