CRM-платформа с low-code/no-code-настройкой: пользователь сам собирает объектную модель раздела, настраивает бизнес-процессы и маршруты согласования под свой бизнес. Дизайнер раздела — инструмент, через который заводится любая бизнес-сущность в системе, задаётся её структура полей и режимы отображения данных.
TL;DR
Дизайнер раздела — основа продукта. Это инструмент, через который клиенты адаптируют CRM под свой бизнес: выделяют нужные им сущности, проектируют объектную модель этих сущностей, не привлекая разработку. Без него платформа не имела бы смысла как low-code-решение.
Над дизайнером раздела я работал в роли продакт-менеджера и продуктового дизайнера, с end-to-end ответственностью на всех этапах. Команда — семь человек: я, ещё один продуктовый дизайнер, которого я менторил по ходу проекта, два фронтенд-разработчика, два бэкенд-разработчика и тестировщик.
Проектировал с нуля. Собрал трёхпанельный редактор с прогрессивным раскрытием настроек, набор типов полей под разные бизнес-задачи (текст, число, дата, время, чекбокс, нумератор, селект, контакт, связь, деталь), валидацию граничных случаев на разных уровнях.
На мне был весь цикл: проработка структуры инструмента, проектирование интерфейса всех типов полей и их настроек, продумывание валидации и граничных случаев, защита решений перед бизнесом, декомпозиция, груминги с командой, поддержка разработки до релиза.
Ниже — общий обзор того, как устроен инструмент и какие продуктовые принципы в его основе. После него — два кейса в глубину: нумератор и деталь.
Трёхпанельный редактор
Дизайнер раздела устроен как редактор с тремя областями. Слева — общие свойства раздела. В центре — холст, на котором собирается макет страницы записи из выбранного пресета сетки. Справа — палитра типов полей и контекстная панель настроек выбранного объекта. Настройки раскрываются по мере действий пользователя.
Типы и свойства полей
Срез по типам сделан по бизнес-назначению, а не по техническому представлению. Доступны: текст, число, дата, дата-время, время, чекбокс, нумератор, селект, контакт, связь, деталь. Часть типов имеет внутренние переключатели, которые меняют поведение поля. У каждого поля есть свой набор свойств. В каждом разделе есть системные поля, которые пользователь не может отредактировать.
Валидация
В дизайнере раздела продумана валидация на нескольких уровнях. Часть проверок блокирует сохранение, часть выводит предупреждение с возможностью пропуска. На одно поле может приходиться сразу несколько ошибок — их нужно было разместить так, чтобы все были видны и понятны.
Нумератор
Автоматическая нумерация записей с двумя моделями счётчика и настраиваемыми масками.
Ситуация
В работе с разделами встречается сценарий, когда требуется автоматическая нумерация записей. Это не редкий кейс, и для него нужен no-code-инструмент, доступный клиенту для самостоятельной настройки.
Задача
Спроектировать механизм нумерации, который работает в двух режимах: с единым счётчиком на весь раздел и с раздельными счётчиками по типам записей внутри одного раздела.
Действия
Решил, что настройка нумерации должна быть в дизайнере раздела на одном уровне с настройкой остальных типов полей. На вход мне дали несколько примеров нумерации, и стало понятно, что внутри них два разных режима: в одних счётчик идёт сквозным потоком на весь раздел, в других — внутри раздела ведутся независимые счётчики по разным критериям, заданным пользователем. Заложил это разделение прямо в модель нумератора — пользователь сначала выбирает тип нумерации (сквозная или раздельная), а дальше работает уже в его логике.
Маски — отдельная сущность внутри нумератора. Каждая маска описывает один формат номера и собирается из набора параметров: статичная строка, текущий месяц, текущий год (две или четыре цифры), текущий день, значение колонки записи на момент сохранения, порядковый номер.
Внутри панели нумератора маски разнесены по двум блокам. Первый — маска по умолчанию, она может быть только одна и нужна, чтобы значение нумератора не оставалось пустым, если ни одно из условий не подошло. Если пользователь подразумевает один общий формат номера — он настраивает только маску по умолчанию, и этого достаточно. Второй блок — маски по условию: их может быть сколько угодно, и в виджете настройки такой маски добавил блок «Применять маску, когда» с системным компонентом фильтра.
В правой панели для уже существующего нумератора добавил визуализацию ресурса — последний созданный номер, шкала заполнения, процент занятости. Это даёт представление о текущем состоянии нумератора до того, как ресурс закончится.
Результат
В дизайнере раздела появился тип поля «Нумератор», который закрывает оба сценария нумерации в рамках одной сущности — сквозную и раздельную модели, маски по умолчанию и по условию. Связь масок с условиями переиспользует системный компонент фильтра, уже знакомый пользователю по другим частям дизайнера.
Для уже работающих нумераторов в правой панели отображается состояние ресурса — текущий счётчик и шкала заполнения. Это позволяет пользователю отслеживать, насколько близок нумератор к исчерпанию, прямо из дизайнера раздела.
Деталь
Вложенная таблица на странице записи, через которую видны связанные с ней записи из другого объекта.
Ситуация
В работе с разделами встречается сценарий, когда на странице записи нужно показать связанные с ней записи из другого раздела. Без встроенного механизма пользователь не может настроить это сам — нужен no-code-инструмент.
Задача
Дать в дизайнере раздела возможность настраивать деталь — вложенную таблицу на странице записи. Чтобы пользователь сам выбрал источник, выстроил связь с основной записью и определил, как деталь выглядит на странице.
Действия
Заложил у детали два типа источника. Существующий — деталь смотрит в уже заведённый раздел системы. Новый — деталь создаёт свой объект, который живёт только в её контексте: он не относится ни к разделам, ни к справочникам, а нужен в случаях, когда обычная объектная модель раздела не закрывает кейс.
Настройку детали целиком собрал внутри дизайнера раздела — пользователь не уходит в отдельный конструктор. Включая настройку объектной модели нового объекта: те же типы полей, тот же макет — собирается прямо в контексте текущей детали.
Связь между основной записью и записями детали — отдельный блок настроек. По умолчанию подставляется связка по идентификатору, если это валидно по объектной модели. Пользователь может выбрать другие колонки, и второе поле фильтруется в зависимости от выбранного первого — чтобы связь оставалась технически возможной.
Результат
В дизайнере раздела появилась деталь, через которую на странице записи показываются связанные с ней записи из другого раздела. Один элемент закрывает оба сценария — связь с существующим разделом системы и работу с собственным объектом для случаев, когда обычная объектная модель раздела не закрывает кейс.
Настройка детали целиком собирается в дизайнере раздела, не вынося пользователя в отдельный конструктор. Связь между основной записью и записями детали настраивается не только по идентификатору, но и по любым полям — это закрывает класс сценариев с непрямыми связями между разделами.
Итог
Дизайнер раздела — один из самых объёмных модулей из всех, над которыми я работал в этом продукте. Сложность была не столько в самой предметной области, сколько в количестве взаимосвязей внутри инструмента и за его пределами.
Настройки в дизайнере раздела не заканчиваются на самом дизайнере. Каждое решение по типам полей и их свойствам прорастает в страницу записи, в режимы отображения данных (таблица, канбан), в edge-cases при добавлении или изменении полей в работающем разделе.
Метрик использования у меня нет — продукт не вышел в коммерческую эксплуатацию. На этапе проектирования я опирался на прогон исходных сценариев в готовом интерфейсе: раскладывал примеры использования, которые мы получили на входе, и проверял, что каждый из них собирается. Это не подтверждает, что решение покрывает все возможные случаи у реальных клиентов, но даёт уверенность хотя бы в том, что для известных нам кейсов оно рабочее.
Главное, что я вынес из этого проекта — опыт проектирования системы, на которую завязано всё остальное в продукте, и более глубокое погружение в техническую сторону.