Разработка взаимозависимых полей с Admiral

Время прочтения — 10 минут
Содержание
При разработке админ-панелей часто возникает необходимость динамически изменять значения одного поля в зависимости от выбранного значения другого. Такие элементы интерфейса называются взаимозависимыми полями, которые позволяют упростить работу с формами и делают управление данными более удобным.

Применение взаимозависимых полей

Взаимозависимые поля широко используют в различных продуктах и сервисах, где данные структурированы и зависят друг от друга:
  • Платформы бронирования – выбор специалиста определяет доступные услуги и свободное время для записи;
  • Системы управления контентом – выбор категории отображает соответствующие подкатегории и поля для заполнения;
  • E-commerce платформы – выбор бренда фильтрует доступные модели и их характеристики.

Упрощение разработки с помощью Admiral

Чтобы быстро и эффективно настраивать взаимозависимые поля, мы используем Admiral – нашу фронтенд админ-панель для создания CRUD-интерфейсов. Admiral позволяет быстро и легко создавать зависимости между полями без лишних запросов к серверу.
Главные преимущества Admiral:
  • Готовые компоненты – экономия времени на разработке базовых элементов интерфейса;
  • Гибкая кастомизация – настройка под специфические потребности вашего продукта;
  • Совместимость с Laravel – простая интеграция с популярным PHP-фреймворком.
Установить Admiral можно по ссылке.

Как настроить взаимозависимые поля в Admiral

Рассмотрим практический пример настройки взаимозависимых полей с помощью Admiral.

Шаг 1: Настройка контроллера

В бэкенде каждому полю соответствует метод ajaxSelect, который принимает параметры зависимых полей и возвращает соответствующие данные.
Пример кода:

public function ajaxSelect(Request $request, $field, RecordValues $values)
{
   return match ($field) {
       'client_id' => $values->clients($request->input('query'), $request->input('location_id')),
       'location_id' => $values->locations($request->input('query')),
       'services' => $values->services($request->input('query'), $request->input('location_id'), $request->input('master_id')),
       'master_id' => $values->masters($request->input('query'), $request->input('location_id')),
       default => collect(),
   };
}

Шаг 2: Создание методов выборки данных

Для каждого зависимого поля создаются методы, которые формируют выборку на основе переданных параметров.
Пример для клиентов:

public function clients(string $query, int $locationId)
{
   return Client::query()
       ->when($query, fn ($q) => $q->where('name', 'ilike', "%{$query}%"))
       ->where('location_id', $locationId)
       ->limit(20)
       ->get(['id', 'first_name', 'last_name'])
       ->map(fn (Client $client) => [
           'value' => $client->id,
           'label' => $client->full_name,
       ]);
}
В этом примере список клиентов фильтруется по местоположению, заданному в поле location_id.

Шаг 3: Настройка зависимостей для других полей

Аналогичным образом настраиваются и другие поля, от которых зависят элементы формы. Достаточно передать значения зависимых полей в методы выборки данных.

Шаг 4: Создание взаимозависимых полей в клиентской части Admiral

Теперь, когда на стороне бэкенда написана соответствующая логика, добавим необходимые компоненты и логику внутри конфигурации функции создания круда (createCrud). А именно – в index.form.create.fields и index.form.edit.fields.
Как правило, мы создаем отдельный компонент, внутри которого рендерятся нужные нам поля. Это позволяет изолировать логику и не превратить функцию createCrud во что-то нечитаемое.

export const CRUD = createCRUD({
   path: '/records',
   resource: 'records',
   form: {
       create: {
           fields: <RecordsFields />,
       },
       edit: {
           fields: <RecordsFields />,
       },
   },
})
Для реализации компонента понадобится отрендерить поля и заблокировать зависимые от других значений поля, если необходимое в форме значение отсутствует, с помощью пропса disabled.
Также понадобится хук useUpdateEffect, который будет выполнять выборку данных при появлении в форме связанного значения. useUpdateEffect отличается от обычного useEffect тем, что будет срабатывать только при обновлении компонента, а не при первом рендере.
Ниже добавили простейшую реализацию компонента с зависимыми полями. В компоненте есть комментарии, которые более детально описывают логику.

import React from 'react'
import { AjaxSelectInput, FieldValues, useForm, useUpdateEffect } from '@devfamily/admiral'
import api from '@/src/config/api'


const resource = 'records'


const RecordsFields = () => {
   const { values, setValues, setOptions } = useForm()


   useUpdateEffect(() => {
       setValues((prevValues: FieldValues) => ({
           ...prevValues,
           // При изменении локации, сбрасываем выбранного мастера.
           client_id: null,
       }))


       const fetchOptions = async () => {
           if (!values.location_id) return


           try {
               const clientOptions = await api.getAjaxSelectOptions(resource, 'client_id', {
                   location_id: values.location_id,
               })


              
               setOptions((prevOptions: FieldValues) => ({
                   ...prevOptions,
                   client_id: clientOptions,
               }))
           } catch (error) {
               console.error('Failed to fetch options:', error)
           }
       }


       fetchOptions()
       // При появлении в форме айди локации, происходит срабатывание данного хука с последующей выборкой данных для получения списка клиентов.
   }, [values.location_id])


   return (
       <>
           <AjaxSelectInput
               label="Место обслуживания"
               name="location_id"
               placeholder="Место обслуживания"
               required
               allowClear
               fetchOptions={(field, query) =>
                   api.getAjaxSelectOptions(resource, field, {
                       query,
                   })
               }
           />
           <AjaxSelectInput
               label="Клиент"
               name="client_id"
               placeholder="Клиент"
               required
               allowClear
               fetchOptions={(field, query) => {
                   return api.getAjaxSelectOptions(resource, field, {
                       query,
                       location_id: values.location_id,
                   })
               }}
               // Поле будет заблокировано, пока не выберем локации в верхнем поле.
               disabled={!values.location_id}
           />
       </>
   )
}


// Обёртка необходима, чтобы useUpdateEffect не срабатывал после инициализации формы.
const RecordsFieldsWrapper = () => {
   const { options } = useForm()


   return !!Object.keys(options).length ? <RecordsFields /> : <></>
}


export default RecordsFieldsWrapper
Ниже добавили простейшую реализацию компонента с зависимыми полями. В компоненте есть комментарии, которые более детально описывают логику.

Заключение

Использование Admiral для создания взаимозависимых полей делает разработку админ-панелей более удобной и эффективной. Наша админка предоставляет готовые инструменты для быстрой настройки интерфейса и упрощения управления данными. Попробуйте Admiral в своем проекте и оцените преимущества гибкого и функционального решения.
Остались вопросы по разработке взаимозависимых полей в Admiral? Мы здесь, чтобы помочь!