pixelon 0.3.26 → 0.3.28
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/core/chat/ChatConfigContext.d.ts +8 -0
- package/dist/core/eventBus/chatEventBus.d.ts +6 -1
- package/dist/index.esm.js +376 -93
- package/dist/index.js +376 -93
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1832,6 +1832,114 @@ function useComponentInteraction({
|
|
|
1832
1832
|
return { emitInteraction, isConfirmed };
|
|
1833
1833
|
}
|
|
1834
1834
|
|
|
1835
|
+
const FETCH_TIMEOUT_MS = 12e4;
|
|
1836
|
+
function fetchViaEventBus(eventBus, service, url, method, body) {
|
|
1837
|
+
const requestId = globalThis.crypto?.randomUUID?.() ?? "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
1838
|
+
const r = Math.random() * 16 | 0;
|
|
1839
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
1840
|
+
});
|
|
1841
|
+
return new Promise((resolve, reject) => {
|
|
1842
|
+
const timeoutId = setTimeout(() => {
|
|
1843
|
+
unsubscribe();
|
|
1844
|
+
reject(new Error(`Timeout: no response for ${url} after ${FETCH_TIMEOUT_MS}ms`));
|
|
1845
|
+
}, FETCH_TIMEOUT_MS);
|
|
1846
|
+
const unsubscribe = eventBus.on(CHAT_EVENT.FETCH_RESPONSE, (response) => {
|
|
1847
|
+
if (response.requestId !== requestId) return;
|
|
1848
|
+
clearTimeout(timeoutId);
|
|
1849
|
+
unsubscribe();
|
|
1850
|
+
if (response.httpError) {
|
|
1851
|
+
reject(new Error(response.httpError));
|
|
1852
|
+
} else {
|
|
1853
|
+
resolve(response.data);
|
|
1854
|
+
}
|
|
1855
|
+
});
|
|
1856
|
+
eventBus.emit(CHAT_EVENT.FETCH_REQUEST, {
|
|
1857
|
+
requestId,
|
|
1858
|
+
service,
|
|
1859
|
+
url,
|
|
1860
|
+
method,
|
|
1861
|
+
...body ? { body } : {}
|
|
1862
|
+
});
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
function mapResponseToOptions(data, endpoint) {
|
|
1867
|
+
let items = [];
|
|
1868
|
+
if (endpoint.response_path) {
|
|
1869
|
+
const parts = endpoint.response_path.split(".");
|
|
1870
|
+
let current = data;
|
|
1871
|
+
for (const part of parts) {
|
|
1872
|
+
if (current != null && typeof current === "object") {
|
|
1873
|
+
current = current[part];
|
|
1874
|
+
} else {
|
|
1875
|
+
return [];
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
items = Array.isArray(current) ? current : [];
|
|
1879
|
+
} else if (Array.isArray(data)) {
|
|
1880
|
+
items = data;
|
|
1881
|
+
} else {
|
|
1882
|
+
return [];
|
|
1883
|
+
}
|
|
1884
|
+
return items.filter((item) => item != null && typeof item === "object").map((item) => ({
|
|
1885
|
+
value: String(item[endpoint.value_field] ?? ""),
|
|
1886
|
+
label: String(item[endpoint.label_field] ?? "")
|
|
1887
|
+
}));
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
function normalizeBody(body) {
|
|
1891
|
+
if (!body) return void 0;
|
|
1892
|
+
const normalized = { ...body };
|
|
1893
|
+
for (const [key, val] of Object.entries(normalized)) {
|
|
1894
|
+
if (/s_id$/.test(key) && typeof val === "string") {
|
|
1895
|
+
normalized[key] = [val];
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
return normalized;
|
|
1899
|
+
}
|
|
1900
|
+
function useSelectEndpoint(endpoint, eventBus) {
|
|
1901
|
+
const [options, setOptions] = React.useState([]);
|
|
1902
|
+
const [status, setStatus] = React.useState(endpoint ? "loading" : "idle");
|
|
1903
|
+
const [errorMessage, setErrorMessage] = React.useState("");
|
|
1904
|
+
const fetchedRef = React.useRef(false);
|
|
1905
|
+
const endpointRef = React.useRef(endpoint);
|
|
1906
|
+
endpointRef.current = endpoint;
|
|
1907
|
+
const doFetch = React.useCallback(async () => {
|
|
1908
|
+
const ep = endpointRef.current;
|
|
1909
|
+
if (!ep || fetchedRef.current) return;
|
|
1910
|
+
fetchedRef.current = true;
|
|
1911
|
+
if (!eventBus) {
|
|
1912
|
+
setOptions([
|
|
1913
|
+
{ value: "mock-1", label: "Mock Option 1" },
|
|
1914
|
+
{ value: "mock-2", label: "Mock Option 2" }
|
|
1915
|
+
]);
|
|
1916
|
+
setStatus("idle");
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
try {
|
|
1920
|
+
const body = normalizeBody(ep.body);
|
|
1921
|
+
if (ep.method.toUpperCase() === "POST" && !body) {
|
|
1922
|
+
setErrorMessage("Faltan par\xE1metros para cargar opciones");
|
|
1923
|
+
setStatus("error");
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1926
|
+
const data = await fetchViaEventBus(eventBus, ep.service, ep.url, ep.method, body);
|
|
1927
|
+
const mapped = mapResponseToOptions(data, ep);
|
|
1928
|
+
setOptions(mapped);
|
|
1929
|
+
setStatus("idle");
|
|
1930
|
+
} catch (err) {
|
|
1931
|
+
setErrorMessage(err instanceof Error ? err.message : "Error al cargar opciones");
|
|
1932
|
+
setStatus("error");
|
|
1933
|
+
}
|
|
1934
|
+
}, [eventBus]);
|
|
1935
|
+
React.useEffect(() => {
|
|
1936
|
+
if (endpoint && !fetchedRef.current) {
|
|
1937
|
+
doFetch();
|
|
1938
|
+
}
|
|
1939
|
+
}, [endpoint, doFetch]);
|
|
1940
|
+
return { options, status, errorMessage };
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1835
1943
|
const MOBILE_BREAKPOINT$4 = 640;
|
|
1836
1944
|
const useIsMobile$5 = () => {
|
|
1837
1945
|
const [isMobile, setIsMobile] = React.useState(
|
|
@@ -1855,10 +1963,15 @@ const SelectWithConfirm = ({
|
|
|
1855
1963
|
const [searchValue, setSearchValue] = React.useState("");
|
|
1856
1964
|
const searchInputRef = React.useRef(null);
|
|
1857
1965
|
const isMobile = useIsMobile$5();
|
|
1858
|
-
const { field_name, value, ...componentProps } = component.props;
|
|
1859
|
-
const
|
|
1966
|
+
const { field_name, value, endpoint: endpointProp, ...componentProps } = component.props;
|
|
1967
|
+
const endpoint = endpointProp;
|
|
1968
|
+
const { options: endpointOptions, status: endpointStatus, errorMessage } = useSelectEndpoint(endpoint, eventBus);
|
|
1969
|
+
const staticOptions = componentProps.options || [];
|
|
1970
|
+
const options = endpoint ? endpointOptions : staticOptions;
|
|
1860
1971
|
const placeholder = componentProps.placeholder || "Seleccionar...";
|
|
1861
1972
|
const label = componentProps.label || "";
|
|
1973
|
+
const isLoading = endpointStatus === "loading";
|
|
1974
|
+
const isError = endpointStatus === "error";
|
|
1862
1975
|
const { emitInteraction, isConfirmed } = useComponentInteraction({
|
|
1863
1976
|
eventBus,
|
|
1864
1977
|
fieldName: component.props.field_name,
|
|
@@ -1878,6 +1991,18 @@ const SelectWithConfirm = ({
|
|
|
1878
1991
|
}
|
|
1879
1992
|
}, [isDrawerOpen]);
|
|
1880
1993
|
if (isConfirmed) return null;
|
|
1994
|
+
if (isLoading) {
|
|
1995
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-select-with-confirm", children: [
|
|
1996
|
+
label && /* @__PURE__ */ jsxRuntime.jsx("label", { className: "px-chat-select-with-confirm__label", children: label }),
|
|
1997
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-select-with-confirm__loading", children: "Cargando opciones..." })
|
|
1998
|
+
] });
|
|
1999
|
+
}
|
|
2000
|
+
if (isError) {
|
|
2001
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-select-with-confirm", children: [
|
|
2002
|
+
label && /* @__PURE__ */ jsxRuntime.jsx("label", { className: "px-chat-select-with-confirm__label", children: label }),
|
|
2003
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-select-with-confirm__error", children: errorMessage })
|
|
2004
|
+
] });
|
|
2005
|
+
}
|
|
1881
2006
|
if (isMobile) {
|
|
1882
2007
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-select-with-confirm", children: [
|
|
1883
2008
|
label && /* @__PURE__ */ jsxRuntime.jsx("label", { className: "px-chat-select-with-confirm__label", children: label }),
|
|
@@ -1997,8 +2122,8 @@ const SelectWithConfirm = ({
|
|
|
1997
2122
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1998
2123
|
Select$2,
|
|
1999
2124
|
{
|
|
2000
|
-
options: [],
|
|
2001
2125
|
...componentProps,
|
|
2126
|
+
options,
|
|
2002
2127
|
variant: "chat",
|
|
2003
2128
|
mode: "single",
|
|
2004
2129
|
showSearch: true,
|
|
@@ -3158,7 +3283,7 @@ const SelectField$1 = ({ field, value, disabled, loading, options, onChange, dra
|
|
|
3158
3283
|
),
|
|
3159
3284
|
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
3285
|
] }),
|
|
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) => {
|
|
3286
|
+
/* @__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
3287
|
const isSelected = opt.value === value;
|
|
3163
3288
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3164
3289
|
"li",
|
|
@@ -3279,7 +3404,7 @@ const SelectMultipleField = ({ field, value, disabled, loading, options, onChang
|
|
|
3279
3404
|
),
|
|
3280
3405
|
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
3406
|
] }),
|
|
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) => {
|
|
3407
|
+
/* @__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
3408
|
const isChecked = selected.includes(opt.value);
|
|
3284
3409
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3285
3410
|
"li",
|
|
@@ -3377,6 +3502,200 @@ const DateField = ({ field, value, disabled, onChange, drawerPlacement }) => {
|
|
|
3377
3502
|
] });
|
|
3378
3503
|
};
|
|
3379
3504
|
|
|
3505
|
+
const PREFIX$3 = "px-chat-currency";
|
|
3506
|
+
const ChatCurrencyDisplay = ({
|
|
3507
|
+
value,
|
|
3508
|
+
currencyCode,
|
|
3509
|
+
currencySymbol,
|
|
3510
|
+
showCode = true,
|
|
3511
|
+
size = "md",
|
|
3512
|
+
className
|
|
3513
|
+
}) => {
|
|
3514
|
+
const { locale, currency: configCurrency } = useChatConfig();
|
|
3515
|
+
const code = currencyCode || configCurrency?.code || "COP";
|
|
3516
|
+
const symbol = currencySymbol || configCurrency?.symbol || "$";
|
|
3517
|
+
const formatted = React.useMemo(() => {
|
|
3518
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
3519
|
+
const num = typeof value === "string" ? parseFloat(value) : value;
|
|
3520
|
+
if (isNaN(num)) return null;
|
|
3521
|
+
try {
|
|
3522
|
+
return new Intl.NumberFormat(locale || "es-CO", {
|
|
3523
|
+
minimumFractionDigits: 2,
|
|
3524
|
+
maximumFractionDigits: 2
|
|
3525
|
+
}).format(num);
|
|
3526
|
+
} catch {
|
|
3527
|
+
return num.toFixed(2);
|
|
3528
|
+
}
|
|
3529
|
+
}, [value, locale]);
|
|
3530
|
+
if (!formatted) return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `${PREFIX$3} ${PREFIX$3}--empty`, children: "-" });
|
|
3531
|
+
const classes = [PREFIX$3, `${PREFIX$3}--${size}`, className].filter(Boolean).join(" ");
|
|
3532
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: classes, children: [
|
|
3533
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `${PREFIX$3}__symbol`, children: symbol }),
|
|
3534
|
+
" ",
|
|
3535
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `${PREFIX$3}__value`, children: formatted }),
|
|
3536
|
+
showCode && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3537
|
+
" ",
|
|
3538
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `${PREFIX$3}__code`, children: code })
|
|
3539
|
+
] })
|
|
3540
|
+
] });
|
|
3541
|
+
};
|
|
3542
|
+
ChatCurrencyDisplay.displayName = "ChatCurrencyDisplay";
|
|
3543
|
+
|
|
3544
|
+
function resolveVariable(template, formValues, fieldStates) {
|
|
3545
|
+
if (!template.startsWith("$")) return template;
|
|
3546
|
+
const withoutDollar = template.slice(1);
|
|
3547
|
+
const dotIndex = withoutDollar.indexOf(".");
|
|
3548
|
+
const fieldName = dotIndex >= 0 ? withoutDollar.slice(0, dotIndex) : withoutDollar;
|
|
3549
|
+
const prop = dotIndex >= 0 ? withoutDollar.slice(dotIndex + 1) : null;
|
|
3550
|
+
const value = formValues[fieldName];
|
|
3551
|
+
const state = fieldStates[fieldName];
|
|
3552
|
+
if (!prop) return value;
|
|
3553
|
+
if (prop === "label" && state) {
|
|
3554
|
+
return state.label || "";
|
|
3555
|
+
}
|
|
3556
|
+
if (prop === "labels" && state && Array.isArray(value)) {
|
|
3557
|
+
return value.map((v) => {
|
|
3558
|
+
const opt = state.options.find((o) => o.value === v);
|
|
3559
|
+
return opt?.label || v;
|
|
3560
|
+
}).join(", ");
|
|
3561
|
+
}
|
|
3562
|
+
if (state) {
|
|
3563
|
+
const selectedOpt = state.options.find((o) => o.value === value);
|
|
3564
|
+
if (selectedOpt && prop in selectedOpt) {
|
|
3565
|
+
return selectedOpt[prop];
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
return "";
|
|
3569
|
+
}
|
|
3570
|
+
function resolveBody(obj, formValues, fieldStates) {
|
|
3571
|
+
if (typeof obj === "string") return resolveVariable(obj, formValues, fieldStates);
|
|
3572
|
+
if (Array.isArray(obj)) return obj.map((item) => resolveBody(item, formValues, fieldStates));
|
|
3573
|
+
if (obj !== null && typeof obj === "object") {
|
|
3574
|
+
const resolved = {};
|
|
3575
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
3576
|
+
resolved[key] = resolveBody(value, formValues, fieldStates);
|
|
3577
|
+
}
|
|
3578
|
+
return resolved;
|
|
3579
|
+
}
|
|
3580
|
+
return obj;
|
|
3581
|
+
}
|
|
3582
|
+
function resolveParams(params, formValues, fieldStates) {
|
|
3583
|
+
const resolved = {};
|
|
3584
|
+
for (const [key, template] of Object.entries(params)) {
|
|
3585
|
+
resolved[key] = resolveVariable(template, formValues, fieldStates);
|
|
3586
|
+
}
|
|
3587
|
+
return resolved;
|
|
3588
|
+
}
|
|
3589
|
+
function resolveSummaryRows(rows, formValues, fieldStates) {
|
|
3590
|
+
return rows.map((row) => ({
|
|
3591
|
+
label: row.label,
|
|
3592
|
+
value: String(resolveVariable(row.value, formValues, fieldStates) ?? "")
|
|
3593
|
+
}));
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
const CostSummaryField = ({
|
|
3597
|
+
field,
|
|
3598
|
+
values,
|
|
3599
|
+
fieldStates,
|
|
3600
|
+
onCostResolved,
|
|
3601
|
+
eventBus,
|
|
3602
|
+
onCancel
|
|
3603
|
+
}) => {
|
|
3604
|
+
const [totalCost, setTotalCost] = React.useState(null);
|
|
3605
|
+
const [loading, setLoading] = React.useState(true);
|
|
3606
|
+
const [error, setError] = React.useState("");
|
|
3607
|
+
const selectedItems = React.useMemo(() => {
|
|
3608
|
+
if (!field.items_from) return [];
|
|
3609
|
+
const { source_field, label_key, value_key } = field.items_from;
|
|
3610
|
+
const sourceState = fieldStates[source_field];
|
|
3611
|
+
const selectedValues = values[source_field];
|
|
3612
|
+
if (!sourceState?.options || !selectedValues) return [];
|
|
3613
|
+
const selectedArr = Array.isArray(selectedValues) ? selectedValues : [selectedValues];
|
|
3614
|
+
return sourceState.options.filter((opt) => selectedArr.includes(opt.value)).map((opt) => ({
|
|
3615
|
+
label: String(opt[label_key] || opt.label || ""),
|
|
3616
|
+
value: Number(opt[value_key] || 0)
|
|
3617
|
+
}));
|
|
3618
|
+
}, [field.items_from, fieldStates, values]);
|
|
3619
|
+
const fetchedRef = React.useRef(false);
|
|
3620
|
+
React.useEffect(() => {
|
|
3621
|
+
if (!field.endpoint || !eventBus || fetchedRef.current) {
|
|
3622
|
+
if (!field.endpoint || !eventBus) setLoading(false);
|
|
3623
|
+
return;
|
|
3624
|
+
}
|
|
3625
|
+
fetchedRef.current = true;
|
|
3626
|
+
const body = resolveBody(field.endpoint.body || {}, values, fieldStates);
|
|
3627
|
+
setLoading(true);
|
|
3628
|
+
setError("");
|
|
3629
|
+
fetchViaEventBus(eventBus, field.endpoint.service, field.endpoint.url, field.endpoint.method, body).then((data) => {
|
|
3630
|
+
const resp = data;
|
|
3631
|
+
if (resp?.notification_type && String(resp.notification_type).toUpperCase() !== "SUCCESS") {
|
|
3632
|
+
setError(String(resp.message || "Error al calcular costo"));
|
|
3633
|
+
return;
|
|
3634
|
+
}
|
|
3635
|
+
const responseData = resp?.response;
|
|
3636
|
+
const totalField = field.endpoint.total_field || "total_cost";
|
|
3637
|
+
const cost = Number(responseData?.[totalField] ?? resp?.[totalField] ?? 0);
|
|
3638
|
+
setTotalCost(cost);
|
|
3639
|
+
if (onCostResolved) {
|
|
3640
|
+
try {
|
|
3641
|
+
const formatted = new Intl.NumberFormat("es-CO", { style: "currency", currency: "COP" }).format(cost);
|
|
3642
|
+
onCostResolved(field.name, formatted);
|
|
3643
|
+
} catch {
|
|
3644
|
+
onCostResolved(field.name, `$${cost}`);
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
}).catch((err) => {
|
|
3648
|
+
setError(err instanceof Error ? err.message : "Error al calcular costo");
|
|
3649
|
+
}).finally(() => setLoading(false));
|
|
3650
|
+
}, [field.endpoint, eventBus]);
|
|
3651
|
+
const handleCancel = () => {
|
|
3652
|
+
if (field.on_cancel && eventBus) {
|
|
3653
|
+
if (field.on_cancel.type === "system_message") {
|
|
3654
|
+
eventBus.emit(CHAT_EVENT.FORM_TRACE, {
|
|
3655
|
+
chatId: "",
|
|
3656
|
+
formTitle: "",
|
|
3657
|
+
source: "reactive_form",
|
|
3658
|
+
phase: "cancel",
|
|
3659
|
+
data: { message: field.on_cancel.message },
|
|
3660
|
+
service: "",
|
|
3661
|
+
url: "",
|
|
3662
|
+
method: ""
|
|
3663
|
+
});
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
onCancel();
|
|
3667
|
+
};
|
|
3668
|
+
if (loading) {
|
|
3669
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__cost-summary px-chat-rf__cost-summary--loading", children: [
|
|
3670
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-rf__cost-summary__title", children: field.label || "Resumen de costos" }),
|
|
3671
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__cost-summary__loader", children: [
|
|
3672
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-rf__spinner" }),
|
|
3673
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Calculando costo..." })
|
|
3674
|
+
] })
|
|
3675
|
+
] });
|
|
3676
|
+
}
|
|
3677
|
+
if (error) {
|
|
3678
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__cost-summary px-chat-rf__cost-summary--error", children: [
|
|
3679
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-rf__cost-summary__title", children: field.label || "Resumen de costos" }),
|
|
3680
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-rf__cost-summary__error", children: error }),
|
|
3681
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "px-chat-rf__btn px-chat-rf__btn--secondary", onClick: handleCancel, children: "Cancelar" })
|
|
3682
|
+
] });
|
|
3683
|
+
}
|
|
3684
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__cost-summary", children: [
|
|
3685
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-rf__cost-summary__title", children: field.label || "Resumen de costos" }),
|
|
3686
|
+
/* @__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: [
|
|
3687
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-rf__cost-summary__item-label", children: item.label }),
|
|
3688
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChatCurrencyDisplay, { value: item.value, size: "sm" })
|
|
3689
|
+
] }, i)) }),
|
|
3690
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-rf__cost-summary__divider" }),
|
|
3691
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__cost-summary__total", children: [
|
|
3692
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-rf__cost-summary__total-label", children: "Total" }),
|
|
3693
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChatCurrencyDisplay, { value: totalCost, size: "lg" })
|
|
3694
|
+
] }),
|
|
3695
|
+
/* @__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" }) })
|
|
3696
|
+
] });
|
|
3697
|
+
};
|
|
3698
|
+
|
|
3380
3699
|
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
3700
|
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
3701
|
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2", y: "3", width: "12", height: "11", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" }),
|
|
@@ -3392,7 +3711,7 @@ const formIcon = /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "
|
|
|
3392
3711
|
] });
|
|
3393
3712
|
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
3713
|
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" }) => {
|
|
3714
|
+
const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form, drawerPlacement = "bottom", eventBus }) => {
|
|
3396
3715
|
const {
|
|
3397
3716
|
formStatus,
|
|
3398
3717
|
setFormStatus,
|
|
@@ -3408,6 +3727,7 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
|
|
|
3408
3727
|
handleNext,
|
|
3409
3728
|
handleBack,
|
|
3410
3729
|
handleSubmit,
|
|
3730
|
+
isSubmitting,
|
|
3411
3731
|
getDependencies,
|
|
3412
3732
|
allDepsResolved,
|
|
3413
3733
|
resolveSummaryRows
|
|
@@ -3467,11 +3787,25 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
|
|
|
3467
3787
|
const renderSummaryActions = () => {
|
|
3468
3788
|
const actions = summary.actions;
|
|
3469
3789
|
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 });
|
|
3790
|
+
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
3791
|
}
|
|
3472
3792
|
const actionHandlers = {
|
|
3473
3793
|
submit: handleSubmit,
|
|
3474
|
-
close: () =>
|
|
3794
|
+
close: () => {
|
|
3795
|
+
if (eventBus) {
|
|
3796
|
+
eventBus.emit(CHAT_EVENT.FORM_TRACE, {
|
|
3797
|
+
chatId: "",
|
|
3798
|
+
formTitle: "",
|
|
3799
|
+
source: "reactive_form",
|
|
3800
|
+
phase: "cancel",
|
|
3801
|
+
data: { message: "El usuario cancel\xF3 el agendamiento de la cita." },
|
|
3802
|
+
service: "",
|
|
3803
|
+
url: "",
|
|
3804
|
+
method: ""
|
|
3805
|
+
});
|
|
3806
|
+
}
|
|
3807
|
+
setFormStatus("closed");
|
|
3808
|
+
},
|
|
3475
3809
|
back: handleBack
|
|
3476
3810
|
};
|
|
3477
3811
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-rf__nav", children: actions.map((act, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3481,7 +3815,8 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
|
|
|
3481
3815
|
className: `px-chat-rf__btn px-chat-rf__btn--${act.variant || "primary"}`,
|
|
3482
3816
|
onClick: actionHandlers[act.action] || (() => {
|
|
3483
3817
|
}),
|
|
3484
|
-
|
|
3818
|
+
disabled: act.action === "submit" && isSubmitting,
|
|
3819
|
+
children: act.action === "submit" && isSubmitting ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-rf__spinner" }) : act.label
|
|
3485
3820
|
},
|
|
3486
3821
|
i
|
|
3487
3822
|
)) });
|
|
@@ -3514,8 +3849,15 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
|
|
|
3514
3849
|
)
|
|
3515
3850
|
] }),
|
|
3516
3851
|
prevStep.fields.map((fname) => {
|
|
3517
|
-
const fs = fieldStates[fname];
|
|
3518
3852
|
const fld = fields.find((f) => f.name === fname);
|
|
3853
|
+
if (fld?.type === "cost_summary") {
|
|
3854
|
+
const costLabel = fieldStates[fname]?.label;
|
|
3855
|
+
return costLabel ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__completed-field", children: [
|
|
3856
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-rf__completed-field-label", children: "Total:" }),
|
|
3857
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-chat-rf__completed-field-value", children: costLabel })
|
|
3858
|
+
] }, fname) : null;
|
|
3859
|
+
}
|
|
3860
|
+
const fs = fieldStates[fname];
|
|
3519
3861
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__completed-field", children: [
|
|
3520
3862
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-chat-rf__completed-field-label", children: [
|
|
3521
3863
|
fld?.label,
|
|
@@ -3528,6 +3870,20 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
|
|
|
3528
3870
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-chat-rf__active-step", children: [
|
|
3529
3871
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-chat-rf__step-title", children: step.title }),
|
|
3530
3872
|
stepFields.map((field) => {
|
|
3873
|
+
if (field.type === "cost_summary") {
|
|
3874
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3875
|
+
CostSummaryField,
|
|
3876
|
+
{
|
|
3877
|
+
field,
|
|
3878
|
+
values,
|
|
3879
|
+
fieldStates,
|
|
3880
|
+
eventBus,
|
|
3881
|
+
onCancel: () => setFormStatus("closed"),
|
|
3882
|
+
onCostResolved: (name, label) => handleFieldChange(name, label, label)
|
|
3883
|
+
},
|
|
3884
|
+
field.name
|
|
3885
|
+
);
|
|
3886
|
+
}
|
|
3531
3887
|
const deps = getDependencies(field);
|
|
3532
3888
|
const isDisabled = !allDepsResolved(deps, values);
|
|
3533
3889
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3623,86 +3979,6 @@ function buildInitialValues(fields) {
|
|
|
3623
3979
|
return values;
|
|
3624
3980
|
}
|
|
3625
3981
|
|
|
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 = crypto.randomUUID();
|
|
3681
|
-
return new Promise((resolve, reject) => {
|
|
3682
|
-
const timeoutId = setTimeout(() => {
|
|
3683
|
-
unsubscribe();
|
|
3684
|
-
reject(new Error(`Timeout: no response for ${url} after ${FETCH_TIMEOUT_MS}ms`));
|
|
3685
|
-
}, FETCH_TIMEOUT_MS);
|
|
3686
|
-
const unsubscribe = eventBus.on(CHAT_EVENT.FETCH_RESPONSE, (response) => {
|
|
3687
|
-
if (response.requestId !== requestId) return;
|
|
3688
|
-
clearTimeout(timeoutId);
|
|
3689
|
-
unsubscribe();
|
|
3690
|
-
if (response.httpError) {
|
|
3691
|
-
reject(new Error(response.httpError));
|
|
3692
|
-
} else {
|
|
3693
|
-
resolve(response.data);
|
|
3694
|
-
}
|
|
3695
|
-
});
|
|
3696
|
-
eventBus.emit(CHAT_EVENT.FETCH_REQUEST, {
|
|
3697
|
-
requestId,
|
|
3698
|
-
service,
|
|
3699
|
-
url,
|
|
3700
|
-
method,
|
|
3701
|
-
...body ? { body } : {}
|
|
3702
|
-
});
|
|
3703
|
-
});
|
|
3704
|
-
}
|
|
3705
|
-
|
|
3706
3982
|
function formatTime$1(value, timezone) {
|
|
3707
3983
|
try {
|
|
3708
3984
|
const d = new Date(value);
|
|
@@ -3710,7 +3986,7 @@ function formatTime$1(value, timezone) {
|
|
|
3710
3986
|
return d.toLocaleTimeString([], {
|
|
3711
3987
|
hour: "2-digit",
|
|
3712
3988
|
minute: "2-digit",
|
|
3713
|
-
hour12:
|
|
3989
|
+
hour12: true,
|
|
3714
3990
|
...timezone ? { timeZone: timezone } : {}
|
|
3715
3991
|
});
|
|
3716
3992
|
} catch {
|
|
@@ -4072,7 +4348,10 @@ function useReactiveForm(props) {
|
|
|
4072
4348
|
};
|
|
4073
4349
|
const handleBack = () => setCurrentStep((s) => s - 1);
|
|
4074
4350
|
const [submitMessage, setSubmitMessage] = React.useState("");
|
|
4351
|
+
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
4075
4352
|
const handleSubmit = async () => {
|
|
4353
|
+
if (isSubmitting) return;
|
|
4354
|
+
setIsSubmitting(true);
|
|
4076
4355
|
const resolvedPayload = resolveBody(submit.payload_map, values, fieldStates);
|
|
4077
4356
|
if (eventBus) {
|
|
4078
4357
|
try {
|
|
@@ -4095,7 +4374,8 @@ function useReactiveForm(props) {
|
|
|
4095
4374
|
submitUrl: submit.url,
|
|
4096
4375
|
result: data,
|
|
4097
4376
|
formValues: values,
|
|
4098
|
-
formLabels
|
|
4377
|
+
formLabels,
|
|
4378
|
+
onSuccess: submit.on_success
|
|
4099
4379
|
});
|
|
4100
4380
|
if (notificationType === "SUCCESS") {
|
|
4101
4381
|
setSubmitMessage(message || "Completado");
|
|
@@ -4111,6 +4391,7 @@ function useReactiveForm(props) {
|
|
|
4111
4391
|
const message = err instanceof Error ? err.message : "Error al enviar formulario";
|
|
4112
4392
|
setSubmitMessage(message);
|
|
4113
4393
|
setFormStatus("error");
|
|
4394
|
+
setIsSubmitting(false);
|
|
4114
4395
|
}
|
|
4115
4396
|
} else {
|
|
4116
4397
|
console.log("[ReactiveForm] Submit:", values);
|
|
@@ -4139,6 +4420,7 @@ function useReactiveForm(props) {
|
|
|
4139
4420
|
handleNext,
|
|
4140
4421
|
handleBack,
|
|
4141
4422
|
handleSubmit,
|
|
4423
|
+
isSubmitting,
|
|
4142
4424
|
getDependencies,
|
|
4143
4425
|
allDepsResolved,
|
|
4144
4426
|
resolveSummaryRows: resolveRows
|
|
@@ -4170,7 +4452,8 @@ const ReactiveForm = ({ component, eventBus }) => {
|
|
|
4170
4452
|
{
|
|
4171
4453
|
...props,
|
|
4172
4454
|
form,
|
|
4173
|
-
drawerPlacement: isMobile ? "bottom" : "right"
|
|
4455
|
+
drawerPlacement: isMobile ? "bottom" : "right",
|
|
4456
|
+
eventBus
|
|
4174
4457
|
}
|
|
4175
4458
|
);
|
|
4176
4459
|
};
|