diff --git a/client/src/components/CreateTaskFormContent.tsx b/client/src/components/CreateTaskFormContent.tsx
index 13ae64c..63d2248 100644
--- a/client/src/components/CreateTaskFormContent.tsx
+++ b/client/src/components/CreateTaskFormContent.tsx
@@ -727,6 +727,7 @@ export function CreateTaskFormContent({
diff --git a/client/src/components/RegistryCellEditor.tsx b/client/src/components/RegistryCellEditor.tsx
index 0c48ee7..15aed16 100644
--- a/client/src/components/RegistryCellEditor.tsx
+++ b/client/src/components/RegistryCellEditor.tsx
@@ -10,6 +10,7 @@ import ContactField from '@/components/ContactField';
import { normalizeSelectOptions } from '@/lib/fieldOptions';
import { fieldControlClass } from '@/components/fields/field-styles';
import { SearchableMultiSelect } from '@/components/fields/SearchableMultiSelect';
+import { UserRoleMultiSelect } from '@/components/fields/editors/UserRoleMultiSelect';
import { AutoResizeTextarea } from '@/components/ui/auto-resize-textarea';
interface FormFieldData {
@@ -284,6 +285,22 @@ export default function RegistryCellEditor({
? roles.filter((r) => filter.roleIds!.includes(r.id))
: (roles || []);
+ // Множественный выбор (allowMultiple): значение — массив ["user:5", "role:2"],
+ // сохранение — по клику вне ячейки (общий механизм редактора)
+ if (field.allowMultiple) {
+ return (
+
e.stopPropagation()}>
+ setEditValue(val)}
+ />
+
+ );
+ }
+
const stringValue = (() => {
if (editValue === null || editValue === undefined || editValue === '') return '__none__';
if (typeof editValue === 'number') return `user:${editValue}`;
diff --git a/client/src/components/TaskRegistry.tsx b/client/src/components/TaskRegistry.tsx
index 009a54e..ecf74a9 100644
--- a/client/src/components/TaskRegistry.tsx
+++ b/client/src/components/TaskRegistry.tsx
@@ -513,6 +513,10 @@ const TaskRegistry = ({ tasks, relatedTaskTitles = {}, fields, statuses, users =
} else if (field?.type === 'task') {
const display = displayFieldValue(field, val, displayContext);
if (display.toLowerCase().includes(qLower)) return true;
+ } else if (field?.type === 'user' || field?.type === 'role') {
+ // В т.ч. мультивыбор: массив ["user:5", "role:2"] — ищем по отображаемым именам
+ const display = displayFieldValue(field, val, displayContext);
+ if (display.toLowerCase().includes(qLower)) return true;
} else {
const str = Array.isArray(val)
? (val as Record[]).map(f => f?.name ?? '').join(' ')
diff --git a/client/src/components/fields/editors/UserRoleEditor.tsx b/client/src/components/fields/editors/UserRoleEditor.tsx
index 6e24118..a6f4ea7 100644
--- a/client/src/components/fields/editors/UserRoleEditor.tsx
+++ b/client/src/components/fields/editors/UserRoleEditor.tsx
@@ -9,6 +9,8 @@ import {
SelectItem,
} from '@/components/ui/select';
import type { FieldEditorProps } from '../FieldRegistry';
+import { UserRoleMultiSelect } from './UserRoleMultiSelect';
+import { parseUserFieldValue, normalizeUserFieldEntry } from '@/lib/userFieldValue';
export const UserRoleEditor: React.FC = ({ field, value, onChange, users, roles, autoOpen = false }) => {
const filter = (field.options as any)?.userFilter as { userIds?: number[]; roleIds?: number[] } | undefined;
@@ -73,6 +75,43 @@ export const UserRoleEditor: React.FC
const hasUsers = filteredUsers.length > 0;
const hasRoles = filteredRoles.length > 0;
+ // Множественный выбор (allowMultiple): чекбокс-список пользователей и ролей,
+ // значение — массив строк ["user:5", "role:2"].
+ // Текущие выбранные значения добавляем в опции с настоящими именами,
+ // даже если они не проходят userFilter (как в одиночном режиме выше).
+ if (field.allowMultiple) {
+ const entries = parseUserFieldValue(value)
+ .map(normalizeUserFieldEntry)
+ .filter((e): e is string => !!e);
+ let multiUsers = filteredUsers;
+ let multiRoles = filteredRoles;
+ for (const entry of entries) {
+ if (entry.startsWith('user:')) {
+ const id = Number(entry.slice(5));
+ if (!multiUsers.some((u) => u.id === id)) {
+ const u = users?.find((x) => x.id === id);
+ if (u) multiUsers = [u, ...multiUsers];
+ }
+ } else if (entry.startsWith('role:')) {
+ const id = Number(entry.slice(5));
+ if (!multiRoles.some((r) => r.id === id)) {
+ const r = roles?.find((x) => x.id === id);
+ if (r) multiRoles = [r, ...multiRoles];
+ }
+ }
+ }
+ return (
+
+ );
+ }
+
return (