@evergis/react 4.0.167 → 4.0.169

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,8 @@ export type ActionParameters = Record<string, unknown>;
5
5
  export interface ActionOptionsMap {
6
6
  runTask: {
7
7
  taskName: string;
8
+ fileName?: string;
9
+ methodName?: string;
8
10
  parameters?: ActionParameters;
9
11
  };
10
12
  setFilters: {
@@ -164,9 +164,8 @@ export interface ElementUploaderProps extends Omit<ContainerProps, "elementConfi
164
164
  */
165
165
  export type ContainerBoxOptions = Pick<ConfigOptions, "width" | "height" | "overflow">;
166
166
  export type AddFeatureContainerOptions = Record<string, never>;
167
- export type AddFeatureButtonOptions = Pick<ConfigOptions, "icon" | "title" | "layerName" | "geometryType">;
168
- export interface AddFeatureButtonChild extends Omit<ConfigContainerChild, "options" | "type"> {
169
- type?: "button";
167
+ export type AddFeatureButtonOptions = Pick<ConfigOptions, "layerName" | "geometryType"> & ElementButtonOptions;
168
+ export interface AddFeatureButtonChild extends Omit<ElementButtonConfig, "options"> {
170
169
  options?: AddFeatureButtonOptions;
171
170
  }
172
171
  export interface AddFeatureContainerConfig extends Omit<ConfigContainerChild, "options" | "templateName" | "children"> {
@@ -356,7 +355,7 @@ export interface EditAttachmentContainerConfig extends Omit<ConfigContainerChild
356
355
  export interface EditAttachmentContainerProps extends Omit<ContainerProps, "elementConfig"> {
357
356
  elementConfig?: EditAttachmentContainerConfig;
358
357
  }
359
- export type ExportPdfContainerOptions = Pick<ConfigOptions, "icon" | "title">;
358
+ export type ExportPdfContainerOptions = Record<string, never>;
360
359
  export interface ExportPdfContainerConfig extends Omit<ConfigContainerChild, "options" | "templateName"> {
361
360
  templateName?: ContainerTemplate.ExportPdf;
362
361
  options?: ExportPdfContainerOptions;
@@ -0,0 +1,4 @@
1
+ import { FC } from 'react';
2
+ import { ElementButtonProps } from '../../types';
3
+ /** Кнопка нативного действия контейнера со стандартными подстановками дочернего конфига. */
4
+ export declare const ContainerButton: FC<ElementButtonProps>;
@@ -1,9 +1,9 @@
1
1
  import { FC } from 'react';
2
- import { VoteScreenProps } from '../types';
2
+ import { VoteButtonProps, VoteScreenProps } from '../types';
3
3
  /**
4
4
  * Тело контейнера: выбирает экран по вью-модели голосования.
5
5
  *
6
6
  * Живёт отдельно от `VoteContainer`, потому что тот отвечает за корень контейнера — размеры,
7
7
  * фон и заголовок — и не должен раздуваться ветвлением по состояниям.
8
8
  */
9
- export declare const VoteScreen: FC<VoteScreenProps>;
9
+ export declare const VoteScreen: FC<VoteScreenProps & VoteButtonProps>;
@@ -1,4 +1,11 @@
1
- import { VoteContainerOptions } from '../../componentTypes';
1
+ import { ElementButtonConfig, VoteContainerOptions } from '../../componentTypes';
2
+ import { WidgetType } from '../../types';
3
+ export type VoteButtonSlotId = "submitButton" | "createButton" | "saveButton" | "deleteButton";
4
+ export type VoteButtonConfigs = Partial<Record<VoteButtonSlotId, ElementButtonConfig>>;
5
+ export interface VoteButtonProps {
6
+ type?: WidgetType;
7
+ buttonConfigs: VoteButtonConfigs;
8
+ }
2
9
  /** Экран, который показывает контейнер голосования. */
3
10
  export type VoteScreen = "loading" | "create" | "voting" | "voted" | "unauthenticated";
4
11
  export interface VoteCategory {
@@ -40,7 +47,7 @@ export interface VoteFormVariant {
40
47
  * к данным отношения не имеют, а `Required<VoteContainerOptions>` потребовал бы и их.
41
48
  */
42
49
  export type VoteDataSources = Required<Pick<VoteContainerOptions, "categoryDataSource" | "questionDataSource" | "variantDataSource" | "answerDataSource">>;
43
- export interface VoteCreateFormProps {
50
+ export interface VoteCreateFormProps extends VoteButtonProps {
44
51
  categories: VoteCategory[];
45
52
  initialValues?: VoteFormValues;
46
53
  isEditing: boolean;
@@ -50,7 +57,7 @@ export interface VoteCreateFormProps {
50
57
  onDelete?: () => void;
51
58
  onCancel?: () => void;
52
59
  }
53
- export interface VoteResultsProps {
60
+ export interface VoteResultsProps extends VoteButtonProps {
54
61
  question: VoteQuestion;
55
62
  category?: VoteCategory;
56
63
  results: VoteVariantResult[];
@@ -833,6 +833,8 @@ export interface ConfigTask {
833
833
  params?: Record<string, unknown>;
834
834
  subType?: string;
835
835
  resourceId?: string;
836
+ fileName?: string;
837
+ methodName?: string;
836
838
  owner?: string;
837
839
  updateDate?: string;
838
840
  id?: string;
@@ -0,0 +1,4 @@
1
+ import { ElementButtonConfig } from '../componentTypes';
2
+ import { ConfigContainerChild } from '../types';
3
+ /** Слот кнопки существует только при совпадении id и явном type: "button". */
4
+ export declare const getButtonSlot: (children: ConfigContainerChild[] | undefined, id: string) => ElementButtonConfig | undefined;
@@ -355,6 +355,12 @@ function getActionOptionsError(type, value) {
355
355
  return "Options must be an object";
356
356
  if (type === "runTask" && (typeof value.taskName !== "string" || !value.taskName))
357
357
  return "runTask requires taskName";
358
+ if (type === "runTask") {
359
+ for (const field of ["fileName", "methodName"]) {
360
+ if (value[field] !== undefined && (typeof value[field] !== "string" || !value[field].trim()))
361
+ return `runTask ${field} must be a non-empty string`;
362
+ }
363
+ }
358
364
  if (type === "openModal" && (typeof value.modalId !== "string" || !value.modalId))
359
365
  return "openModal requires modalId";
360
366
  if (type === "openUrl" && (typeof value.url !== "string" || !value.url))
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,2 @@
1
+ import { PythonExecutionDependencies } from './types';
2
+ export declare const getPythonTaskConfiguration: (resourceId: string, manager: Pick<PythonExecutionDependencies["manager"], "getTaskResource">, hasExplicitTarget: boolean) => Promise<import('@evergis/api').TaskConfigurationDc>;
package/dist/index.js CHANGED
@@ -5154,7 +5154,7 @@ const FeatureControls = styled(uilibGl.Flex).withConfig({ displayName: "FeatureC
5154
5154
  const FiltersContainerWrapper = styled(uilibGl.Flex).withConfig({ displayName: "FiltersContainerWrapper", componentId: "sc-1a6ru74" }) `
5155
5155
  flex-direction: column;
5156
5156
  padding: ${({ $padding }) => $padding ?? `${DEFAULT_FILTER_PADDING}px`};
5157
- background-color: ${({ $bgColor, $fontColor, theme: { palette } }) => $bgColor || ($fontColor ? transparentizeColor($fontColor, 6) : palette.element)};
5157
+ background: ${({ $bgColor, $fontColor, theme: { palette } }) => $bgColor || ($fontColor ? transparentizeColor($fontColor, 6) : palette.element)};
5158
5158
  border-radius: ${({ theme: { borderRadius } }) => borderRadius.medium};
5159
5159
 
5160
5160
  ${uilibGl.DropdownField}, input {
@@ -6469,6 +6469,12 @@ function getActionOptionsError(type, value) {
6469
6469
  return "Options must be an object";
6470
6470
  if (type === "runTask" && (typeof value.taskName !== "string" || !value.taskName))
6471
6471
  return "runTask requires taskName";
6472
+ if (type === "runTask") {
6473
+ for (const field of ["fileName", "methodName"]) {
6474
+ if (value[field] !== undefined && (typeof value[field] !== "string" || !value[field].trim()))
6475
+ return `runTask ${field} must be a non-empty string`;
6476
+ }
6477
+ }
6472
6478
  if (type === "openModal" && (typeof value.modalId !== "string" || !value.modalId))
6473
6479
  return "openModal requires modalId";
6474
6480
  if (type === "openUrl" && (typeof value.url !== "string" || !value.url))
@@ -8948,7 +8954,7 @@ const POLL_SUBTASK_INTERVAL_MS = 500;
8948
8954
  const POLL_SUBTASK_TIMEOUT_MS = 15_000;
8949
8955
 
8950
8956
  const StatusBadge = styled(uilibGl.Chip).withConfig({ displayName: "StatusBadge", componentId: "sc-1xmfl10" }) `
8951
- background-color: ${({ bgColor }) => bgColor};
8957
+ background: ${({ bgColor }) => bgColor};
8952
8958
  color: ${({ theme }) => theme.palette.iconContrast};
8953
8959
  `;
8954
8960
 
@@ -9037,6 +9043,22 @@ class PythonExecutionError extends Error {
9037
9043
  const RUN_ID_RADIX = 36;
9038
9044
  const TASK_OBSERVATION_TIMEOUT_MS = 86_400_000;
9039
9045
 
9046
+ const BAD_REQUEST_STATUS = 400;
9047
+ const getPythonTaskConfiguration = async (resourceId, manager, hasExplicitTarget) => {
9048
+ try {
9049
+ return await manager.getTaskResource({ id: resourceId });
9050
+ }
9051
+ catch (error) {
9052
+ if (!hasExplicitTarget || !(error instanceof api.HTTPError) || error.response.status !== BAD_REQUEST_STATUS)
9053
+ throw error;
9054
+ // Clone the response so a propagated error retains its body for the caller.
9055
+ const body = await error.response.clone().text().catch(() => "");
9056
+ if (!body.includes("Python importMethod is required"))
9057
+ throw error;
9058
+ return undefined;
9059
+ }
9060
+ };
9061
+
9040
9062
  const resolvePythonTaskDefinition = async (request, manager) => {
9041
9063
  const task = request.taskName ? request.tasks?.find(({ name }) => name === request.taskName) : undefined;
9042
9064
  if (request.taskName && !task)
@@ -9044,20 +9066,23 @@ const resolvePythonTaskDefinition = async (request, manager) => {
9044
9066
  const resourceId = task?.resourceId ?? request.resourceId;
9045
9067
  if (!resourceId)
9046
9068
  throw new Error(`Task resourceId is missing: ${request.taskName ?? ""}`);
9047
- const configuration = await manager.getTaskResource({ id: resourceId });
9048
- if (!["pythonService", "PythonTask"].includes(configuration?.taskType)) {
9069
+ const fileName = request.fileName ?? task?.fileName;
9070
+ const methodName = request.methodName ?? task?.methodName;
9071
+ const hasExplicitTarget = typeof fileName === "string" && !!fileName.trim() && typeof methodName === "string" && !!methodName.trim();
9072
+ const configuration = await getPythonTaskConfiguration(resourceId, manager, hasExplicitTarget);
9073
+ if (configuration && configuration.taskResourceSubType !== api.TaskResourceSubType.PythonTask && !["pythonService", "PythonTask"].includes(configuration.taskType)) {
9049
9074
  throw new Error(`Task ${request.taskName ?? resourceId} is not a Python task`);
9050
9075
  }
9051
- const schema = configuration.defaultConfiguration ?? {};
9076
+ const schema = configuration?.defaultConfiguration ?? {};
9052
9077
  const defaults = buildDefaultForSchema(new SchemaResolver(schema), schema);
9053
- const parameters = [defaults, configuration.userConfiguration, task?.params, request.parameters]
9078
+ const parameters = [defaults, configuration?.userConfiguration, task?.params, request.parameters]
9054
9079
  .filter(isObject)
9055
9080
  .reduce((base, override) => mergeActionParameters(base, override), {});
9056
9081
  return {
9057
9082
  resourceId,
9058
9083
  parameters,
9059
- fileName: request.fileName ?? schema.fileName,
9060
- methodName: request.methodName ?? schema.methodName,
9084
+ fileName: fileName ?? schema.fileName,
9085
+ methodName: methodName ?? schema.methodName,
9061
9086
  script: request.script,
9062
9087
  };
9063
9088
  };
@@ -11293,7 +11318,7 @@ const FilterChip = styled.div.withConfig({ displayName: "FilterChip", componentI
11293
11318
  padding: 0.3125rem 0.5rem;
11294
11319
  height: 1.5rem;
11295
11320
  border-radius: 0.25rem;
11296
- background-color: ${({ $isActive, $bgColor, theme }) => $bgColor
11321
+ background: ${({ $isActive, $bgColor, theme }) => $bgColor
11297
11322
  ? $bgColor
11298
11323
  : $isActive
11299
11324
  ? theme.palette?.primary
@@ -11313,7 +11338,7 @@ const FilterChip = styled.div.withConfig({ displayName: "FilterChip", componentI
11313
11338
  box-sizing: border-box;
11314
11339
 
11315
11340
  &:hover {
11316
- background-color: ${({ $isActive, $bgColor, theme }) => $isActive
11341
+ background: ${({ $isActive, $bgColor, theme }) => $isActive
11317
11342
  ? $bgColor || 'inherit'
11318
11343
  : $bgColor || theme.palette?.elementDark};
11319
11344
  }
@@ -14558,12 +14583,21 @@ const ContainerBackground = React.memo(({ elementConfig, renderElement }) => {
14558
14583
  return (jsxRuntime.jsx(ContainerBackgroundLayer, { "$outflow": elementConfig?.options?.outflow, children: renderElement({ id: BG_IMAGE_SLOT_ID, wrap: false }) }));
14559
14584
  });
14560
14585
 
14561
- const AddFeatureContainer = React.memo(({ elementConfig, renderElement }) => {
14586
+ /** Кнопка нативного действия контейнера со стандартными подстановками дочернего конфига. */
14587
+ const ContainerButton = React.memo(({ type = exports.WidgetType.Dashboard, elementConfig, ...props }) => {
14588
+ const sources = useConfigStringSources(type);
14589
+ const node = useActionNode();
14590
+ const attributes = props.attributes ?? node.attributes ?? sources.attributes;
14591
+ const resolvedConfig = React.useMemo(() => resolveElementStrings(elementConfig, { ...sources, attributes }), [elementConfig, sources, attributes]);
14592
+ return jsxRuntime.jsx(ElementButton, { ...props, type: type, attributes: attributes, elementConfig: resolvedConfig });
14593
+ });
14594
+
14595
+ const AddFeatureContainer = React.memo(({ type, elementConfig, renderElement }) => {
14562
14596
  const { id, children, style } = elementConfig || {};
14563
14597
  const bgHost = useBgImageHost(elementConfig);
14564
- return (jsxRuntime.jsxs(Container, { id: id, style: style, ...bgHost, children: [jsxRuntime.jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), children
14565
- .filter(({ type }) => type === "button")
14566
- .map(({ options }, index) => (jsxRuntime.jsx(AddFeatureButton, { icon: options?.icon, title: options?.title, layerName: options?.layerName, geometryType: options?.geometryType }, index)))] }));
14598
+ const handleAddFeature = React.useCallback(() => undefined, []);
14599
+ return (jsxRuntime.jsxs(Container, { id: id, style: style, ...bgHost, children: [jsxRuntime.jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), children?.filter(({ type: childType }) => childType === "button")
14600
+ .map((button, index) => (jsxRuntime.jsx(ContainerButton, { type: type, elementConfig: button, onClick: handleAddFeature }, button.id ?? index)))] }));
14567
14601
  });
14568
14602
 
14569
14603
  /**
@@ -16497,7 +16531,7 @@ const RoundedBackgroundContainerWrapper = styled(uilibGl.Flex).withConfig({ disp
16497
16531
  * иначе фон тонируется цветом иконки, а без неё — цветом текста (colorAttribute/fontColor);
16498
16532
  * если не задан ни один цвет, остаётся токен темы.
16499
16533
  */
16500
- background-color: ${({ $bgColor, $iconColor, $color, theme: { palette } }) => $bgColor || tintedBackground($iconColor || $color) || palette.element};
16534
+ background: ${({ $bgColor, $iconColor, $color, theme: { palette } }) => $bgColor || tintedBackground($iconColor || $color) || palette.element};
16501
16535
  border-radius: ${({ theme: { borderRadius } }) => borderRadius.large};
16502
16536
  flex-wrap: nowrap;
16503
16537
 
@@ -16568,11 +16602,6 @@ const RoundedBackgroundContainerWrapper = styled(uilibGl.Flex).withConfig({ disp
16568
16602
  > * {
16569
16603
  text-align: left;
16570
16604
  }
16571
-
16572
- /* Зазор наружу даёт обёртка иконки, здесь — отступ внутри её бокса. */
16573
- span[kind] {
16574
- margin-right: 0.5rem;
16575
- }
16576
16605
 
16577
16606
  ${ContainerValue} {
16578
16607
  width: auto;
@@ -17286,7 +17315,7 @@ const TabContainer = styled.a.withConfig({ displayName: "TabContainer", componen
17286
17315
  justify-content: center;
17287
17316
  flex-wrap: nowrap;
17288
17317
  padding: ${({ onlyIcon }) => (onlyIcon ? 1 : 0.5)}rem 1rem;
17289
- background-color: ${({ active, bgColor, theme: { palette } }) => active ? bgColor || palette.primary : palette.element};
17318
+ background: ${({ active, bgColor, theme: { palette } }) => active ? bgColor || palette.primary : palette.element};
17290
17319
  border-radius: ${({ column, radius, theme: { borderRadius } }) => radius ? `${radius}px` : column ? borderRadius.medium : borderRadius.xLarge};
17291
17320
  text-decoration: none;
17292
17321
 
@@ -17752,22 +17781,22 @@ const useVoteForm = (initialValues) => {
17752
17781
  };
17753
17782
  };
17754
17783
 
17755
- const VoteCreateForm = ({ categories, initialValues, isEditing, canDelete, submitting, onSubmit, onDelete, onCancel, }) => {
17784
+ const VoteCreateForm = ({ type, buttonConfigs, categories, initialValues, isEditing, canDelete, submitting, onSubmit, onDelete, onCancel, }) => {
17756
17785
  const { t } = useGlobalContext();
17757
17786
  const form = useVoteForm(initialValues);
17758
17787
  const { categoryId, setCategoryId, text, setText, multiSelect, setMultiSelect, variants, addVariant, removeVariant, draft, setDraft, isValid, values, } = form;
17759
17788
  const categoryOptions = React.useMemo(() => categories.map(({ id, name }) => ({ text: name, value: id })), [categories]);
17789
+ const submitButton = React.useMemo(() => isEditing ? buttonConfigs.saveButton : buttonConfigs.createButton, [buttonConfigs, isEditing]);
17790
+ const handleSubmit = React.useCallback(() => onSubmit(values), [onSubmit, values]);
17760
17791
  return (jsxRuntime.jsxs(VoteWrapper, { children: [!isEditing && jsxRuntime.jsx(VoteHeading, { children: t("vote.newVote", { ns: VOTE_NS, defaultValue: "Новое голосование" }) }), jsxRuntime.jsx(uilibGl.Dropdown, { zIndex: 110000, label: t("vote.category", { ns: VOTE_NS, defaultValue: "Категория" }), width: "100%", placeholder: t("vote.categoryPlaceholder", { ns: VOTE_NS, defaultValue: "Выберите категорию" }), options: categoryOptions, value: categoryId, onChange: items => setCategoryId(String(items[0]?.value ?? "")) }), jsxRuntime.jsx(uilibGl.Input, { label: t("vote.question", { ns: VOTE_NS, defaultValue: "Вопрос" }), width: "100%", placeholder: t("vote.questionPlaceholder", { ns: VOTE_NS, defaultValue: "Текст вопроса" }), value: text, onChange: ({ target }) => setText(target.value) }), jsxRuntime.jsx(VoteSectionLabel, { children: t("vote.variants", { ns: VOTE_NS, defaultValue: "Варианты ответов" }) }), !!variants.length && (jsxRuntime.jsx(VoteChipList, { children: variants.map(({ text: variantText }, index) => (jsxRuntime.jsxs(VoteChip, { children: [jsxRuntime.jsx(VoteVariantText, { children: variantText }), jsxRuntime.jsx(uilibGl.IconButton, { kind: "close", onClick: () => removeVariant(index) })] }, `${variantText}:${index}`))) })), jsxRuntime.jsx(uilibGl.Input, { width: "100%", placeholder: t("vote.variantPlaceholder", { ns: VOTE_NS, defaultValue: "Введите вариант и нажмите Enter" }), value: draft, onChange: ({ target }) => setDraft(target.value), onKeyDown: event => {
17761
17792
  if (event.key === "Enter") {
17762
17793
  event.preventDefault();
17763
17794
  addVariant();
17764
17795
  }
17765
- } }), jsxRuntime.jsxs(uilibGl.Flex, { alignItems: "center", justifyContent: "space-between", children: [jsxRuntime.jsx(VoteSectionLabel, { children: t("vote.multiSelect", { ns: VOTE_NS, defaultValue: "Множественный выбор" }) }), jsxRuntime.jsx(uilibGl.Switch, { checked: multiSelect, onChange: () => setMultiSelect(prev => !prev) })] }), jsxRuntime.jsx(uilibGl.RaisedButton, { primary: true, disabled: !isValid || submitting, onClick: () => onSubmit(values), children: isEditing
17766
- ? t("vote.save", { ns: VOTE_NS, defaultValue: "Сохранить" })
17767
- : t("vote.create", { ns: VOTE_NS, defaultValue: "Создать голосование" }) }), !isValid && (jsxRuntime.jsx(VoteHint, { children: t("vote.formHint", {
17796
+ } }), jsxRuntime.jsxs(uilibGl.Flex, { alignItems: "center", justifyContent: "space-between", children: [jsxRuntime.jsx(VoteSectionLabel, { children: t("vote.multiSelect", { ns: VOTE_NS, defaultValue: "Множественный выбор" }) }), jsxRuntime.jsx(uilibGl.Switch, { checked: multiSelect, onChange: () => setMultiSelect(prev => !prev) })] }), submitButton && (jsxRuntime.jsx(ContainerButton, { type: type, elementConfig: submitButton, disabled: !isValid || submitting, onClick: handleSubmit })), !isValid && (jsxRuntime.jsx(VoteHint, { children: t("vote.formHint", {
17768
17797
  ns: VOTE_NS,
17769
17798
  defaultValue: "Нужны категория, вопрос и минимум два варианта ответа",
17770
- }) })), isEditing && (jsxRuntime.jsxs(uilibGl.Flex, { alignItems: "center", justifyContent: "space-between", children: [onCancel && (jsxRuntime.jsx(VoteEditLink, { onClick: onCancel, children: t("vote.cancel", { ns: VOTE_NS, defaultValue: "Отмена" }) })), canDelete && onDelete && (jsxRuntime.jsx(uilibGl.RaisedButton, { error: true, disabled: submitting, onClick: onDelete, children: t("vote.delete", { ns: VOTE_NS, defaultValue: "Удалить голосование" }) }))] }))] }));
17799
+ }) })), isEditing && (jsxRuntime.jsxs(uilibGl.Flex, { alignItems: "center", justifyContent: "space-between", children: [onCancel && (jsxRuntime.jsx(VoteEditLink, { onClick: onCancel, children: t("vote.cancel", { ns: VOTE_NS, defaultValue: "Отмена" }) })), canDelete && onDelete && buttonConfigs.deleteButton && (jsxRuntime.jsx(ContainerButton, { type: type, elementConfig: buttonConfigs.deleteButton, disabled: submitting, onClick: onDelete }))] }))] }));
17771
17800
  };
17772
17801
 
17773
17802
  /** Блок «Поделиться в соцсетях» — пока только вёрстка, без рабочих ссылок. */
@@ -17776,7 +17805,7 @@ const VoteShare = () => {
17776
17805
  return (jsxRuntime.jsxs(VoteShareBlock, { children: [jsxRuntime.jsx(VoteSectionLabel, { children: t("vote.share", { ns: VOTE_NS, defaultValue: "Поделиться в соцсетях" }) }), jsxRuntime.jsx(VoteShareChips, { children: VOTE_SOCIALS.map(name => (jsxRuntime.jsx(VoteShareChip, { children: name }, name))) })] }));
17777
17806
  };
17778
17807
 
17779
- const VoteResults = ({ question, category, results, screen, isOwner, hasAnswers, submitting, onSubmit, onEdit, }) => {
17808
+ const VoteResults = ({ type, buttonConfigs, question, category, results, screen, isOwner, hasAnswers, submitting, onSubmit, onEdit, }) => {
17780
17809
  const { t } = useGlobalContext();
17781
17810
  const [selected, setSelected] = React.useState([]);
17782
17811
  const showControls = screen === "voting";
@@ -17790,8 +17819,9 @@ const VoteResults = ({ question, category, results, screen, isOwner, hasAnswers,
17790
17819
  });
17791
17820
  }, [question.multiSelect]);
17792
17821
  const canSubmit = React.useMemo(() => selected.length > 0 && !submitting, [selected.length, submitting]);
17822
+ const handleSubmit = React.useCallback(() => onSubmit(selected), [onSubmit, selected]);
17793
17823
  return (jsxRuntime.jsxs(VoteWrapper, { children: [category && jsxRuntime.jsx(VoteCategoryLabel, { children: category.name }), jsxRuntime.jsx(VoteQuestionText, { children: question.text }), jsxRuntime.jsx(VoteVariantList, { column: true, value: selected[0] ?? "", children: results.map(({ id, text, count, percent }) => (jsxRuntime.jsxs(VoteVariantRow, { children: [jsxRuntime.jsxs(VoteVariantHead, { children: [showControls &&
17794
- (question.multiSelect ? (jsxRuntime.jsx(uilibGl.Checkbox, { checked: selected.includes(id), onChange: () => toggle(id) })) : (jsxRuntime.jsx(uilibGl.Radio, { value: id, checked: selected.includes(id), onChange: () => toggle(id) }))), jsxRuntime.jsx(VoteVariantText, { children: text }), jsxRuntime.jsx(VoteVariantCount, { children: count })] }), jsxRuntime.jsx(uilibGl.LinearProgress, { done: percent })] }, id))) }), screen === "unauthenticated" && (jsxRuntime.jsx(VoteHint, { children: t("vote.authHint", { ns: VOTE_NS, defaultValue: "Чтобы проголосовать, пожалуйста, авторизуйтесь" }) })), screen === "voted" && (jsxRuntime.jsxs(VoteAcceptedBadge, { children: [jsxRuntime.jsx(uilibGl.Icon, { kind: "success" }), t("vote.accepted", { ns: VOTE_NS, defaultValue: "Ваш ответ принят" })] })), showControls && (jsxRuntime.jsx(uilibGl.RaisedButton, { primary: true, disabled: !canSubmit, onClick: () => onSubmit(selected), children: t("vote.submit", { ns: VOTE_NS, defaultValue: "Отправить ответ" }) })), canEdit && onEdit && (jsxRuntime.jsx(uilibGl.Flex, { children: jsxRuntime.jsx(VoteEditLink, { onClick: onEdit, children: t("vote.edit", { ns: VOTE_NS, defaultValue: "Редактировать" }) }) })), showShare && jsxRuntime.jsx(VoteShare, {})] }));
17824
+ (question.multiSelect ? (jsxRuntime.jsx(uilibGl.Checkbox, { checked: selected.includes(id), onChange: () => toggle(id) })) : (jsxRuntime.jsx(uilibGl.Radio, { value: id, checked: selected.includes(id), onChange: () => toggle(id) }))), jsxRuntime.jsx(VoteVariantText, { children: text }), jsxRuntime.jsx(VoteVariantCount, { children: count })] }), jsxRuntime.jsx(uilibGl.LinearProgress, { done: percent })] }, id))) }), screen === "unauthenticated" && (jsxRuntime.jsx(VoteHint, { children: t("vote.authHint", { ns: VOTE_NS, defaultValue: "Чтобы проголосовать, пожалуйста, авторизуйтесь" }) })), screen === "voted" && (jsxRuntime.jsxs(VoteAcceptedBadge, { children: [jsxRuntime.jsx(uilibGl.Icon, { kind: "success" }), t("vote.accepted", { ns: VOTE_NS, defaultValue: "Ваш ответ принят" })] })), showControls && buttonConfigs.submitButton && (jsxRuntime.jsx(ContainerButton, { type: type, elementConfig: buttonConfigs.submitButton, disabled: !canSubmit, onClick: handleSubmit })), canEdit && onEdit && (jsxRuntime.jsx(uilibGl.Flex, { children: jsxRuntime.jsx(VoteEditLink, { onClick: onEdit, children: t("vote.edit", { ns: VOTE_NS, defaultValue: "Редактировать" }) }) })), showShare && jsxRuntime.jsx(VoteShare, {})] }));
17795
17825
  };
17796
17826
 
17797
17827
  /**
@@ -17800,7 +17830,7 @@ const VoteResults = ({ question, category, results, screen, isOwner, hasAnswers,
17800
17830
  * Живёт отдельно от `VoteContainer`, потому что тот отвечает за корень контейнера — размеры,
17801
17831
  * фон и заголовок — и не должен раздуваться ветвлением по состояниям.
17802
17832
  */
17803
- const VoteScreen = ({ screen, categories, question, category, results, totalVotes, isOwner, hasAnswers, isEditing, setIsEditing, editInitialValues, submitting, createVote, submitAnswer, editVote, deleteVote, }) => {
17833
+ const VoteScreen = ({ type, buttonConfigs, screen, categories, question, category, results, totalVotes, isOwner, hasAnswers, isEditing, setIsEditing, editInitialValues, submitting, createVote, submitAnswer, editVote, deleteVote, }) => {
17804
17834
  const handleCreate = React.useCallback((values) => void createVote(values), [createVote]);
17805
17835
  const handleEdit = React.useCallback((values) => void editVote(values).then(() => setIsEditing(false)), [editVote, setIsEditing]);
17806
17836
  const handleDelete = React.useCallback(() => void deleteVote().then(() => setIsEditing(false)), [deleteVote, setIsEditing]);
@@ -17810,14 +17840,14 @@ const VoteScreen = ({ screen, categories, question, category, results, totalVote
17810
17840
  if (screen === "loading")
17811
17841
  return jsxRuntime.jsx(ContainerLoading, {});
17812
17842
  if (isEditing && question) {
17813
- return (jsxRuntime.jsx(VoteCreateForm, { categories: categories, initialValues: editInitialValues, isEditing: true, canDelete: !hasAnswers, submitting: submitting, onSubmit: handleEdit, onDelete: handleDelete, onCancel: handleCancel }));
17843
+ return (jsxRuntime.jsx(VoteCreateForm, { type: type, buttonConfigs: buttonConfigs, categories: categories, initialValues: editInitialValues, isEditing: true, canDelete: !hasAnswers, submitting: submitting, onSubmit: handleEdit, onDelete: handleDelete, onCancel: handleCancel }));
17814
17844
  }
17815
17845
  if (screen === "create") {
17816
- return (jsxRuntime.jsx(VoteCreateForm, { categories: categories, isEditing: false, canDelete: false, submitting: submitting, onSubmit: handleCreate }));
17846
+ return (jsxRuntime.jsx(VoteCreateForm, { type: type, buttonConfigs: buttonConfigs, categories: categories, isEditing: false, canDelete: false, submitting: submitting, onSubmit: handleCreate }));
17817
17847
  }
17818
17848
  if (!question)
17819
17849
  return null;
17820
- return (jsxRuntime.jsx(VoteResults, { question: question, category: category, results: results, totalVotes: totalVotes, screen: screen, isOwner: isOwner, hasAnswers: hasAnswers, submitting: submitting, onSubmit: handleSubmitAnswer, onEdit: handleEditOpen }));
17850
+ return (jsxRuntime.jsx(VoteResults, { type: type, buttonConfigs: buttonConfigs, question: question, category: category, results: results, totalVotes: totalVotes, screen: screen, isOwner: isOwner, hasAnswers: hasAnswers, submitting: submitting, onSubmit: handleSubmitAnswer, onEdit: handleEditOpen }));
17821
17851
  };
17822
17852
 
17823
17853
  /** Приводит значение атрибута к строке-идентификатору (id приходят числами/строками). */
@@ -18244,6 +18274,9 @@ const useVoteContainer = (elementConfig) => {
18244
18274
  };
18245
18275
  };
18246
18276
 
18277
+ /** Слот кнопки существует только при совпадении id и явном type: "button". */
18278
+ const getButtonSlot = (children, id) => children?.find((child) => child.id === id && child.type === "button");
18279
+
18247
18280
  /**
18248
18281
  * Контейнер «Голосование»: привязывает опрос к объекту карты через `attributeName`
18249
18282
  * и показывает один из экранов — создание, голосование, после голосования
@@ -18255,6 +18288,13 @@ const useVoteContainer = (elementConfig) => {
18255
18288
  const VoteContainer = React.memo(({ type, elementConfig, isVisible, renderElement }) => {
18256
18289
  const { root, body } = useContainerRoot({ elementConfig });
18257
18290
  const { configured, ...vote } = useVoteContainer(elementConfig);
18291
+ const children = elementConfig?.children;
18292
+ const buttonConfigs = React.useMemo(() => ({
18293
+ submitButton: getButtonSlot(children, "submitButton"),
18294
+ createButton: getButtonSlot(children, "createButton"),
18295
+ saveButton: getButtonSlot(children, "saveButton"),
18296
+ deleteButton: getButtonSlot(children, "deleteButton"),
18297
+ }), [children]);
18258
18298
  // Без имени атрибута или хотя бы одного источника контейнеру не с чем работать. Ошибка вместо
18259
18299
  // пустоты: молчаливый `null` выдавал бы опечатку в конфиге за «блока просто нет».
18260
18300
  if (!configured) {
@@ -18264,7 +18304,7 @@ const VoteContainer = React.memo(({ type, elementConfig, isVisible, renderElemen
18264
18304
  // в карточке в этом случае тоже не нужен, поэтому не рисуем и корень.
18265
18305
  if (!vote.screen)
18266
18306
  return null;
18267
- return (jsxRuntime.jsxs(ContainerRoot, { ...root, children: [jsxRuntime.jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsxRuntime.jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxRuntime.jsx(Container, { ...body, isColumn: true, children: jsxRuntime.jsx(VoteScreen, { ...vote, screen: vote.screen }) }))] }));
18307
+ return (jsxRuntime.jsxs(ContainerRoot, { ...root, children: [jsxRuntime.jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsxRuntime.jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsxRuntime.jsx(Container, { ...body, isColumn: true, children: jsxRuntime.jsx(VoteScreen, { ...vote, screen: vote.screen, type: type, buttonConfigs: buttonConfigs }) }))] }));
18268
18308
  });
18269
18309
 
18270
18310
  const ExpandableTitle = React.memo(({ elementConfig, type, renderElement }) => {
@@ -18880,12 +18920,11 @@ const ContainersGroupContainer = React.memo(props => {
18880
18920
  });
18881
18921
 
18882
18922
  const ExportPdfContainer = React.memo(({ type, elementConfig, renderElement }) => {
18883
- const { t } = useGlobalContext();
18884
18923
  const { loading, onExport } = useExportPdf(getRootElementId(type));
18885
- const { id, style, options } = elementConfig || {};
18886
- const { icon, title } = options || {};
18924
+ const { id, style, children } = elementConfig || {};
18925
+ const buttonConfig = React.useMemo(() => getButtonSlot(children, "button"), [children]);
18887
18926
  const bgHost = useBgImageHost(elementConfig);
18888
- return (jsxRuntime.jsxs(Container, { id: id, style: style, ...bgHost, children: [jsxRuntime.jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsxRuntime.jsx(uilibGl.IconButton, { kind: icon || "download", primary: true, disabled: loading, onClick: onExport, children: title ?? t("downloadPdf", { ns: "dashboard", defaultValue: "Скачать PDF" }) })] }));
18927
+ return (jsxRuntime.jsxs(Container, { id: id, style: style, ...bgHost, children: [jsxRuntime.jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), buttonConfig && jsxRuntime.jsx(ContainerButton, { type: type, elementConfig: buttonConfig, disabled: loading, onClick: onExport })] }));
18889
18928
  });
18890
18929
 
18891
18930
  exports.ThemeName = void 0;