pixelon 0.3.27 → 0.3.29
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/components/atoms/chat/CurrencyDisplay/ChatCurrencyDisplay.d.ts +18 -0
- package/dist/components/atoms/chat/CurrencyDisplay/index.d.ts +2 -0
- package/dist/components/atoms/chat/index.d.ts +2 -0
- package/dist/components/molecules/chat/StructuredContent/components/ReactiveForm/CostSummaryField.d.ts +13 -0
- package/dist/components/molecules/chat/StructuredContent/components/ReactiveForm/ReactiveFormView.d.ts +2 -0
- package/dist/components/molecules/chat/StructuredContent/components/ReactiveForm/types.d.ts +19 -1
- package/dist/components/molecules/chat/StructuredContent/components/ReactiveForm/useReactiveForm.d.ts +1 -0
- package/dist/components/molecules/chat/StructuredContent/components/SelectWithConfirm/types.d.ts +11 -0
- package/dist/components/molecules/chat/StructuredContent/components/SelectWithConfirm/useSelectEndpoint.d.ts +11 -0
- package/dist/components/molecules/chat/StructuredContent/components/index.d.ts +1 -0
- package/dist/components/molecules/chat/StructuredContent/helpers/AnsweredSummary.d.ts +5 -0
- package/dist/components/molecules/chat/StructuredContent/helpers/types.d.ts +18 -0
- package/dist/components/molecules/chat/StructuredContent/helpers/useComponentInteraction.d.ts +1 -1
- package/dist/core/chat/ChatConfigContext.d.ts +8 -0
- package/dist/core/eventBus/chatEventBus.d.ts +8 -1
- package/dist/index.esm.js +443 -99
- package/dist/index.js +443 -99
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -1639,8 +1639,47 @@ function parseContent(content) {
|
|
|
1639
1639
|
return null;
|
|
1640
1640
|
}
|
|
1641
1641
|
|
|
1642
|
+
const TERMINAL_STATUS = /* @__PURE__ */ new Set(["answered", "cancelled", "expired"]);
|
|
1642
1643
|
const DISPLAY_COMPONENTS = /* @__PURE__ */ new Set(["card", "badge", "reactive_form", "carousel"]);
|
|
1643
1644
|
|
|
1645
|
+
const AnsweredSummary = ({ component }) => {
|
|
1646
|
+
const status = component.status;
|
|
1647
|
+
const values = component.selected_values ?? [];
|
|
1648
|
+
const labels = values.map((v) => v.label).filter(Boolean).join(", ");
|
|
1649
|
+
let icon = "\u2713";
|
|
1650
|
+
let text = labels || "Respondido";
|
|
1651
|
+
let color = "#3a8a3a";
|
|
1652
|
+
if (status === "cancelled") {
|
|
1653
|
+
icon = "\u2715";
|
|
1654
|
+
text = "Cancelado";
|
|
1655
|
+
color = "#999";
|
|
1656
|
+
} else if (status === "expired") {
|
|
1657
|
+
icon = "\u231B";
|
|
1658
|
+
text = "Expir\xF3";
|
|
1659
|
+
color = "#b58a00";
|
|
1660
|
+
}
|
|
1661
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1662
|
+
"div",
|
|
1663
|
+
{
|
|
1664
|
+
style: {
|
|
1665
|
+
display: "inline-flex",
|
|
1666
|
+
alignItems: "center",
|
|
1667
|
+
gap: "6px",
|
|
1668
|
+
padding: "6px 12px",
|
|
1669
|
+
borderRadius: "999px",
|
|
1670
|
+
background: "rgba(0,0,0,0.04)",
|
|
1671
|
+
color,
|
|
1672
|
+
fontSize: "0.875rem",
|
|
1673
|
+
fontWeight: 500
|
|
1674
|
+
},
|
|
1675
|
+
children: [
|
|
1676
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, children: icon }),
|
|
1677
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: text })
|
|
1678
|
+
]
|
|
1679
|
+
}
|
|
1680
|
+
);
|
|
1681
|
+
};
|
|
1682
|
+
|
|
1644
1683
|
const useAppHeight$5 = () => {
|
|
1645
1684
|
React.useEffect(() => {
|
|
1646
1685
|
const setDrawerHeight = () => {
|
|
@@ -1817,13 +1856,14 @@ function useComponentInteraction({
|
|
|
1817
1856
|
}) {
|
|
1818
1857
|
const [isConfirmed, setIsConfirmed] = React.useState(false);
|
|
1819
1858
|
const emitInteraction = React.useCallback(
|
|
1820
|
-
(value, label) => {
|
|
1859
|
+
(value, label, actionId) => {
|
|
1821
1860
|
eventBus?.emit(CHAT_EVENT.COMPONENT_INTERACTION, {
|
|
1822
1861
|
fieldName,
|
|
1823
1862
|
value,
|
|
1824
1863
|
label,
|
|
1825
1864
|
componentType,
|
|
1826
|
-
assistantMessageId
|
|
1865
|
+
assistantMessageId,
|
|
1866
|
+
actionId
|
|
1827
1867
|
});
|
|
1828
1868
|
setIsConfirmed(true);
|
|
1829
1869
|
},
|
|
@@ -1832,6 +1872,114 @@ function useComponentInteraction({
|
|
|
1832
1872
|
return { emitInteraction, isConfirmed };
|
|
1833
1873
|
}
|
|
1834
1874
|
|
|
1875
|
+
const FETCH_TIMEOUT_MS = 12e4;
|
|
1876
|
+
function fetchViaEventBus(eventBus, service, url, method, body) {
|
|
1877
|
+
const requestId = globalThis.crypto?.randomUUID?.() ?? "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
1878
|
+
const r = Math.random() * 16 | 0;
|
|
1879
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
1880
|
+
});
|
|
1881
|
+
return new Promise((resolve, reject) => {
|
|
1882
|
+
const timeoutId = setTimeout(() => {
|
|
1883
|
+
unsubscribe();
|
|
1884
|
+
reject(new Error(`Timeout: no response for ${url} after ${FETCH_TIMEOUT_MS}ms`));
|
|
1885
|
+
}, FETCH_TIMEOUT_MS);
|
|
1886
|
+
const unsubscribe = eventBus.on(CHAT_EVENT.FETCH_RESPONSE, (response) => {
|
|
1887
|
+
if (response.requestId !== requestId) return;
|
|
1888
|
+
clearTimeout(timeoutId);
|
|
1889
|
+
unsubscribe();
|
|
1890
|
+
if (response.httpError) {
|
|
1891
|
+
reject(new Error(response.httpError));
|
|
1892
|
+
} else {
|
|
1893
|
+
resolve(response.data);
|
|
1894
|
+
}
|
|
1895
|
+
});
|
|
1896
|
+
eventBus.emit(CHAT_EVENT.FETCH_REQUEST, {
|
|
1897
|
+
requestId,
|
|
1898
|
+
service,
|
|
1899
|
+
url,
|
|
1900
|
+
method,
|
|
1901
|
+
...body ? { body } : {}
|
|
1902
|
+
});
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
function mapResponseToOptions(data, endpoint) {
|
|
1907
|
+
let items = [];
|
|
1908
|
+
if (endpoint.response_path) {
|
|
1909
|
+
const parts = endpoint.response_path.split(".");
|
|
1910
|
+
let current = data;
|
|
1911
|
+
for (const part of parts) {
|
|
1912
|
+
if (current != null && typeof current === "object") {
|
|
1913
|
+
current = current[part];
|
|
1914
|
+
} else {
|
|
1915
|
+
return [];
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
items = Array.isArray(current) ? current : [];
|
|
1919
|
+
} else if (Array.isArray(data)) {
|
|
1920
|
+
items = data;
|
|
1921
|
+
} else {
|
|
1922
|
+
return [];
|
|
1923
|
+
}
|
|
1924
|
+
return items.filter((item) => item != null && typeof item === "object").map((item) => ({
|
|
1925
|
+
value: String(item[endpoint.value_field] ?? ""),
|
|
1926
|
+
label: String(item[endpoint.label_field] ?? "")
|
|
1927
|
+
}));
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
function normalizeBody(body) {
|
|
1931
|
+
if (!body) return void 0;
|
|
1932
|
+
const normalized = { ...body };
|
|
1933
|
+
for (const [key, val] of Object.entries(normalized)) {
|
|
1934
|
+
if (/s_id$/.test(key) && typeof val === "string") {
|
|
1935
|
+
normalized[key] = [val];
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
return normalized;
|
|
1939
|
+
}
|
|
1940
|
+
function useSelectEndpoint(endpoint, eventBus) {
|
|
1941
|
+
const [options, setOptions] = React.useState([]);
|
|
1942
|
+
const [status, setStatus] = React.useState(endpoint ? "loading" : "idle");
|
|
1943
|
+
const [errorMessage, setErrorMessage] = React.useState("");
|
|
1944
|
+
const fetchedRef = React.useRef(false);
|
|
1945
|
+
const endpointRef = React.useRef(endpoint);
|
|
1946
|
+
endpointRef.current = endpoint;
|
|
1947
|
+
const doFetch = React.useCallback(async () => {
|
|
1948
|
+
const ep = endpointRef.current;
|
|
1949
|
+
if (!ep || fetchedRef.current) return;
|
|
1950
|
+
fetchedRef.current = true;
|
|
1951
|
+
if (!eventBus) {
|
|
1952
|
+
setOptions([
|
|
1953
|
+
{ value: "mock-1", label: "Mock Option 1" },
|
|
1954
|
+
{ value: "mock-2", label: "Mock Option 2" }
|
|
1955
|
+
]);
|
|
1956
|
+
setStatus("idle");
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
try {
|
|
1960
|
+
const body = normalizeBody(ep.body);
|
|
1961
|
+
if (ep.method.toUpperCase() === "POST" && !body) {
|
|
1962
|
+
setErrorMessage("Faltan par\xE1metros para cargar opciones");
|
|
1963
|
+
setStatus("error");
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
const data = await fetchViaEventBus(eventBus, ep.service, ep.url, ep.method, body);
|
|
1967
|
+
const mapped = mapResponseToOptions(data, ep);
|
|
1968
|
+
setOptions(mapped);
|
|
1969
|
+
setStatus("idle");
|
|
1970
|
+
} catch (err) {
|
|
1971
|
+
setErrorMessage(err instanceof Error ? err.message : "Error al cargar opciones");
|
|
1972
|
+
setStatus("error");
|
|
1973
|
+
}
|
|
1974
|
+
}, [eventBus]);
|
|
1975
|
+
React.useEffect(() => {
|
|
1976
|
+
if (endpoint && !fetchedRef.current) {
|
|
1977
|
+
doFetch();
|
|
1978
|
+
}
|
|
1979
|
+
}, [endpoint, doFetch]);
|
|
1980
|
+
return { options, status, errorMessage };
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1835
1983
|
const MOBILE_BREAKPOINT$4 = 640;
|
|
1836
1984
|
const useIsMobile$5 = () => {
|
|
1837
1985
|
const [isMobile, setIsMobile] = React.useState(
|
|
@@ -1855,10 +2003,15 @@ const SelectWithConfirm = ({
|
|
|
1855
2003
|
const [searchValue, setSearchValue] = React.useState("");
|
|
1856
2004
|
const searchInputRef = React.useRef(null);
|
|
1857
2005
|
const isMobile = useIsMobile$5();
|
|
1858
|
-
const { field_name, value, ...componentProps } = component.props;
|
|
1859
|
-
const
|
|
2006
|
+
const { field_name, value, endpoint: endpointProp, ...componentProps } = component.props;
|
|
2007
|
+
const endpoint = endpointProp;
|
|
2008
|
+
const { options: endpointOptions, status: endpointStatus, errorMessage } = useSelectEndpoint(endpoint, eventBus);
|
|
2009
|
+
const staticOptions = componentProps.options || [];
|
|
2010
|
+
const options = endpoint ? endpointOptions : staticOptions;
|
|
1860
2011
|
const placeholder = componentProps.placeholder || "Seleccionar...";
|
|
1861
2012
|
const label = componentProps.label || "";
|
|
2013
|
+
const isLoading = endpointStatus === "loading";
|
|
2014
|
+
const isError = endpointStatus === "error";
|
|
1862
2015
|
const { emitInteraction, isConfirmed } = useComponentInteraction({
|
|
1863
2016
|
eventBus,
|
|
1864
2017
|
fieldName: component.props.field_name,
|
|
@@ -1878,6 +2031,18 @@ const SelectWithConfirm = ({
|
|
|
1878
2031
|
}
|
|
1879
2032
|
}, [isDrawerOpen]);
|
|
1880
2033
|
if (isConfirmed) return null;
|
|
2034
|
+
if (isLoading) {
|
|
2035
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-select-with-confirm", children: [
|
|
2036
|
+
label && /* @__PURE__ */ jsxRuntime.jsx("label", { className: "px-chat-select-with-confirm__label", children: label }),
|
|
2037
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-select-with-confirm__loading", children: "Cargando opciones..." })
|
|
2038
|
+
] });
|
|
2039
|
+
}
|
|
2040
|
+
if (isError) {
|
|
2041
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-select-with-confirm", children: [
|
|
2042
|
+
label && /* @__PURE__ */ jsxRuntime.jsx("label", { className: "px-chat-select-with-confirm__label", children: label }),
|
|
2043
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-select-with-confirm__error", children: errorMessage })
|
|
2044
|
+
] });
|
|
2045
|
+
}
|
|
1881
2046
|
if (isMobile) {
|
|
1882
2047
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-select-with-confirm", children: [
|
|
1883
2048
|
label && /* @__PURE__ */ jsxRuntime.jsx("label", { className: "px-chat-select-with-confirm__label", children: label }),
|
|
@@ -1997,8 +2162,8 @@ const SelectWithConfirm = ({
|
|
|
1997
2162
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1998
2163
|
Select$2,
|
|
1999
2164
|
{
|
|
2000
|
-
options: [],
|
|
2001
2165
|
...componentProps,
|
|
2166
|
+
options,
|
|
2002
2167
|
variant: "chat",
|
|
2003
2168
|
mode: "single",
|
|
2004
2169
|
showSearch: true,
|
|
@@ -2535,6 +2700,22 @@ const ConfirmButton = ({
|
|
|
2535
2700
|
assistantMessageId
|
|
2536
2701
|
});
|
|
2537
2702
|
if (isConfirmed) return null;
|
|
2703
|
+
const actions = component.actions;
|
|
2704
|
+
if (actions && actions.length > 0) {
|
|
2705
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: "8px", flexWrap: "wrap" }, children: actions.map((a) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2706
|
+
Button$1,
|
|
2707
|
+
{
|
|
2708
|
+
variant: a.intent === "primary" ? "primary" : "secondary",
|
|
2709
|
+
onClick: () => emitInteraction(
|
|
2710
|
+
a.emits_value ? component.props.value : a.value,
|
|
2711
|
+
a.label,
|
|
2712
|
+
a.id
|
|
2713
|
+
),
|
|
2714
|
+
children: a.label
|
|
2715
|
+
},
|
|
2716
|
+
a.id
|
|
2717
|
+
)) });
|
|
2718
|
+
}
|
|
2538
2719
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2539
2720
|
Button$1,
|
|
2540
2721
|
{
|
|
@@ -3158,7 +3339,7 @@ const SelectField$1 = ({ field, value, disabled, loading, options, onChange, dra
|
|
|
3158
3339
|
),
|
|
3159
3340
|
searchValue && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "px-chat-select-drawer__search-clear", onClick: () => setSearchValue(""), children: /* @__PURE__ */ jsxRuntime.jsx(Icon$1, { type: ICON_TYPES.CLOSE, size: "small" }) })
|
|
3160
3341
|
] }),
|
|
3161
|
-
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "px-chat-select-drawer__options", role: "listbox", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("li", { className: "px-chat-select-drawer__empty", children: "No hay opciones disponibles" }) : filteredOptions.map((opt) => {
|
|
3342
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "px-chat-select-drawer__options", role: "listbox", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("li", { className: "px-chat-select-drawer__empty", children: field.empty_message || "No hay opciones disponibles" }) : filteredOptions.map((opt) => {
|
|
3162
3343
|
const isSelected = opt.value === value;
|
|
3163
3344
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3164
3345
|
"li",
|
|
@@ -3279,7 +3460,7 @@ const SelectMultipleField = ({ field, value, disabled, loading, options, onChang
|
|
|
3279
3460
|
),
|
|
3280
3461
|
searchValue && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "px-chat-select-drawer__search-clear", onClick: () => setSearchValue(""), children: /* @__PURE__ */ jsxRuntime.jsx(Icon$1, { type: ICON_TYPES.CLOSE, size: "small" }) })
|
|
3281
3462
|
] }),
|
|
3282
|
-
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "px-chat-select-drawer__options", role: "listbox", "aria-multiselectable": "true", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("li", { className: "px-chat-select-drawer__empty", children: "No hay opciones disponibles" }) : filteredOptions.map((opt) => {
|
|
3463
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "px-chat-select-drawer__options", role: "listbox", "aria-multiselectable": "true", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("li", { className: "px-chat-select-drawer__empty", children: field.empty_message || "No hay opciones disponibles" }) : filteredOptions.map((opt) => {
|
|
3283
3464
|
const isChecked = selected.includes(opt.value);
|
|
3284
3465
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3285
3466
|
"li",
|
|
@@ -3377,6 +3558,200 @@ const DateField = ({ field, value, disabled, onChange, drawerPlacement }) => {
|
|
|
3377
3558
|
] });
|
|
3378
3559
|
};
|
|
3379
3560
|
|
|
3561
|
+
const PREFIX$3 = "px-chat-currency";
|
|
3562
|
+
const ChatCurrencyDisplay = ({
|
|
3563
|
+
value,
|
|
3564
|
+
currencyCode,
|
|
3565
|
+
currencySymbol,
|
|
3566
|
+
showCode = true,
|
|
3567
|
+
size = "md",
|
|
3568
|
+
className
|
|
3569
|
+
}) => {
|
|
3570
|
+
const { locale, currency: configCurrency } = useChatConfig();
|
|
3571
|
+
const code = currencyCode || configCurrency?.code || "COP";
|
|
3572
|
+
const symbol = currencySymbol || configCurrency?.symbol || "$";
|
|
3573
|
+
const formatted = React.useMemo(() => {
|
|
3574
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
3575
|
+
const num = typeof value === "string" ? parseFloat(value) : value;
|
|
3576
|
+
if (isNaN(num)) return null;
|
|
3577
|
+
try {
|
|
3578
|
+
return new Intl.NumberFormat(locale || "es-CO", {
|
|
3579
|
+
minimumFractionDigits: 2,
|
|
3580
|
+
maximumFractionDigits: 2
|
|
3581
|
+
}).format(num);
|
|
3582
|
+
} catch {
|
|
3583
|
+
return num.toFixed(2);
|
|
3584
|
+
}
|
|
3585
|
+
}, [value, locale]);
|
|
3586
|
+
if (!formatted) return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${PREFIX$3} ${PREFIX$3}--empty`, children: "-" });
|
|
3587
|
+
const classes = [PREFIX$3, `${PREFIX$3}--${size}`, className].filter(Boolean).join(" ");
|
|
3588
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: classes, children: [
|
|
3589
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `${PREFIX$3}__symbol`, children: symbol }),
|
|
3590
|
+
" ",
|
|
3591
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `${PREFIX$3}__value`, children: formatted }),
|
|
3592
|
+
showCode && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3593
|
+
" ",
|
|
3594
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `${PREFIX$3}__code`, children: code })
|
|
3595
|
+
] })
|
|
3596
|
+
] });
|
|
3597
|
+
};
|
|
3598
|
+
ChatCurrencyDisplay.displayName = "ChatCurrencyDisplay";
|
|
3599
|
+
|
|
3600
|
+
function resolveVariable(template, formValues, fieldStates) {
|
|
3601
|
+
if (!template.startsWith("$")) return template;
|
|
3602
|
+
const withoutDollar = template.slice(1);
|
|
3603
|
+
const dotIndex = withoutDollar.indexOf(".");
|
|
3604
|
+
const fieldName = dotIndex >= 0 ? withoutDollar.slice(0, dotIndex) : withoutDollar;
|
|
3605
|
+
const prop = dotIndex >= 0 ? withoutDollar.slice(dotIndex + 1) : null;
|
|
3606
|
+
const value = formValues[fieldName];
|
|
3607
|
+
const state = fieldStates[fieldName];
|
|
3608
|
+
if (!prop) return value;
|
|
3609
|
+
if (prop === "label" && state) {
|
|
3610
|
+
return state.label || "";
|
|
3611
|
+
}
|
|
3612
|
+
if (prop === "labels" && state && Array.isArray(value)) {
|
|
3613
|
+
return value.map((v) => {
|
|
3614
|
+
const opt = state.options.find((o) => o.value === v);
|
|
3615
|
+
return opt?.label || v;
|
|
3616
|
+
}).join(", ");
|
|
3617
|
+
}
|
|
3618
|
+
if (state) {
|
|
3619
|
+
const selectedOpt = state.options.find((o) => o.value === value);
|
|
3620
|
+
if (selectedOpt && prop in selectedOpt) {
|
|
3621
|
+
return selectedOpt[prop];
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
return "";
|
|
3625
|
+
}
|
|
3626
|
+
function resolveBody(obj, formValues, fieldStates) {
|
|
3627
|
+
if (typeof obj === "string") return resolveVariable(obj, formValues, fieldStates);
|
|
3628
|
+
if (Array.isArray(obj)) return obj.map((item) => resolveBody(item, formValues, fieldStates));
|
|
3629
|
+
if (obj !== null && typeof obj === "object") {
|
|
3630
|
+
const resolved = {};
|
|
3631
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
3632
|
+
resolved[key] = resolveBody(value, formValues, fieldStates);
|
|
3633
|
+
}
|
|
3634
|
+
return resolved;
|
|
3635
|
+
}
|
|
3636
|
+
return obj;
|
|
3637
|
+
}
|
|
3638
|
+
function resolveParams(params, formValues, fieldStates) {
|
|
3639
|
+
const resolved = {};
|
|
3640
|
+
for (const [key, template] of Object.entries(params)) {
|
|
3641
|
+
resolved[key] = resolveVariable(template, formValues, fieldStates);
|
|
3642
|
+
}
|
|
3643
|
+
return resolved;
|
|
3644
|
+
}
|
|
3645
|
+
function resolveSummaryRows(rows, formValues, fieldStates) {
|
|
3646
|
+
return rows.map((row) => ({
|
|
3647
|
+
label: row.label,
|
|
3648
|
+
value: String(resolveVariable(row.value, formValues, fieldStates) ?? "")
|
|
3649
|
+
}));
|
|
3650
|
+
}
|
|
3651
|
+
|
|
3652
|
+
const CostSummaryField = ({
|
|
3653
|
+
field,
|
|
3654
|
+
values,
|
|
3655
|
+
fieldStates,
|
|
3656
|
+
onCostResolved,
|
|
3657
|
+
eventBus,
|
|
3658
|
+
onCancel
|
|
3659
|
+
}) => {
|
|
3660
|
+
const [totalCost, setTotalCost] = React.useState(null);
|
|
3661
|
+
const [loading, setLoading] = React.useState(true);
|
|
3662
|
+
const [error, setError] = React.useState("");
|
|
3663
|
+
const selectedItems = React.useMemo(() => {
|
|
3664
|
+
if (!field.items_from) return [];
|
|
3665
|
+
const { source_field, label_key, value_key } = field.items_from;
|
|
3666
|
+
const sourceState = fieldStates[source_field];
|
|
3667
|
+
const selectedValues = values[source_field];
|
|
3668
|
+
if (!sourceState?.options || !selectedValues) return [];
|
|
3669
|
+
const selectedArr = Array.isArray(selectedValues) ? selectedValues : [selectedValues];
|
|
3670
|
+
return sourceState.options.filter((opt) => selectedArr.includes(opt.value)).map((opt) => ({
|
|
3671
|
+
label: String(opt[label_key] || opt.label || ""),
|
|
3672
|
+
value: Number(opt[value_key] || 0)
|
|
3673
|
+
}));
|
|
3674
|
+
}, [field.items_from, fieldStates, values]);
|
|
3675
|
+
const fetchedRef = React.useRef(false);
|
|
3676
|
+
React.useEffect(() => {
|
|
3677
|
+
if (!field.endpoint || !eventBus || fetchedRef.current) {
|
|
3678
|
+
if (!field.endpoint || !eventBus) setLoading(false);
|
|
3679
|
+
return;
|
|
3680
|
+
}
|
|
3681
|
+
fetchedRef.current = true;
|
|
3682
|
+
const body = resolveBody(field.endpoint.body || {}, values, fieldStates);
|
|
3683
|
+
setLoading(true);
|
|
3684
|
+
setError("");
|
|
3685
|
+
fetchViaEventBus(eventBus, field.endpoint.service, field.endpoint.url, field.endpoint.method, body).then((data) => {
|
|
3686
|
+
const resp = data;
|
|
3687
|
+
if (resp?.notification_type && String(resp.notification_type).toUpperCase() !== "SUCCESS") {
|
|
3688
|
+
setError(String(resp.message || "Error al calcular costo"));
|
|
3689
|
+
return;
|
|
3690
|
+
}
|
|
3691
|
+
const responseData = resp?.response;
|
|
3692
|
+
const totalField = field.endpoint.total_field || "total_cost";
|
|
3693
|
+
const cost = Number(responseData?.[totalField] ?? resp?.[totalField] ?? 0);
|
|
3694
|
+
setTotalCost(cost);
|
|
3695
|
+
if (onCostResolved) {
|
|
3696
|
+
try {
|
|
3697
|
+
const formatted = new Intl.NumberFormat("es-CO", { style: "currency", currency: "COP" }).format(cost);
|
|
3698
|
+
onCostResolved(field.name, formatted);
|
|
3699
|
+
} catch {
|
|
3700
|
+
onCostResolved(field.name, `$${cost}`);
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
}).catch((err) => {
|
|
3704
|
+
setError(err instanceof Error ? err.message : "Error al calcular costo");
|
|
3705
|
+
}).finally(() => setLoading(false));
|
|
3706
|
+
}, [field.endpoint, eventBus]);
|
|
3707
|
+
const handleCancel = () => {
|
|
3708
|
+
if (field.on_cancel && eventBus) {
|
|
3709
|
+
if (field.on_cancel.type === "system_message") {
|
|
3710
|
+
eventBus.emit(CHAT_EVENT.FORM_TRACE, {
|
|
3711
|
+
chatId: "",
|
|
3712
|
+
formTitle: "",
|
|
3713
|
+
source: "reactive_form",
|
|
3714
|
+
phase: "cancel",
|
|
3715
|
+
data: { message: field.on_cancel.message },
|
|
3716
|
+
service: "",
|
|
3717
|
+
url: "",
|
|
3718
|
+
method: ""
|
|
3719
|
+
});
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
onCancel();
|
|
3723
|
+
};
|
|
3724
|
+
if (loading) {
|
|
3725
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__cost-summary px-chat-rf__cost-summary--loading", children: [
|
|
3726
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-rf__cost-summary__title", children: field.label || "Resumen de costos" }),
|
|
3727
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__cost-summary__loader", children: [
|
|
3728
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-rf__spinner" }),
|
|
3729
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Calculando costo..." })
|
|
3730
|
+
] })
|
|
3731
|
+
] });
|
|
3732
|
+
}
|
|
3733
|
+
if (error) {
|
|
3734
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__cost-summary px-chat-rf__cost-summary--error", children: [
|
|
3735
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-rf__cost-summary__title", children: field.label || "Resumen de costos" }),
|
|
3736
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-rf__cost-summary__error", children: error }),
|
|
3737
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "px-chat-rf__btn px-chat-rf__btn--secondary", onClick: handleCancel, children: "Cancelar" })
|
|
3738
|
+
] });
|
|
3739
|
+
}
|
|
3740
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__cost-summary", children: [
|
|
3741
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-rf__cost-summary__title", children: field.label || "Resumen de costos" }),
|
|
3742
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-rf__cost-summary__items", children: selectedItems.map((item, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__cost-summary__item", children: [
|
|
3743
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-rf__cost-summary__item-label", children: item.label }),
|
|
3744
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChatCurrencyDisplay, { value: item.value, size: "sm" })
|
|
3745
|
+
] }, i)) }),
|
|
3746
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-rf__cost-summary__divider" }),
|
|
3747
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__cost-summary__total", children: [
|
|
3748
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-rf__cost-summary__total-label", children: "Total" }),
|
|
3749
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChatCurrencyDisplay, { value: totalCost, size: "lg" })
|
|
3750
|
+
] }),
|
|
3751
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-rf__cost-summary__actions", children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "px-chat-rf__btn px-chat-rf__btn--secondary", onClick: handleCancel, children: "Cancelar" }) })
|
|
3752
|
+
] });
|
|
3753
|
+
};
|
|
3754
|
+
|
|
3380
3755
|
const checkIcon = /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2.5 6L5 8.5L9.5 3.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
3381
3756
|
const calendarIcon = /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
|
|
3382
3757
|
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2", y: "3", width: "12", height: "11", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" }),
|
|
@@ -3392,7 +3767,7 @@ const formIcon = /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "
|
|
|
3392
3767
|
] });
|
|
3393
3768
|
const chevronDown = /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 4.5L6 7.5L9 4.5", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
3394
3769
|
const chevronRight = /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4.5 3L7.5 6L4.5 9", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
3395
|
-
const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form, drawerPlacement = "bottom" }) => {
|
|
3770
|
+
const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form, drawerPlacement = "bottom", eventBus }) => {
|
|
3396
3771
|
const {
|
|
3397
3772
|
formStatus,
|
|
3398
3773
|
setFormStatus,
|
|
@@ -3408,6 +3783,7 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
|
|
|
3408
3783
|
handleNext,
|
|
3409
3784
|
handleBack,
|
|
3410
3785
|
handleSubmit,
|
|
3786
|
+
isSubmitting,
|
|
3411
3787
|
getDependencies,
|
|
3412
3788
|
allDepsResolved,
|
|
3413
3789
|
resolveSummaryRows
|
|
@@ -3467,11 +3843,25 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
|
|
|
3467
3843
|
const renderSummaryActions = () => {
|
|
3468
3844
|
const actions = summary.actions;
|
|
3469
3845
|
if (!actions || actions.length === 0) {
|
|
3470
|
-
return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "px-chat-rf__btn px-chat-rf__btn--primary px-chat-rf__btn--full", onClick: handleSubmit, children: submit.label });
|
|
3846
|
+
return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "px-chat-rf__btn px-chat-rf__btn--primary px-chat-rf__btn--full", onClick: handleSubmit, disabled: isSubmitting, children: isSubmitting ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-rf__spinner" }) : submit.label });
|
|
3471
3847
|
}
|
|
3472
3848
|
const actionHandlers = {
|
|
3473
3849
|
submit: handleSubmit,
|
|
3474
|
-
close: () =>
|
|
3850
|
+
close: () => {
|
|
3851
|
+
if (eventBus) {
|
|
3852
|
+
eventBus.emit(CHAT_EVENT.FORM_TRACE, {
|
|
3853
|
+
chatId: "",
|
|
3854
|
+
formTitle: "",
|
|
3855
|
+
source: "reactive_form",
|
|
3856
|
+
phase: "cancel",
|
|
3857
|
+
data: { message: "El usuario cancel\xF3 el agendamiento de la cita." },
|
|
3858
|
+
service: "",
|
|
3859
|
+
url: "",
|
|
3860
|
+
method: ""
|
|
3861
|
+
});
|
|
3862
|
+
}
|
|
3863
|
+
setFormStatus("closed");
|
|
3864
|
+
},
|
|
3475
3865
|
back: handleBack
|
|
3476
3866
|
};
|
|
3477
3867
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-rf__nav", children: actions.map((act, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3481,7 +3871,8 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
|
|
|
3481
3871
|
className: `px-chat-rf__btn px-chat-rf__btn--${act.variant || "primary"}`,
|
|
3482
3872
|
onClick: actionHandlers[act.action] || (() => {
|
|
3483
3873
|
}),
|
|
3484
|
-
|
|
3874
|
+
disabled: act.action === "submit" && isSubmitting,
|
|
3875
|
+
children: act.action === "submit" && isSubmitting ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-rf__spinner" }) : act.label
|
|
3485
3876
|
},
|
|
3486
3877
|
i
|
|
3487
3878
|
)) });
|
|
@@ -3514,8 +3905,15 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
|
|
|
3514
3905
|
)
|
|
3515
3906
|
] }),
|
|
3516
3907
|
prevStep.fields.map((fname) => {
|
|
3517
|
-
const fs = fieldStates[fname];
|
|
3518
3908
|
const fld = fields.find((f) => f.name === fname);
|
|
3909
|
+
if (fld?.type === "cost_summary") {
|
|
3910
|
+
const costLabel = fieldStates[fname]?.label;
|
|
3911
|
+
return costLabel ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__completed-field", children: [
|
|
3912
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-rf__completed-field-label", children: "Total:" }),
|
|
3913
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-rf__completed-field-value", children: costLabel })
|
|
3914
|
+
] }, fname) : null;
|
|
3915
|
+
}
|
|
3916
|
+
const fs = fieldStates[fname];
|
|
3519
3917
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__completed-field", children: [
|
|
3520
3918
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-chat-rf__completed-field-label", children: [
|
|
3521
3919
|
fld?.label,
|
|
@@ -3528,6 +3926,20 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
|
|
|
3528
3926
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__active-step", children: [
|
|
3529
3927
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-rf__step-title", children: step.title }),
|
|
3530
3928
|
stepFields.map((field) => {
|
|
3929
|
+
if (field.type === "cost_summary") {
|
|
3930
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3931
|
+
CostSummaryField,
|
|
3932
|
+
{
|
|
3933
|
+
field,
|
|
3934
|
+
values,
|
|
3935
|
+
fieldStates,
|
|
3936
|
+
eventBus,
|
|
3937
|
+
onCancel: () => setFormStatus("closed"),
|
|
3938
|
+
onCostResolved: (name, label) => handleFieldChange(name, label, label)
|
|
3939
|
+
},
|
|
3940
|
+
field.name
|
|
3941
|
+
);
|
|
3942
|
+
}
|
|
3531
3943
|
const deps = getDependencies(field);
|
|
3532
3944
|
const isDisabled = !allDepsResolved(deps, values);
|
|
3533
3945
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3623,89 +4035,6 @@ function buildInitialValues(fields) {
|
|
|
3623
4035
|
return values;
|
|
3624
4036
|
}
|
|
3625
4037
|
|
|
3626
|
-
function resolveVariable(template, formValues, fieldStates) {
|
|
3627
|
-
if (!template.startsWith("$")) return template;
|
|
3628
|
-
const withoutDollar = template.slice(1);
|
|
3629
|
-
const dotIndex = withoutDollar.indexOf(".");
|
|
3630
|
-
const fieldName = dotIndex >= 0 ? withoutDollar.slice(0, dotIndex) : withoutDollar;
|
|
3631
|
-
const prop = dotIndex >= 0 ? withoutDollar.slice(dotIndex + 1) : null;
|
|
3632
|
-
const value = formValues[fieldName];
|
|
3633
|
-
const state = fieldStates[fieldName];
|
|
3634
|
-
if (!prop) return value;
|
|
3635
|
-
if (prop === "label" && state) {
|
|
3636
|
-
return state.label || "";
|
|
3637
|
-
}
|
|
3638
|
-
if (prop === "labels" && state && Array.isArray(value)) {
|
|
3639
|
-
return value.map((v) => {
|
|
3640
|
-
const opt = state.options.find((o) => o.value === v);
|
|
3641
|
-
return opt?.label || v;
|
|
3642
|
-
}).join(", ");
|
|
3643
|
-
}
|
|
3644
|
-
if (state) {
|
|
3645
|
-
const selectedOpt = state.options.find((o) => o.value === value);
|
|
3646
|
-
if (selectedOpt && prop in selectedOpt) {
|
|
3647
|
-
return selectedOpt[prop];
|
|
3648
|
-
}
|
|
3649
|
-
}
|
|
3650
|
-
return "";
|
|
3651
|
-
}
|
|
3652
|
-
function resolveBody(obj, formValues, fieldStates) {
|
|
3653
|
-
if (typeof obj === "string") return resolveVariable(obj, formValues, fieldStates);
|
|
3654
|
-
if (Array.isArray(obj)) return obj.map((item) => resolveBody(item, formValues, fieldStates));
|
|
3655
|
-
if (obj !== null && typeof obj === "object") {
|
|
3656
|
-
const resolved = {};
|
|
3657
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
3658
|
-
resolved[key] = resolveBody(value, formValues, fieldStates);
|
|
3659
|
-
}
|
|
3660
|
-
return resolved;
|
|
3661
|
-
}
|
|
3662
|
-
return obj;
|
|
3663
|
-
}
|
|
3664
|
-
function resolveParams(params, formValues, fieldStates) {
|
|
3665
|
-
const resolved = {};
|
|
3666
|
-
for (const [key, template] of Object.entries(params)) {
|
|
3667
|
-
resolved[key] = resolveVariable(template, formValues, fieldStates);
|
|
3668
|
-
}
|
|
3669
|
-
return resolved;
|
|
3670
|
-
}
|
|
3671
|
-
function resolveSummaryRows(rows, formValues, fieldStates) {
|
|
3672
|
-
return rows.map((row) => ({
|
|
3673
|
-
label: row.label,
|
|
3674
|
-
value: String(resolveVariable(row.value, formValues, fieldStates) ?? "")
|
|
3675
|
-
}));
|
|
3676
|
-
}
|
|
3677
|
-
|
|
3678
|
-
const FETCH_TIMEOUT_MS = 12e4;
|
|
3679
|
-
function fetchViaEventBus(eventBus, service, url, method, body) {
|
|
3680
|
-
const requestId = globalThis.crypto?.randomUUID?.() ?? "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
3681
|
-
const r = Math.random() * 16 | 0;
|
|
3682
|
-
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
3683
|
-
});
|
|
3684
|
-
return new Promise((resolve, reject) => {
|
|
3685
|
-
const timeoutId = setTimeout(() => {
|
|
3686
|
-
unsubscribe();
|
|
3687
|
-
reject(new Error(`Timeout: no response for ${url} after ${FETCH_TIMEOUT_MS}ms`));
|
|
3688
|
-
}, FETCH_TIMEOUT_MS);
|
|
3689
|
-
const unsubscribe = eventBus.on(CHAT_EVENT.FETCH_RESPONSE, (response) => {
|
|
3690
|
-
if (response.requestId !== requestId) return;
|
|
3691
|
-
clearTimeout(timeoutId);
|
|
3692
|
-
unsubscribe();
|
|
3693
|
-
if (response.httpError) {
|
|
3694
|
-
reject(new Error(response.httpError));
|
|
3695
|
-
} else {
|
|
3696
|
-
resolve(response.data);
|
|
3697
|
-
}
|
|
3698
|
-
});
|
|
3699
|
-
eventBus.emit(CHAT_EVENT.FETCH_REQUEST, {
|
|
3700
|
-
requestId,
|
|
3701
|
-
service,
|
|
3702
|
-
url,
|
|
3703
|
-
method,
|
|
3704
|
-
...body ? { body } : {}
|
|
3705
|
-
});
|
|
3706
|
-
});
|
|
3707
|
-
}
|
|
3708
|
-
|
|
3709
4038
|
function formatTime$1(value, timezone) {
|
|
3710
4039
|
try {
|
|
3711
4040
|
const d = new Date(value);
|
|
@@ -3713,7 +4042,7 @@ function formatTime$1(value, timezone) {
|
|
|
3713
4042
|
return d.toLocaleTimeString([], {
|
|
3714
4043
|
hour: "2-digit",
|
|
3715
4044
|
minute: "2-digit",
|
|
3716
|
-
hour12:
|
|
4045
|
+
hour12: true,
|
|
3717
4046
|
...timezone ? { timeZone: timezone } : {}
|
|
3718
4047
|
});
|
|
3719
4048
|
} catch {
|
|
@@ -4075,7 +4404,10 @@ function useReactiveForm(props) {
|
|
|
4075
4404
|
};
|
|
4076
4405
|
const handleBack = () => setCurrentStep((s) => s - 1);
|
|
4077
4406
|
const [submitMessage, setSubmitMessage] = React.useState("");
|
|
4407
|
+
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
4078
4408
|
const handleSubmit = async () => {
|
|
4409
|
+
if (isSubmitting) return;
|
|
4410
|
+
setIsSubmitting(true);
|
|
4079
4411
|
const resolvedPayload = resolveBody(submit.payload_map, values, fieldStates);
|
|
4080
4412
|
if (eventBus) {
|
|
4081
4413
|
try {
|
|
@@ -4098,7 +4430,8 @@ function useReactiveForm(props) {
|
|
|
4098
4430
|
submitUrl: submit.url,
|
|
4099
4431
|
result: data,
|
|
4100
4432
|
formValues: values,
|
|
4101
|
-
formLabels
|
|
4433
|
+
formLabels,
|
|
4434
|
+
onSuccess: submit.on_success
|
|
4102
4435
|
});
|
|
4103
4436
|
if (notificationType === "SUCCESS") {
|
|
4104
4437
|
setSubmitMessage(message || "Completado");
|
|
@@ -4114,6 +4447,7 @@ function useReactiveForm(props) {
|
|
|
4114
4447
|
const message = err instanceof Error ? err.message : "Error al enviar formulario";
|
|
4115
4448
|
setSubmitMessage(message);
|
|
4116
4449
|
setFormStatus("error");
|
|
4450
|
+
setIsSubmitting(false);
|
|
4117
4451
|
}
|
|
4118
4452
|
} else {
|
|
4119
4453
|
console.log("[ReactiveForm] Submit:", values);
|
|
@@ -4142,6 +4476,7 @@ function useReactiveForm(props) {
|
|
|
4142
4476
|
handleNext,
|
|
4143
4477
|
handleBack,
|
|
4144
4478
|
handleSubmit,
|
|
4479
|
+
isSubmitting,
|
|
4145
4480
|
getDependencies,
|
|
4146
4481
|
allDepsResolved,
|
|
4147
4482
|
resolveSummaryRows: resolveRows
|
|
@@ -4173,7 +4508,8 @@ const ReactiveForm = ({ component, eventBus }) => {
|
|
|
4173
4508
|
{
|
|
4174
4509
|
...props,
|
|
4175
4510
|
form,
|
|
4176
|
-
drawerPlacement: isMobile ? "bottom" : "right"
|
|
4511
|
+
drawerPlacement: isMobile ? "bottom" : "right",
|
|
4512
|
+
eventBus
|
|
4177
4513
|
}
|
|
4178
4514
|
);
|
|
4179
4515
|
};
|
|
@@ -4839,7 +5175,15 @@ const COMPONENT_MAP = {
|
|
|
4839
5175
|
};
|
|
4840
5176
|
function renderComponent(props) {
|
|
4841
5177
|
const { component } = props;
|
|
4842
|
-
if (component.
|
|
5178
|
+
if (component.status) {
|
|
5179
|
+
if (TERMINAL_STATUS.has(component.status)) {
|
|
5180
|
+
const policy = component.visibility_policy ?? "hide_on_terminal";
|
|
5181
|
+
if (policy === "hide_on_terminal") return null;
|
|
5182
|
+
if (component.selected_values && component.selected_values.length > 0) {
|
|
5183
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AnsweredSummary, { component });
|
|
5184
|
+
}
|
|
5185
|
+
}
|
|
5186
|
+
} else if (component.answered && !DISPLAY_COMPONENTS.has(component.type)) {
|
|
4843
5187
|
return null;
|
|
4844
5188
|
}
|
|
4845
5189
|
const Component = COMPONENT_MAP[component.type];
|