@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.
package/dist/react.esm.js CHANGED
@@ -3,7 +3,7 @@ import { Icon, IconButtonInnerChild, IconButton, Flex, CircularProgress, transit
3
3
  import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
4
4
  import { memo, forwardRef, isValidElement, Fragment, createContext, useContext, useId, useMemo, useRef, useCallback, useLayoutEffect, useSyncExternalStore, useEffect, useState, createElement } from 'react';
5
5
  import { barChartClassNames, lineChartClassNames, BarChart as BarChart$1, LineChart, PieChart } from '@evergis/charts';
6
- import { AttributeType, AttributeIconType, StringSubType, RemoteTaskStatus, generateId, STORAGE_TOKEN_KEY, parseJwt, STORAGE_REFRESH_TOKEN_KEY, AttributeConfigurationType, LayerServiceType, OgcGeometryType } from '@evergis/api';
6
+ import { AttributeType, AttributeIconType, StringSubType, RemoteTaskStatus, HTTPError, TaskResourceSubType, generateId, STORAGE_TOKEN_KEY, parseJwt, STORAGE_REFRESH_TOKEN_KEY, AttributeConfigurationType, LayerServiceType, OgcGeometryType } from '@evergis/api';
7
7
  import { Color as Color$1, ColorScale } from '@evergis/color';
8
8
  import { isValid, format, parseJSON, parseISO, toDate } from 'date-fns';
9
9
  import { isNil, isUndefined, isEqual, cloneDeep, isEmpty, uniqueId, uniqBy, unescape } from 'lodash';
@@ -5152,7 +5152,7 @@ const FeatureControls = styled(Flex).withConfig({ displayName: "FeatureControls"
5152
5152
  const FiltersContainerWrapper = styled(Flex).withConfig({ displayName: "FiltersContainerWrapper", componentId: "sc-1a6ru74" }) `
5153
5153
  flex-direction: column;
5154
5154
  padding: ${({ $padding }) => $padding ?? `${DEFAULT_FILTER_PADDING}px`};
5155
- background-color: ${({ $bgColor, $fontColor, theme: { palette } }) => $bgColor || ($fontColor ? transparentizeColor($fontColor, 6) : palette.element)};
5155
+ background: ${({ $bgColor, $fontColor, theme: { palette } }) => $bgColor || ($fontColor ? transparentizeColor($fontColor, 6) : palette.element)};
5156
5156
  border-radius: ${({ theme: { borderRadius } }) => borderRadius.medium};
5157
5157
 
5158
5158
  ${DropdownField}, input {
@@ -6467,6 +6467,12 @@ function getActionOptionsError(type, value) {
6467
6467
  return "Options must be an object";
6468
6468
  if (type === "runTask" && (typeof value.taskName !== "string" || !value.taskName))
6469
6469
  return "runTask requires taskName";
6470
+ if (type === "runTask") {
6471
+ for (const field of ["fileName", "methodName"]) {
6472
+ if (value[field] !== undefined && (typeof value[field] !== "string" || !value[field].trim()))
6473
+ return `runTask ${field} must be a non-empty string`;
6474
+ }
6475
+ }
6470
6476
  if (type === "openModal" && (typeof value.modalId !== "string" || !value.modalId))
6471
6477
  return "openModal requires modalId";
6472
6478
  if (type === "openUrl" && (typeof value.url !== "string" || !value.url))
@@ -8946,7 +8952,7 @@ const POLL_SUBTASK_INTERVAL_MS = 500;
8946
8952
  const POLL_SUBTASK_TIMEOUT_MS = 15_000;
8947
8953
 
8948
8954
  const StatusBadge = styled(Chip).withConfig({ displayName: "StatusBadge", componentId: "sc-1xmfl10" }) `
8949
- background-color: ${({ bgColor }) => bgColor};
8955
+ background: ${({ bgColor }) => bgColor};
8950
8956
  color: ${({ theme }) => theme.palette.iconContrast};
8951
8957
  `;
8952
8958
 
@@ -9035,6 +9041,22 @@ class PythonExecutionError extends Error {
9035
9041
  const RUN_ID_RADIX = 36;
9036
9042
  const TASK_OBSERVATION_TIMEOUT_MS = 86_400_000;
9037
9043
 
9044
+ const BAD_REQUEST_STATUS = 400;
9045
+ const getPythonTaskConfiguration = async (resourceId, manager, hasExplicitTarget) => {
9046
+ try {
9047
+ return await manager.getTaskResource({ id: resourceId });
9048
+ }
9049
+ catch (error) {
9050
+ if (!hasExplicitTarget || !(error instanceof HTTPError) || error.response.status !== BAD_REQUEST_STATUS)
9051
+ throw error;
9052
+ // Clone the response so a propagated error retains its body for the caller.
9053
+ const body = await error.response.clone().text().catch(() => "");
9054
+ if (!body.includes("Python importMethod is required"))
9055
+ throw error;
9056
+ return undefined;
9057
+ }
9058
+ };
9059
+
9038
9060
  const resolvePythonTaskDefinition = async (request, manager) => {
9039
9061
  const task = request.taskName ? request.tasks?.find(({ name }) => name === request.taskName) : undefined;
9040
9062
  if (request.taskName && !task)
@@ -9042,20 +9064,23 @@ const resolvePythonTaskDefinition = async (request, manager) => {
9042
9064
  const resourceId = task?.resourceId ?? request.resourceId;
9043
9065
  if (!resourceId)
9044
9066
  throw new Error(`Task resourceId is missing: ${request.taskName ?? ""}`);
9045
- const configuration = await manager.getTaskResource({ id: resourceId });
9046
- if (!["pythonService", "PythonTask"].includes(configuration?.taskType)) {
9067
+ const fileName = request.fileName ?? task?.fileName;
9068
+ const methodName = request.methodName ?? task?.methodName;
9069
+ const hasExplicitTarget = typeof fileName === "string" && !!fileName.trim() && typeof methodName === "string" && !!methodName.trim();
9070
+ const configuration = await getPythonTaskConfiguration(resourceId, manager, hasExplicitTarget);
9071
+ if (configuration && configuration.taskResourceSubType !== TaskResourceSubType.PythonTask && !["pythonService", "PythonTask"].includes(configuration.taskType)) {
9047
9072
  throw new Error(`Task ${request.taskName ?? resourceId} is not a Python task`);
9048
9073
  }
9049
- const schema = configuration.defaultConfiguration ?? {};
9074
+ const schema = configuration?.defaultConfiguration ?? {};
9050
9075
  const defaults = buildDefaultForSchema(new SchemaResolver(schema), schema);
9051
- const parameters = [defaults, configuration.userConfiguration, task?.params, request.parameters]
9076
+ const parameters = [defaults, configuration?.userConfiguration, task?.params, request.parameters]
9052
9077
  .filter(isObject)
9053
9078
  .reduce((base, override) => mergeActionParameters(base, override), {});
9054
9079
  return {
9055
9080
  resourceId,
9056
9081
  parameters,
9057
- fileName: request.fileName ?? schema.fileName,
9058
- methodName: request.methodName ?? schema.methodName,
9082
+ fileName: fileName ?? schema.fileName,
9083
+ methodName: methodName ?? schema.methodName,
9059
9084
  script: request.script,
9060
9085
  };
9061
9086
  };
@@ -11291,7 +11316,7 @@ const FilterChip = styled.div.withConfig({ displayName: "FilterChip", componentI
11291
11316
  padding: 0.3125rem 0.5rem;
11292
11317
  height: 1.5rem;
11293
11318
  border-radius: 0.25rem;
11294
- background-color: ${({ $isActive, $bgColor, theme }) => $bgColor
11319
+ background: ${({ $isActive, $bgColor, theme }) => $bgColor
11295
11320
  ? $bgColor
11296
11321
  : $isActive
11297
11322
  ? theme.palette?.primary
@@ -11311,7 +11336,7 @@ const FilterChip = styled.div.withConfig({ displayName: "FilterChip", componentI
11311
11336
  box-sizing: border-box;
11312
11337
 
11313
11338
  &:hover {
11314
- background-color: ${({ $isActive, $bgColor, theme }) => $isActive
11339
+ background: ${({ $isActive, $bgColor, theme }) => $isActive
11315
11340
  ? $bgColor || 'inherit'
11316
11341
  : $bgColor || theme.palette?.elementDark};
11317
11342
  }
@@ -14556,12 +14581,21 @@ const ContainerBackground = memo(({ elementConfig, renderElement }) => {
14556
14581
  return (jsx(ContainerBackgroundLayer, { "$outflow": elementConfig?.options?.outflow, children: renderElement({ id: BG_IMAGE_SLOT_ID, wrap: false }) }));
14557
14582
  });
14558
14583
 
14559
- const AddFeatureContainer = memo(({ elementConfig, renderElement }) => {
14584
+ /** Кнопка нативного действия контейнера со стандартными подстановками дочернего конфига. */
14585
+ const ContainerButton = memo(({ type = WidgetType.Dashboard, elementConfig, ...props }) => {
14586
+ const sources = useConfigStringSources(type);
14587
+ const node = useActionNode();
14588
+ const attributes = props.attributes ?? node.attributes ?? sources.attributes;
14589
+ const resolvedConfig = useMemo(() => resolveElementStrings(elementConfig, { ...sources, attributes }), [elementConfig, sources, attributes]);
14590
+ return jsx(ElementButton, { ...props, type: type, attributes: attributes, elementConfig: resolvedConfig });
14591
+ });
14592
+
14593
+ const AddFeatureContainer = memo(({ type, elementConfig, renderElement }) => {
14560
14594
  const { id, children, style } = elementConfig || {};
14561
14595
  const bgHost = useBgImageHost(elementConfig);
14562
- return (jsxs(Container, { id: id, style: style, ...bgHost, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), children
14563
- .filter(({ type }) => type === "button")
14564
- .map(({ options }, index) => (jsx(AddFeatureButton, { icon: options?.icon, title: options?.title, layerName: options?.layerName, geometryType: options?.geometryType }, index)))] }));
14596
+ const handleAddFeature = useCallback(() => undefined, []);
14597
+ return (jsxs(Container, { id: id, style: style, ...bgHost, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), children?.filter(({ type: childType }) => childType === "button")
14598
+ .map((button, index) => (jsx(ContainerButton, { type: type, elementConfig: button, onClick: handleAddFeature }, button.id ?? index)))] }));
14565
14599
  });
14566
14600
 
14567
14601
  /**
@@ -16495,7 +16529,7 @@ const RoundedBackgroundContainerWrapper = styled(Flex).withConfig({ displayName:
16495
16529
  * иначе фон тонируется цветом иконки, а без неё — цветом текста (colorAttribute/fontColor);
16496
16530
  * если не задан ни один цвет, остаётся токен темы.
16497
16531
  */
16498
- background-color: ${({ $bgColor, $iconColor, $color, theme: { palette } }) => $bgColor || tintedBackground($iconColor || $color) || palette.element};
16532
+ background: ${({ $bgColor, $iconColor, $color, theme: { palette } }) => $bgColor || tintedBackground($iconColor || $color) || palette.element};
16499
16533
  border-radius: ${({ theme: { borderRadius } }) => borderRadius.large};
16500
16534
  flex-wrap: nowrap;
16501
16535
 
@@ -16566,11 +16600,6 @@ const RoundedBackgroundContainerWrapper = styled(Flex).withConfig({ displayName:
16566
16600
  > * {
16567
16601
  text-align: left;
16568
16602
  }
16569
-
16570
- /* Зазор наружу даёт обёртка иконки, здесь — отступ внутри её бокса. */
16571
- span[kind] {
16572
- margin-right: 0.5rem;
16573
- }
16574
16603
 
16575
16604
  ${ContainerValue} {
16576
16605
  width: auto;
@@ -17284,7 +17313,7 @@ const TabContainer = styled.a.withConfig({ displayName: "TabContainer", componen
17284
17313
  justify-content: center;
17285
17314
  flex-wrap: nowrap;
17286
17315
  padding: ${({ onlyIcon }) => (onlyIcon ? 1 : 0.5)}rem 1rem;
17287
- background-color: ${({ active, bgColor, theme: { palette } }) => active ? bgColor || palette.primary : palette.element};
17316
+ background: ${({ active, bgColor, theme: { palette } }) => active ? bgColor || palette.primary : palette.element};
17288
17317
  border-radius: ${({ column, radius, theme: { borderRadius } }) => radius ? `${radius}px` : column ? borderRadius.medium : borderRadius.xLarge};
17289
17318
  text-decoration: none;
17290
17319
 
@@ -17750,22 +17779,22 @@ const useVoteForm = (initialValues) => {
17750
17779
  };
17751
17780
  };
17752
17781
 
17753
- const VoteCreateForm = ({ categories, initialValues, isEditing, canDelete, submitting, onSubmit, onDelete, onCancel, }) => {
17782
+ const VoteCreateForm = ({ type, buttonConfigs, categories, initialValues, isEditing, canDelete, submitting, onSubmit, onDelete, onCancel, }) => {
17754
17783
  const { t } = useGlobalContext();
17755
17784
  const form = useVoteForm(initialValues);
17756
17785
  const { categoryId, setCategoryId, text, setText, multiSelect, setMultiSelect, variants, addVariant, removeVariant, draft, setDraft, isValid, values, } = form;
17757
17786
  const categoryOptions = useMemo(() => categories.map(({ id, name }) => ({ text: name, value: id })), [categories]);
17787
+ const submitButton = useMemo(() => isEditing ? buttonConfigs.saveButton : buttonConfigs.createButton, [buttonConfigs, isEditing]);
17788
+ const handleSubmit = useCallback(() => onSubmit(values), [onSubmit, values]);
17758
17789
  return (jsxs(VoteWrapper, { children: [!isEditing && jsx(VoteHeading, { children: t("vote.newVote", { ns: VOTE_NS, defaultValue: "Новое голосование" }) }), jsx(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 ?? "")) }), jsx(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) }), jsx(VoteSectionLabel, { children: t("vote.variants", { ns: VOTE_NS, defaultValue: "Варианты ответов" }) }), !!variants.length && (jsx(VoteChipList, { children: variants.map(({ text: variantText }, index) => (jsxs(VoteChip, { children: [jsx(VoteVariantText, { children: variantText }), jsx(IconButton, { kind: "close", onClick: () => removeVariant(index) })] }, `${variantText}:${index}`))) })), jsx(Input, { width: "100%", placeholder: t("vote.variantPlaceholder", { ns: VOTE_NS, defaultValue: "Введите вариант и нажмите Enter" }), value: draft, onChange: ({ target }) => setDraft(target.value), onKeyDown: event => {
17759
17790
  if (event.key === "Enter") {
17760
17791
  event.preventDefault();
17761
17792
  addVariant();
17762
17793
  }
17763
- } }), jsxs(Flex, { alignItems: "center", justifyContent: "space-between", children: [jsx(VoteSectionLabel, { children: t("vote.multiSelect", { ns: VOTE_NS, defaultValue: "Множественный выбор" }) }), jsx(Switch, { checked: multiSelect, onChange: () => setMultiSelect(prev => !prev) })] }), jsx(RaisedButton, { primary: true, disabled: !isValid || submitting, onClick: () => onSubmit(values), children: isEditing
17764
- ? t("vote.save", { ns: VOTE_NS, defaultValue: "Сохранить" })
17765
- : t("vote.create", { ns: VOTE_NS, defaultValue: "Создать голосование" }) }), !isValid && (jsx(VoteHint, { children: t("vote.formHint", {
17794
+ } }), jsxs(Flex, { alignItems: "center", justifyContent: "space-between", children: [jsx(VoteSectionLabel, { children: t("vote.multiSelect", { ns: VOTE_NS, defaultValue: "Множественный выбор" }) }), jsx(Switch, { checked: multiSelect, onChange: () => setMultiSelect(prev => !prev) })] }), submitButton && (jsx(ContainerButton, { type: type, elementConfig: submitButton, disabled: !isValid || submitting, onClick: handleSubmit })), !isValid && (jsx(VoteHint, { children: t("vote.formHint", {
17766
17795
  ns: VOTE_NS,
17767
17796
  defaultValue: "Нужны категория, вопрос и минимум два варианта ответа",
17768
- }) })), isEditing && (jsxs(Flex, { alignItems: "center", justifyContent: "space-between", children: [onCancel && (jsx(VoteEditLink, { onClick: onCancel, children: t("vote.cancel", { ns: VOTE_NS, defaultValue: "Отмена" }) })), canDelete && onDelete && (jsx(RaisedButton, { error: true, disabled: submitting, onClick: onDelete, children: t("vote.delete", { ns: VOTE_NS, defaultValue: "Удалить голосование" }) }))] }))] }));
17797
+ }) })), isEditing && (jsxs(Flex, { alignItems: "center", justifyContent: "space-between", children: [onCancel && (jsx(VoteEditLink, { onClick: onCancel, children: t("vote.cancel", { ns: VOTE_NS, defaultValue: "Отмена" }) })), canDelete && onDelete && buttonConfigs.deleteButton && (jsx(ContainerButton, { type: type, elementConfig: buttonConfigs.deleteButton, disabled: submitting, onClick: onDelete }))] }))] }));
17769
17798
  };
17770
17799
 
17771
17800
  /** Блок «Поделиться в соцсетях» — пока только вёрстка, без рабочих ссылок. */
@@ -17774,7 +17803,7 @@ const VoteShare = () => {
17774
17803
  return (jsxs(VoteShareBlock, { children: [jsx(VoteSectionLabel, { children: t("vote.share", { ns: VOTE_NS, defaultValue: "Поделиться в соцсетях" }) }), jsx(VoteShareChips, { children: VOTE_SOCIALS.map(name => (jsx(VoteShareChip, { children: name }, name))) })] }));
17775
17804
  };
17776
17805
 
17777
- const VoteResults = ({ question, category, results, screen, isOwner, hasAnswers, submitting, onSubmit, onEdit, }) => {
17806
+ const VoteResults = ({ type, buttonConfigs, question, category, results, screen, isOwner, hasAnswers, submitting, onSubmit, onEdit, }) => {
17778
17807
  const { t } = useGlobalContext();
17779
17808
  const [selected, setSelected] = useState([]);
17780
17809
  const showControls = screen === "voting";
@@ -17788,8 +17817,9 @@ const VoteResults = ({ question, category, results, screen, isOwner, hasAnswers,
17788
17817
  });
17789
17818
  }, [question.multiSelect]);
17790
17819
  const canSubmit = useMemo(() => selected.length > 0 && !submitting, [selected.length, submitting]);
17820
+ const handleSubmit = useCallback(() => onSubmit(selected), [onSubmit, selected]);
17791
17821
  return (jsxs(VoteWrapper, { children: [category && jsx(VoteCategoryLabel, { children: category.name }), jsx(VoteQuestionText, { children: question.text }), jsx(VoteVariantList, { column: true, value: selected[0] ?? "", children: results.map(({ id, text, count, percent }) => (jsxs(VoteVariantRow, { children: [jsxs(VoteVariantHead, { children: [showControls &&
17792
- (question.multiSelect ? (jsx(Checkbox, { checked: selected.includes(id), onChange: () => toggle(id) })) : (jsx(Radio, { value: id, checked: selected.includes(id), onChange: () => toggle(id) }))), jsx(VoteVariantText, { children: text }), jsx(VoteVariantCount, { children: count })] }), jsx(LinearProgress, { done: percent })] }, id))) }), screen === "unauthenticated" && (jsx(VoteHint, { children: t("vote.authHint", { ns: VOTE_NS, defaultValue: "Чтобы проголосовать, пожалуйста, авторизуйтесь" }) })), screen === "voted" && (jsxs(VoteAcceptedBadge, { children: [jsx(Icon, { kind: "success" }), t("vote.accepted", { ns: VOTE_NS, defaultValue: "Ваш ответ принят" })] })), showControls && (jsx(RaisedButton, { primary: true, disabled: !canSubmit, onClick: () => onSubmit(selected), children: t("vote.submit", { ns: VOTE_NS, defaultValue: "Отправить ответ" }) })), canEdit && onEdit && (jsx(Flex, { children: jsx(VoteEditLink, { onClick: onEdit, children: t("vote.edit", { ns: VOTE_NS, defaultValue: "Редактировать" }) }) })), showShare && jsx(VoteShare, {})] }));
17822
+ (question.multiSelect ? (jsx(Checkbox, { checked: selected.includes(id), onChange: () => toggle(id) })) : (jsx(Radio, { value: id, checked: selected.includes(id), onChange: () => toggle(id) }))), jsx(VoteVariantText, { children: text }), jsx(VoteVariantCount, { children: count })] }), jsx(LinearProgress, { done: percent })] }, id))) }), screen === "unauthenticated" && (jsx(VoteHint, { children: t("vote.authHint", { ns: VOTE_NS, defaultValue: "Чтобы проголосовать, пожалуйста, авторизуйтесь" }) })), screen === "voted" && (jsxs(VoteAcceptedBadge, { children: [jsx(Icon, { kind: "success" }), t("vote.accepted", { ns: VOTE_NS, defaultValue: "Ваш ответ принят" })] })), showControls && buttonConfigs.submitButton && (jsx(ContainerButton, { type: type, elementConfig: buttonConfigs.submitButton, disabled: !canSubmit, onClick: handleSubmit })), canEdit && onEdit && (jsx(Flex, { children: jsx(VoteEditLink, { onClick: onEdit, children: t("vote.edit", { ns: VOTE_NS, defaultValue: "Редактировать" }) }) })), showShare && jsx(VoteShare, {})] }));
17793
17823
  };
17794
17824
 
17795
17825
  /**
@@ -17798,7 +17828,7 @@ const VoteResults = ({ question, category, results, screen, isOwner, hasAnswers,
17798
17828
  * Живёт отдельно от `VoteContainer`, потому что тот отвечает за корень контейнера — размеры,
17799
17829
  * фон и заголовок — и не должен раздуваться ветвлением по состояниям.
17800
17830
  */
17801
- const VoteScreen = ({ screen, categories, question, category, results, totalVotes, isOwner, hasAnswers, isEditing, setIsEditing, editInitialValues, submitting, createVote, submitAnswer, editVote, deleteVote, }) => {
17831
+ const VoteScreen = ({ type, buttonConfigs, screen, categories, question, category, results, totalVotes, isOwner, hasAnswers, isEditing, setIsEditing, editInitialValues, submitting, createVote, submitAnswer, editVote, deleteVote, }) => {
17802
17832
  const handleCreate = useCallback((values) => void createVote(values), [createVote]);
17803
17833
  const handleEdit = useCallback((values) => void editVote(values).then(() => setIsEditing(false)), [editVote, setIsEditing]);
17804
17834
  const handleDelete = useCallback(() => void deleteVote().then(() => setIsEditing(false)), [deleteVote, setIsEditing]);
@@ -17808,14 +17838,14 @@ const VoteScreen = ({ screen, categories, question, category, results, totalVote
17808
17838
  if (screen === "loading")
17809
17839
  return jsx(ContainerLoading, {});
17810
17840
  if (isEditing && question) {
17811
- return (jsx(VoteCreateForm, { categories: categories, initialValues: editInitialValues, isEditing: true, canDelete: !hasAnswers, submitting: submitting, onSubmit: handleEdit, onDelete: handleDelete, onCancel: handleCancel }));
17841
+ return (jsx(VoteCreateForm, { type: type, buttonConfigs: buttonConfigs, categories: categories, initialValues: editInitialValues, isEditing: true, canDelete: !hasAnswers, submitting: submitting, onSubmit: handleEdit, onDelete: handleDelete, onCancel: handleCancel }));
17812
17842
  }
17813
17843
  if (screen === "create") {
17814
- return (jsx(VoteCreateForm, { categories: categories, isEditing: false, canDelete: false, submitting: submitting, onSubmit: handleCreate }));
17844
+ return (jsx(VoteCreateForm, { type: type, buttonConfigs: buttonConfigs, categories: categories, isEditing: false, canDelete: false, submitting: submitting, onSubmit: handleCreate }));
17815
17845
  }
17816
17846
  if (!question)
17817
17847
  return null;
17818
- return (jsx(VoteResults, { question: question, category: category, results: results, totalVotes: totalVotes, screen: screen, isOwner: isOwner, hasAnswers: hasAnswers, submitting: submitting, onSubmit: handleSubmitAnswer, onEdit: handleEditOpen }));
17848
+ return (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 }));
17819
17849
  };
17820
17850
 
17821
17851
  /** Приводит значение атрибута к строке-идентификатору (id приходят числами/строками). */
@@ -18242,6 +18272,9 @@ const useVoteContainer = (elementConfig) => {
18242
18272
  };
18243
18273
  };
18244
18274
 
18275
+ /** Слот кнопки существует только при совпадении id и явном type: "button". */
18276
+ const getButtonSlot = (children, id) => children?.find((child) => child.id === id && child.type === "button");
18277
+
18245
18278
  /**
18246
18279
  * Контейнер «Голосование»: привязывает опрос к объекту карты через `attributeName`
18247
18280
  * и показывает один из экранов — создание, голосование, после голосования
@@ -18253,6 +18286,13 @@ const useVoteContainer = (elementConfig) => {
18253
18286
  const VoteContainer = memo(({ type, elementConfig, isVisible, renderElement }) => {
18254
18287
  const { root, body } = useContainerRoot({ elementConfig });
18255
18288
  const { configured, ...vote } = useVoteContainer(elementConfig);
18289
+ const children = elementConfig?.children;
18290
+ const buttonConfigs = useMemo(() => ({
18291
+ submitButton: getButtonSlot(children, "submitButton"),
18292
+ createButton: getButtonSlot(children, "createButton"),
18293
+ saveButton: getButtonSlot(children, "saveButton"),
18294
+ deleteButton: getButtonSlot(children, "deleteButton"),
18295
+ }), [children]);
18256
18296
  // Без имени атрибута или хотя бы одного источника контейнеру не с чем работать. Ошибка вместо
18257
18297
  // пустоты: молчаливый `null` выдавал бы опечатку в конфиге за «блока просто нет».
18258
18298
  if (!configured) {
@@ -18262,7 +18302,7 @@ const VoteContainer = memo(({ type, elementConfig, isVisible, renderElement }) =
18262
18302
  // в карточке в этом случае тоже не нужен, поэтому не рисуем и корень.
18263
18303
  if (!vote.screen)
18264
18304
  return null;
18265
- return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsx(Container, { ...body, isColumn: true, children: jsx(VoteScreen, { ...vote, screen: vote.screen }) }))] }));
18305
+ return (jsxs(ContainerRoot, { ...root, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(ExpandableTitle, { elementConfig: elementConfig, type: type, renderElement: renderElement }), isVisible && (jsx(Container, { ...body, isColumn: true, children: jsx(VoteScreen, { ...vote, screen: vote.screen, type: type, buttonConfigs: buttonConfigs }) }))] }));
18266
18306
  });
18267
18307
 
18268
18308
  const ExpandableTitle = memo(({ elementConfig, type, renderElement }) => {
@@ -18878,12 +18918,11 @@ const ContainersGroupContainer = memo(props => {
18878
18918
  });
18879
18919
 
18880
18920
  const ExportPdfContainer = memo(({ type, elementConfig, renderElement }) => {
18881
- const { t } = useGlobalContext();
18882
18921
  const { loading, onExport } = useExportPdf(getRootElementId(type));
18883
- const { id, style, options } = elementConfig || {};
18884
- const { icon, title } = options || {};
18922
+ const { id, style, children } = elementConfig || {};
18923
+ const buttonConfig = useMemo(() => getButtonSlot(children, "button"), [children]);
18885
18924
  const bgHost = useBgImageHost(elementConfig);
18886
- return (jsxs(Container, { id: id, style: style, ...bgHost, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), jsx(IconButton, { kind: icon || "download", primary: true, disabled: loading, onClick: onExport, children: title ?? t("downloadPdf", { ns: "dashboard", defaultValue: "Скачать PDF" }) })] }));
18925
+ return (jsxs(Container, { id: id, style: style, ...bgHost, children: [jsx(ContainerBackground, { elementConfig: elementConfig, renderElement: renderElement }), buttonConfig && jsx(ContainerButton, { type: type, elementConfig: buttonConfig, disabled: loading, onClick: onExport })] }));
18887
18926
  });
18888
18927
 
18889
18928
  var ThemeName;