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.esm.js
CHANGED
|
@@ -1811,6 +1811,114 @@ function useComponentInteraction({
|
|
|
1811
1811
|
return { emitInteraction, isConfirmed };
|
|
1812
1812
|
}
|
|
1813
1813
|
|
|
1814
|
+
const FETCH_TIMEOUT_MS = 12e4;
|
|
1815
|
+
function fetchViaEventBus(eventBus, service, url, method, body) {
|
|
1816
|
+
const requestId = globalThis.crypto?.randomUUID?.() ?? "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
1817
|
+
const r = Math.random() * 16 | 0;
|
|
1818
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
1819
|
+
});
|
|
1820
|
+
return new Promise((resolve, reject) => {
|
|
1821
|
+
const timeoutId = setTimeout(() => {
|
|
1822
|
+
unsubscribe();
|
|
1823
|
+
reject(new Error(`Timeout: no response for ${url} after ${FETCH_TIMEOUT_MS}ms`));
|
|
1824
|
+
}, FETCH_TIMEOUT_MS);
|
|
1825
|
+
const unsubscribe = eventBus.on(CHAT_EVENT.FETCH_RESPONSE, (response) => {
|
|
1826
|
+
if (response.requestId !== requestId) return;
|
|
1827
|
+
clearTimeout(timeoutId);
|
|
1828
|
+
unsubscribe();
|
|
1829
|
+
if (response.httpError) {
|
|
1830
|
+
reject(new Error(response.httpError));
|
|
1831
|
+
} else {
|
|
1832
|
+
resolve(response.data);
|
|
1833
|
+
}
|
|
1834
|
+
});
|
|
1835
|
+
eventBus.emit(CHAT_EVENT.FETCH_REQUEST, {
|
|
1836
|
+
requestId,
|
|
1837
|
+
service,
|
|
1838
|
+
url,
|
|
1839
|
+
method,
|
|
1840
|
+
...body ? { body } : {}
|
|
1841
|
+
});
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
function mapResponseToOptions(data, endpoint) {
|
|
1846
|
+
let items = [];
|
|
1847
|
+
if (endpoint.response_path) {
|
|
1848
|
+
const parts = endpoint.response_path.split(".");
|
|
1849
|
+
let current = data;
|
|
1850
|
+
for (const part of parts) {
|
|
1851
|
+
if (current != null && typeof current === "object") {
|
|
1852
|
+
current = current[part];
|
|
1853
|
+
} else {
|
|
1854
|
+
return [];
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
items = Array.isArray(current) ? current : [];
|
|
1858
|
+
} else if (Array.isArray(data)) {
|
|
1859
|
+
items = data;
|
|
1860
|
+
} else {
|
|
1861
|
+
return [];
|
|
1862
|
+
}
|
|
1863
|
+
return items.filter((item) => item != null && typeof item === "object").map((item) => ({
|
|
1864
|
+
value: String(item[endpoint.value_field] ?? ""),
|
|
1865
|
+
label: String(item[endpoint.label_field] ?? "")
|
|
1866
|
+
}));
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
function normalizeBody(body) {
|
|
1870
|
+
if (!body) return void 0;
|
|
1871
|
+
const normalized = { ...body };
|
|
1872
|
+
for (const [key, val] of Object.entries(normalized)) {
|
|
1873
|
+
if (/s_id$/.test(key) && typeof val === "string") {
|
|
1874
|
+
normalized[key] = [val];
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
return normalized;
|
|
1878
|
+
}
|
|
1879
|
+
function useSelectEndpoint(endpoint, eventBus) {
|
|
1880
|
+
const [options, setOptions] = useState([]);
|
|
1881
|
+
const [status, setStatus] = useState(endpoint ? "loading" : "idle");
|
|
1882
|
+
const [errorMessage, setErrorMessage] = useState("");
|
|
1883
|
+
const fetchedRef = useRef(false);
|
|
1884
|
+
const endpointRef = useRef(endpoint);
|
|
1885
|
+
endpointRef.current = endpoint;
|
|
1886
|
+
const doFetch = useCallback(async () => {
|
|
1887
|
+
const ep = endpointRef.current;
|
|
1888
|
+
if (!ep || fetchedRef.current) return;
|
|
1889
|
+
fetchedRef.current = true;
|
|
1890
|
+
if (!eventBus) {
|
|
1891
|
+
setOptions([
|
|
1892
|
+
{ value: "mock-1", label: "Mock Option 1" },
|
|
1893
|
+
{ value: "mock-2", label: "Mock Option 2" }
|
|
1894
|
+
]);
|
|
1895
|
+
setStatus("idle");
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
try {
|
|
1899
|
+
const body = normalizeBody(ep.body);
|
|
1900
|
+
if (ep.method.toUpperCase() === "POST" && !body) {
|
|
1901
|
+
setErrorMessage("Faltan par\xE1metros para cargar opciones");
|
|
1902
|
+
setStatus("error");
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
const data = await fetchViaEventBus(eventBus, ep.service, ep.url, ep.method, body);
|
|
1906
|
+
const mapped = mapResponseToOptions(data, ep);
|
|
1907
|
+
setOptions(mapped);
|
|
1908
|
+
setStatus("idle");
|
|
1909
|
+
} catch (err) {
|
|
1910
|
+
setErrorMessage(err instanceof Error ? err.message : "Error al cargar opciones");
|
|
1911
|
+
setStatus("error");
|
|
1912
|
+
}
|
|
1913
|
+
}, [eventBus]);
|
|
1914
|
+
useEffect(() => {
|
|
1915
|
+
if (endpoint && !fetchedRef.current) {
|
|
1916
|
+
doFetch();
|
|
1917
|
+
}
|
|
1918
|
+
}, [endpoint, doFetch]);
|
|
1919
|
+
return { options, status, errorMessage };
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1814
1922
|
const MOBILE_BREAKPOINT$4 = 640;
|
|
1815
1923
|
const useIsMobile$5 = () => {
|
|
1816
1924
|
const [isMobile, setIsMobile] = useState(
|
|
@@ -1834,10 +1942,15 @@ const SelectWithConfirm = ({
|
|
|
1834
1942
|
const [searchValue, setSearchValue] = useState("");
|
|
1835
1943
|
const searchInputRef = useRef(null);
|
|
1836
1944
|
const isMobile = useIsMobile$5();
|
|
1837
|
-
const { field_name, value, ...componentProps } = component.props;
|
|
1838
|
-
const
|
|
1945
|
+
const { field_name, value, endpoint: endpointProp, ...componentProps } = component.props;
|
|
1946
|
+
const endpoint = endpointProp;
|
|
1947
|
+
const { options: endpointOptions, status: endpointStatus, errorMessage } = useSelectEndpoint(endpoint, eventBus);
|
|
1948
|
+
const staticOptions = componentProps.options || [];
|
|
1949
|
+
const options = endpoint ? endpointOptions : staticOptions;
|
|
1839
1950
|
const placeholder = componentProps.placeholder || "Seleccionar...";
|
|
1840
1951
|
const label = componentProps.label || "";
|
|
1952
|
+
const isLoading = endpointStatus === "loading";
|
|
1953
|
+
const isError = endpointStatus === "error";
|
|
1841
1954
|
const { emitInteraction, isConfirmed } = useComponentInteraction({
|
|
1842
1955
|
eventBus,
|
|
1843
1956
|
fieldName: component.props.field_name,
|
|
@@ -1857,6 +1970,18 @@ const SelectWithConfirm = ({
|
|
|
1857
1970
|
}
|
|
1858
1971
|
}, [isDrawerOpen]);
|
|
1859
1972
|
if (isConfirmed) return null;
|
|
1973
|
+
if (isLoading) {
|
|
1974
|
+
return /* @__PURE__ */ jsxs("div", { className: "px-chat-select-with-confirm", children: [
|
|
1975
|
+
label && /* @__PURE__ */ jsx("label", { className: "px-chat-select-with-confirm__label", children: label }),
|
|
1976
|
+
/* @__PURE__ */ jsx("div", { className: "px-chat-select-with-confirm__loading", children: "Cargando opciones..." })
|
|
1977
|
+
] });
|
|
1978
|
+
}
|
|
1979
|
+
if (isError) {
|
|
1980
|
+
return /* @__PURE__ */ jsxs("div", { className: "px-chat-select-with-confirm", children: [
|
|
1981
|
+
label && /* @__PURE__ */ jsx("label", { className: "px-chat-select-with-confirm__label", children: label }),
|
|
1982
|
+
/* @__PURE__ */ jsx("div", { className: "px-chat-select-with-confirm__error", children: errorMessage })
|
|
1983
|
+
] });
|
|
1984
|
+
}
|
|
1860
1985
|
if (isMobile) {
|
|
1861
1986
|
return /* @__PURE__ */ jsxs("div", { className: "px-chat-select-with-confirm", children: [
|
|
1862
1987
|
label && /* @__PURE__ */ jsx("label", { className: "px-chat-select-with-confirm__label", children: label }),
|
|
@@ -1976,8 +2101,8 @@ const SelectWithConfirm = ({
|
|
|
1976
2101
|
/* @__PURE__ */ jsx(
|
|
1977
2102
|
Select$2,
|
|
1978
2103
|
{
|
|
1979
|
-
options: [],
|
|
1980
2104
|
...componentProps,
|
|
2105
|
+
options,
|
|
1981
2106
|
variant: "chat",
|
|
1982
2107
|
mode: "single",
|
|
1983
2108
|
showSearch: true,
|
|
@@ -3137,7 +3262,7 @@ const SelectField$1 = ({ field, value, disabled, loading, options, onChange, dra
|
|
|
3137
3262
|
),
|
|
3138
3263
|
searchValue && /* @__PURE__ */ jsx("button", { type: "button", className: "px-chat-select-drawer__search-clear", onClick: () => setSearchValue(""), children: /* @__PURE__ */ jsx(Icon$1, { type: ICON_TYPES.CLOSE, size: "small" }) })
|
|
3139
3264
|
] }),
|
|
3140
|
-
/* @__PURE__ */ jsx("ul", { className: "px-chat-select-drawer__options", role: "listbox", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsx("li", { className: "px-chat-select-drawer__empty", children: "No hay opciones disponibles" }) : filteredOptions.map((opt) => {
|
|
3265
|
+
/* @__PURE__ */ jsx("ul", { className: "px-chat-select-drawer__options", role: "listbox", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsx("li", { className: "px-chat-select-drawer__empty", children: field.empty_message || "No hay opciones disponibles" }) : filteredOptions.map((opt) => {
|
|
3141
3266
|
const isSelected = opt.value === value;
|
|
3142
3267
|
return /* @__PURE__ */ jsxs(
|
|
3143
3268
|
"li",
|
|
@@ -3258,7 +3383,7 @@ const SelectMultipleField = ({ field, value, disabled, loading, options, onChang
|
|
|
3258
3383
|
),
|
|
3259
3384
|
searchValue && /* @__PURE__ */ jsx("button", { type: "button", className: "px-chat-select-drawer__search-clear", onClick: () => setSearchValue(""), children: /* @__PURE__ */ jsx(Icon$1, { type: ICON_TYPES.CLOSE, size: "small" }) })
|
|
3260
3385
|
] }),
|
|
3261
|
-
/* @__PURE__ */ jsx("ul", { className: "px-chat-select-drawer__options", role: "listbox", "aria-multiselectable": "true", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsx("li", { className: "px-chat-select-drawer__empty", children: "No hay opciones disponibles" }) : filteredOptions.map((opt) => {
|
|
3386
|
+
/* @__PURE__ */ jsx("ul", { className: "px-chat-select-drawer__options", role: "listbox", "aria-multiselectable": "true", children: filteredOptions.length === 0 ? /* @__PURE__ */ jsx("li", { className: "px-chat-select-drawer__empty", children: field.empty_message || "No hay opciones disponibles" }) : filteredOptions.map((opt) => {
|
|
3262
3387
|
const isChecked = selected.includes(opt.value);
|
|
3263
3388
|
return /* @__PURE__ */ jsxs(
|
|
3264
3389
|
"li",
|
|
@@ -3356,6 +3481,200 @@ const DateField = ({ field, value, disabled, onChange, drawerPlacement }) => {
|
|
|
3356
3481
|
] });
|
|
3357
3482
|
};
|
|
3358
3483
|
|
|
3484
|
+
const PREFIX$3 = "px-chat-currency";
|
|
3485
|
+
const ChatCurrencyDisplay = ({
|
|
3486
|
+
value,
|
|
3487
|
+
currencyCode,
|
|
3488
|
+
currencySymbol,
|
|
3489
|
+
showCode = true,
|
|
3490
|
+
size = "md",
|
|
3491
|
+
className
|
|
3492
|
+
}) => {
|
|
3493
|
+
const { locale, currency: configCurrency } = useChatConfig();
|
|
3494
|
+
const code = currencyCode || configCurrency?.code || "COP";
|
|
3495
|
+
const symbol = currencySymbol || configCurrency?.symbol || "$";
|
|
3496
|
+
const formatted = useMemo(() => {
|
|
3497
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
3498
|
+
const num = typeof value === "string" ? parseFloat(value) : value;
|
|
3499
|
+
if (isNaN(num)) return null;
|
|
3500
|
+
try {
|
|
3501
|
+
return new Intl.NumberFormat(locale || "es-CO", {
|
|
3502
|
+
minimumFractionDigits: 2,
|
|
3503
|
+
maximumFractionDigits: 2
|
|
3504
|
+
}).format(num);
|
|
3505
|
+
} catch {
|
|
3506
|
+
return num.toFixed(2);
|
|
3507
|
+
}
|
|
3508
|
+
}, [value, locale]);
|
|
3509
|
+
if (!formatted) return /* @__PURE__ */ jsx("span", { className: `${PREFIX$3} ${PREFIX$3}--empty`, children: "-" });
|
|
3510
|
+
const classes = [PREFIX$3, `${PREFIX$3}--${size}`, className].filter(Boolean).join(" ");
|
|
3511
|
+
return /* @__PURE__ */ jsxs("span", { className: classes, children: [
|
|
3512
|
+
/* @__PURE__ */ jsx("span", { className: `${PREFIX$3}__symbol`, children: symbol }),
|
|
3513
|
+
" ",
|
|
3514
|
+
/* @__PURE__ */ jsx("span", { className: `${PREFIX$3}__value`, children: formatted }),
|
|
3515
|
+
showCode && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3516
|
+
" ",
|
|
3517
|
+
/* @__PURE__ */ jsx("span", { className: `${PREFIX$3}__code`, children: code })
|
|
3518
|
+
] })
|
|
3519
|
+
] });
|
|
3520
|
+
};
|
|
3521
|
+
ChatCurrencyDisplay.displayName = "ChatCurrencyDisplay";
|
|
3522
|
+
|
|
3523
|
+
function resolveVariable(template, formValues, fieldStates) {
|
|
3524
|
+
if (!template.startsWith("$")) return template;
|
|
3525
|
+
const withoutDollar = template.slice(1);
|
|
3526
|
+
const dotIndex = withoutDollar.indexOf(".");
|
|
3527
|
+
const fieldName = dotIndex >= 0 ? withoutDollar.slice(0, dotIndex) : withoutDollar;
|
|
3528
|
+
const prop = dotIndex >= 0 ? withoutDollar.slice(dotIndex + 1) : null;
|
|
3529
|
+
const value = formValues[fieldName];
|
|
3530
|
+
const state = fieldStates[fieldName];
|
|
3531
|
+
if (!prop) return value;
|
|
3532
|
+
if (prop === "label" && state) {
|
|
3533
|
+
return state.label || "";
|
|
3534
|
+
}
|
|
3535
|
+
if (prop === "labels" && state && Array.isArray(value)) {
|
|
3536
|
+
return value.map((v) => {
|
|
3537
|
+
const opt = state.options.find((o) => o.value === v);
|
|
3538
|
+
return opt?.label || v;
|
|
3539
|
+
}).join(", ");
|
|
3540
|
+
}
|
|
3541
|
+
if (state) {
|
|
3542
|
+
const selectedOpt = state.options.find((o) => o.value === value);
|
|
3543
|
+
if (selectedOpt && prop in selectedOpt) {
|
|
3544
|
+
return selectedOpt[prop];
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
return "";
|
|
3548
|
+
}
|
|
3549
|
+
function resolveBody(obj, formValues, fieldStates) {
|
|
3550
|
+
if (typeof obj === "string") return resolveVariable(obj, formValues, fieldStates);
|
|
3551
|
+
if (Array.isArray(obj)) return obj.map((item) => resolveBody(item, formValues, fieldStates));
|
|
3552
|
+
if (obj !== null && typeof obj === "object") {
|
|
3553
|
+
const resolved = {};
|
|
3554
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
3555
|
+
resolved[key] = resolveBody(value, formValues, fieldStates);
|
|
3556
|
+
}
|
|
3557
|
+
return resolved;
|
|
3558
|
+
}
|
|
3559
|
+
return obj;
|
|
3560
|
+
}
|
|
3561
|
+
function resolveParams(params, formValues, fieldStates) {
|
|
3562
|
+
const resolved = {};
|
|
3563
|
+
for (const [key, template] of Object.entries(params)) {
|
|
3564
|
+
resolved[key] = resolveVariable(template, formValues, fieldStates);
|
|
3565
|
+
}
|
|
3566
|
+
return resolved;
|
|
3567
|
+
}
|
|
3568
|
+
function resolveSummaryRows(rows, formValues, fieldStates) {
|
|
3569
|
+
return rows.map((row) => ({
|
|
3570
|
+
label: row.label,
|
|
3571
|
+
value: String(resolveVariable(row.value, formValues, fieldStates) ?? "")
|
|
3572
|
+
}));
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
const CostSummaryField = ({
|
|
3576
|
+
field,
|
|
3577
|
+
values,
|
|
3578
|
+
fieldStates,
|
|
3579
|
+
onCostResolved,
|
|
3580
|
+
eventBus,
|
|
3581
|
+
onCancel
|
|
3582
|
+
}) => {
|
|
3583
|
+
const [totalCost, setTotalCost] = useState(null);
|
|
3584
|
+
const [loading, setLoading] = useState(true);
|
|
3585
|
+
const [error, setError] = useState("");
|
|
3586
|
+
const selectedItems = useMemo(() => {
|
|
3587
|
+
if (!field.items_from) return [];
|
|
3588
|
+
const { source_field, label_key, value_key } = field.items_from;
|
|
3589
|
+
const sourceState = fieldStates[source_field];
|
|
3590
|
+
const selectedValues = values[source_field];
|
|
3591
|
+
if (!sourceState?.options || !selectedValues) return [];
|
|
3592
|
+
const selectedArr = Array.isArray(selectedValues) ? selectedValues : [selectedValues];
|
|
3593
|
+
return sourceState.options.filter((opt) => selectedArr.includes(opt.value)).map((opt) => ({
|
|
3594
|
+
label: String(opt[label_key] || opt.label || ""),
|
|
3595
|
+
value: Number(opt[value_key] || 0)
|
|
3596
|
+
}));
|
|
3597
|
+
}, [field.items_from, fieldStates, values]);
|
|
3598
|
+
const fetchedRef = useRef(false);
|
|
3599
|
+
useEffect(() => {
|
|
3600
|
+
if (!field.endpoint || !eventBus || fetchedRef.current) {
|
|
3601
|
+
if (!field.endpoint || !eventBus) setLoading(false);
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
3604
|
+
fetchedRef.current = true;
|
|
3605
|
+
const body = resolveBody(field.endpoint.body || {}, values, fieldStates);
|
|
3606
|
+
setLoading(true);
|
|
3607
|
+
setError("");
|
|
3608
|
+
fetchViaEventBus(eventBus, field.endpoint.service, field.endpoint.url, field.endpoint.method, body).then((data) => {
|
|
3609
|
+
const resp = data;
|
|
3610
|
+
if (resp?.notification_type && String(resp.notification_type).toUpperCase() !== "SUCCESS") {
|
|
3611
|
+
setError(String(resp.message || "Error al calcular costo"));
|
|
3612
|
+
return;
|
|
3613
|
+
}
|
|
3614
|
+
const responseData = resp?.response;
|
|
3615
|
+
const totalField = field.endpoint.total_field || "total_cost";
|
|
3616
|
+
const cost = Number(responseData?.[totalField] ?? resp?.[totalField] ?? 0);
|
|
3617
|
+
setTotalCost(cost);
|
|
3618
|
+
if (onCostResolved) {
|
|
3619
|
+
try {
|
|
3620
|
+
const formatted = new Intl.NumberFormat("es-CO", { style: "currency", currency: "COP" }).format(cost);
|
|
3621
|
+
onCostResolved(field.name, formatted);
|
|
3622
|
+
} catch {
|
|
3623
|
+
onCostResolved(field.name, `$${cost}`);
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
}).catch((err) => {
|
|
3627
|
+
setError(err instanceof Error ? err.message : "Error al calcular costo");
|
|
3628
|
+
}).finally(() => setLoading(false));
|
|
3629
|
+
}, [field.endpoint, eventBus]);
|
|
3630
|
+
const handleCancel = () => {
|
|
3631
|
+
if (field.on_cancel && eventBus) {
|
|
3632
|
+
if (field.on_cancel.type === "system_message") {
|
|
3633
|
+
eventBus.emit(CHAT_EVENT.FORM_TRACE, {
|
|
3634
|
+
chatId: "",
|
|
3635
|
+
formTitle: "",
|
|
3636
|
+
source: "reactive_form",
|
|
3637
|
+
phase: "cancel",
|
|
3638
|
+
data: { message: field.on_cancel.message },
|
|
3639
|
+
service: "",
|
|
3640
|
+
url: "",
|
|
3641
|
+
method: ""
|
|
3642
|
+
});
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
onCancel();
|
|
3646
|
+
};
|
|
3647
|
+
if (loading) {
|
|
3648
|
+
return /* @__PURE__ */ jsxs("div", { className: "px-chat-rf__cost-summary px-chat-rf__cost-summary--loading", children: [
|
|
3649
|
+
/* @__PURE__ */ jsx("div", { className: "px-chat-rf__cost-summary__title", children: field.label || "Resumen de costos" }),
|
|
3650
|
+
/* @__PURE__ */ jsxs("div", { className: "px-chat-rf__cost-summary__loader", children: [
|
|
3651
|
+
/* @__PURE__ */ jsx("span", { className: "px-chat-rf__spinner" }),
|
|
3652
|
+
/* @__PURE__ */ jsx("span", { children: "Calculando costo..." })
|
|
3653
|
+
] })
|
|
3654
|
+
] });
|
|
3655
|
+
}
|
|
3656
|
+
if (error) {
|
|
3657
|
+
return /* @__PURE__ */ jsxs("div", { className: "px-chat-rf__cost-summary px-chat-rf__cost-summary--error", children: [
|
|
3658
|
+
/* @__PURE__ */ jsx("div", { className: "px-chat-rf__cost-summary__title", children: field.label || "Resumen de costos" }),
|
|
3659
|
+
/* @__PURE__ */ jsx("div", { className: "px-chat-rf__cost-summary__error", children: error }),
|
|
3660
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "px-chat-rf__btn px-chat-rf__btn--secondary", onClick: handleCancel, children: "Cancelar" })
|
|
3661
|
+
] });
|
|
3662
|
+
}
|
|
3663
|
+
return /* @__PURE__ */ jsxs("div", { className: "px-chat-rf__cost-summary", children: [
|
|
3664
|
+
/* @__PURE__ */ jsx("div", { className: "px-chat-rf__cost-summary__title", children: field.label || "Resumen de costos" }),
|
|
3665
|
+
/* @__PURE__ */ jsx("div", { className: "px-chat-rf__cost-summary__items", children: selectedItems.map((item, i) => /* @__PURE__ */ jsxs("div", { className: "px-chat-rf__cost-summary__item", children: [
|
|
3666
|
+
/* @__PURE__ */ jsx("span", { className: "px-chat-rf__cost-summary__item-label", children: item.label }),
|
|
3667
|
+
/* @__PURE__ */ jsx(ChatCurrencyDisplay, { value: item.value, size: "sm" })
|
|
3668
|
+
] }, i)) }),
|
|
3669
|
+
/* @__PURE__ */ jsx("div", { className: "px-chat-rf__cost-summary__divider" }),
|
|
3670
|
+
/* @__PURE__ */ jsxs("div", { className: "px-chat-rf__cost-summary__total", children: [
|
|
3671
|
+
/* @__PURE__ */ jsx("span", { className: "px-chat-rf__cost-summary__total-label", children: "Total" }),
|
|
3672
|
+
/* @__PURE__ */ jsx(ChatCurrencyDisplay, { value: totalCost, size: "lg" })
|
|
3673
|
+
] }),
|
|
3674
|
+
/* @__PURE__ */ jsx("div", { className: "px-chat-rf__cost-summary__actions", children: /* @__PURE__ */ jsx("button", { type: "button", className: "px-chat-rf__btn px-chat-rf__btn--secondary", onClick: handleCancel, children: "Cancelar" }) })
|
|
3675
|
+
] });
|
|
3676
|
+
};
|
|
3677
|
+
|
|
3359
3678
|
const checkIcon = /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx("path", { d: "M2.5 6L5 8.5L9.5 3.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
3360
3679
|
const calendarIcon = /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
|
|
3361
3680
|
/* @__PURE__ */ jsx("rect", { x: "2", y: "3", width: "12", height: "11", rx: "1.5", stroke: "currentColor", strokeWidth: "1.2" }),
|
|
@@ -3371,7 +3690,7 @@ const formIcon = /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBo
|
|
|
3371
3690
|
] });
|
|
3372
3691
|
const chevronDown = /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx("path", { d: "M3 4.5L6 7.5L9 4.5", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
3373
3692
|
const chevronRight = /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx("path", { d: "M4.5 3L7.5 6L4.5 9", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
3374
|
-
const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form, drawerPlacement = "bottom" }) => {
|
|
3693
|
+
const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form, drawerPlacement = "bottom", eventBus }) => {
|
|
3375
3694
|
const {
|
|
3376
3695
|
formStatus,
|
|
3377
3696
|
setFormStatus,
|
|
@@ -3387,6 +3706,7 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
|
|
|
3387
3706
|
handleNext,
|
|
3388
3707
|
handleBack,
|
|
3389
3708
|
handleSubmit,
|
|
3709
|
+
isSubmitting,
|
|
3390
3710
|
getDependencies,
|
|
3391
3711
|
allDepsResolved,
|
|
3392
3712
|
resolveSummaryRows
|
|
@@ -3446,11 +3766,25 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
|
|
|
3446
3766
|
const renderSummaryActions = () => {
|
|
3447
3767
|
const actions = summary.actions;
|
|
3448
3768
|
if (!actions || actions.length === 0) {
|
|
3449
|
-
return /* @__PURE__ */ jsx("button", { type: "button", className: "px-chat-rf__btn px-chat-rf__btn--primary px-chat-rf__btn--full", onClick: handleSubmit, children: submit.label });
|
|
3769
|
+
return /* @__PURE__ */ 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__ */ jsx("span", { className: "px-chat-rf__spinner" }) : submit.label });
|
|
3450
3770
|
}
|
|
3451
3771
|
const actionHandlers = {
|
|
3452
3772
|
submit: handleSubmit,
|
|
3453
|
-
close: () =>
|
|
3773
|
+
close: () => {
|
|
3774
|
+
if (eventBus) {
|
|
3775
|
+
eventBus.emit(CHAT_EVENT.FORM_TRACE, {
|
|
3776
|
+
chatId: "",
|
|
3777
|
+
formTitle: "",
|
|
3778
|
+
source: "reactive_form",
|
|
3779
|
+
phase: "cancel",
|
|
3780
|
+
data: { message: "El usuario cancel\xF3 el agendamiento de la cita." },
|
|
3781
|
+
service: "",
|
|
3782
|
+
url: "",
|
|
3783
|
+
method: ""
|
|
3784
|
+
});
|
|
3785
|
+
}
|
|
3786
|
+
setFormStatus("closed");
|
|
3787
|
+
},
|
|
3454
3788
|
back: handleBack
|
|
3455
3789
|
};
|
|
3456
3790
|
return /* @__PURE__ */ jsx("div", { className: "px-chat-rf__nav", children: actions.map((act, i) => /* @__PURE__ */ jsx(
|
|
@@ -3460,7 +3794,8 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
|
|
|
3460
3794
|
className: `px-chat-rf__btn px-chat-rf__btn--${act.variant || "primary"}`,
|
|
3461
3795
|
onClick: actionHandlers[act.action] || (() => {
|
|
3462
3796
|
}),
|
|
3463
|
-
|
|
3797
|
+
disabled: act.action === "submit" && isSubmitting,
|
|
3798
|
+
children: act.action === "submit" && isSubmitting ? /* @__PURE__ */ jsx("span", { className: "px-chat-rf__spinner" }) : act.label
|
|
3464
3799
|
},
|
|
3465
3800
|
i
|
|
3466
3801
|
)) });
|
|
@@ -3493,8 +3828,15 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
|
|
|
3493
3828
|
)
|
|
3494
3829
|
] }),
|
|
3495
3830
|
prevStep.fields.map((fname) => {
|
|
3496
|
-
const fs = fieldStates[fname];
|
|
3497
3831
|
const fld = fields.find((f) => f.name === fname);
|
|
3832
|
+
if (fld?.type === "cost_summary") {
|
|
3833
|
+
const costLabel = fieldStates[fname]?.label;
|
|
3834
|
+
return costLabel ? /* @__PURE__ */ jsxs("div", { className: "px-chat-rf__completed-field", children: [
|
|
3835
|
+
/* @__PURE__ */ jsx("span", { className: "px-chat-rf__completed-field-label", children: "Total:" }),
|
|
3836
|
+
/* @__PURE__ */ jsx("span", { className: "px-chat-rf__completed-field-value", children: costLabel })
|
|
3837
|
+
] }, fname) : null;
|
|
3838
|
+
}
|
|
3839
|
+
const fs = fieldStates[fname];
|
|
3498
3840
|
return /* @__PURE__ */ jsxs("div", { className: "px-chat-rf__completed-field", children: [
|
|
3499
3841
|
/* @__PURE__ */ jsxs("span", { className: "px-chat-rf__completed-field-label", children: [
|
|
3500
3842
|
fld?.label,
|
|
@@ -3507,6 +3849,20 @@ const ReactiveFormView = ({ title, trigger, steps, fields, submit, summary, form
|
|
|
3507
3849
|
/* @__PURE__ */ jsxs("div", { className: "px-chat-rf__active-step", children: [
|
|
3508
3850
|
/* @__PURE__ */ jsx("div", { className: "px-chat-rf__step-title", children: step.title }),
|
|
3509
3851
|
stepFields.map((field) => {
|
|
3852
|
+
if (field.type === "cost_summary") {
|
|
3853
|
+
return /* @__PURE__ */ jsx(
|
|
3854
|
+
CostSummaryField,
|
|
3855
|
+
{
|
|
3856
|
+
field,
|
|
3857
|
+
values,
|
|
3858
|
+
fieldStates,
|
|
3859
|
+
eventBus,
|
|
3860
|
+
onCancel: () => setFormStatus("closed"),
|
|
3861
|
+
onCostResolved: (name, label) => handleFieldChange(name, label, label)
|
|
3862
|
+
},
|
|
3863
|
+
field.name
|
|
3864
|
+
);
|
|
3865
|
+
}
|
|
3510
3866
|
const deps = getDependencies(field);
|
|
3511
3867
|
const isDisabled = !allDepsResolved(deps, values);
|
|
3512
3868
|
return /* @__PURE__ */ jsx(
|
|
@@ -3602,86 +3958,6 @@ function buildInitialValues(fields) {
|
|
|
3602
3958
|
return values;
|
|
3603
3959
|
}
|
|
3604
3960
|
|
|
3605
|
-
function resolveVariable(template, formValues, fieldStates) {
|
|
3606
|
-
if (!template.startsWith("$")) return template;
|
|
3607
|
-
const withoutDollar = template.slice(1);
|
|
3608
|
-
const dotIndex = withoutDollar.indexOf(".");
|
|
3609
|
-
const fieldName = dotIndex >= 0 ? withoutDollar.slice(0, dotIndex) : withoutDollar;
|
|
3610
|
-
const prop = dotIndex >= 0 ? withoutDollar.slice(dotIndex + 1) : null;
|
|
3611
|
-
const value = formValues[fieldName];
|
|
3612
|
-
const state = fieldStates[fieldName];
|
|
3613
|
-
if (!prop) return value;
|
|
3614
|
-
if (prop === "label" && state) {
|
|
3615
|
-
return state.label || "";
|
|
3616
|
-
}
|
|
3617
|
-
if (prop === "labels" && state && Array.isArray(value)) {
|
|
3618
|
-
return value.map((v) => {
|
|
3619
|
-
const opt = state.options.find((o) => o.value === v);
|
|
3620
|
-
return opt?.label || v;
|
|
3621
|
-
}).join(", ");
|
|
3622
|
-
}
|
|
3623
|
-
if (state) {
|
|
3624
|
-
const selectedOpt = state.options.find((o) => o.value === value);
|
|
3625
|
-
if (selectedOpt && prop in selectedOpt) {
|
|
3626
|
-
return selectedOpt[prop];
|
|
3627
|
-
}
|
|
3628
|
-
}
|
|
3629
|
-
return "";
|
|
3630
|
-
}
|
|
3631
|
-
function resolveBody(obj, formValues, fieldStates) {
|
|
3632
|
-
if (typeof obj === "string") return resolveVariable(obj, formValues, fieldStates);
|
|
3633
|
-
if (Array.isArray(obj)) return obj.map((item) => resolveBody(item, formValues, fieldStates));
|
|
3634
|
-
if (obj !== null && typeof obj === "object") {
|
|
3635
|
-
const resolved = {};
|
|
3636
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
3637
|
-
resolved[key] = resolveBody(value, formValues, fieldStates);
|
|
3638
|
-
}
|
|
3639
|
-
return resolved;
|
|
3640
|
-
}
|
|
3641
|
-
return obj;
|
|
3642
|
-
}
|
|
3643
|
-
function resolveParams(params, formValues, fieldStates) {
|
|
3644
|
-
const resolved = {};
|
|
3645
|
-
for (const [key, template] of Object.entries(params)) {
|
|
3646
|
-
resolved[key] = resolveVariable(template, formValues, fieldStates);
|
|
3647
|
-
}
|
|
3648
|
-
return resolved;
|
|
3649
|
-
}
|
|
3650
|
-
function resolveSummaryRows(rows, formValues, fieldStates) {
|
|
3651
|
-
return rows.map((row) => ({
|
|
3652
|
-
label: row.label,
|
|
3653
|
-
value: String(resolveVariable(row.value, formValues, fieldStates) ?? "")
|
|
3654
|
-
}));
|
|
3655
|
-
}
|
|
3656
|
-
|
|
3657
|
-
const FETCH_TIMEOUT_MS = 12e4;
|
|
3658
|
-
function fetchViaEventBus(eventBus, service, url, method, body) {
|
|
3659
|
-
const requestId = crypto.randomUUID();
|
|
3660
|
-
return new Promise((resolve, reject) => {
|
|
3661
|
-
const timeoutId = setTimeout(() => {
|
|
3662
|
-
unsubscribe();
|
|
3663
|
-
reject(new Error(`Timeout: no response for ${url} after ${FETCH_TIMEOUT_MS}ms`));
|
|
3664
|
-
}, FETCH_TIMEOUT_MS);
|
|
3665
|
-
const unsubscribe = eventBus.on(CHAT_EVENT.FETCH_RESPONSE, (response) => {
|
|
3666
|
-
if (response.requestId !== requestId) return;
|
|
3667
|
-
clearTimeout(timeoutId);
|
|
3668
|
-
unsubscribe();
|
|
3669
|
-
if (response.httpError) {
|
|
3670
|
-
reject(new Error(response.httpError));
|
|
3671
|
-
} else {
|
|
3672
|
-
resolve(response.data);
|
|
3673
|
-
}
|
|
3674
|
-
});
|
|
3675
|
-
eventBus.emit(CHAT_EVENT.FETCH_REQUEST, {
|
|
3676
|
-
requestId,
|
|
3677
|
-
service,
|
|
3678
|
-
url,
|
|
3679
|
-
method,
|
|
3680
|
-
...body ? { body } : {}
|
|
3681
|
-
});
|
|
3682
|
-
});
|
|
3683
|
-
}
|
|
3684
|
-
|
|
3685
3961
|
function formatTime$1(value, timezone) {
|
|
3686
3962
|
try {
|
|
3687
3963
|
const d = new Date(value);
|
|
@@ -3689,7 +3965,7 @@ function formatTime$1(value, timezone) {
|
|
|
3689
3965
|
return d.toLocaleTimeString([], {
|
|
3690
3966
|
hour: "2-digit",
|
|
3691
3967
|
minute: "2-digit",
|
|
3692
|
-
hour12:
|
|
3968
|
+
hour12: true,
|
|
3693
3969
|
...timezone ? { timeZone: timezone } : {}
|
|
3694
3970
|
});
|
|
3695
3971
|
} catch {
|
|
@@ -4051,7 +4327,10 @@ function useReactiveForm(props) {
|
|
|
4051
4327
|
};
|
|
4052
4328
|
const handleBack = () => setCurrentStep((s) => s - 1);
|
|
4053
4329
|
const [submitMessage, setSubmitMessage] = useState("");
|
|
4330
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
4054
4331
|
const handleSubmit = async () => {
|
|
4332
|
+
if (isSubmitting) return;
|
|
4333
|
+
setIsSubmitting(true);
|
|
4055
4334
|
const resolvedPayload = resolveBody(submit.payload_map, values, fieldStates);
|
|
4056
4335
|
if (eventBus) {
|
|
4057
4336
|
try {
|
|
@@ -4074,7 +4353,8 @@ function useReactiveForm(props) {
|
|
|
4074
4353
|
submitUrl: submit.url,
|
|
4075
4354
|
result: data,
|
|
4076
4355
|
formValues: values,
|
|
4077
|
-
formLabels
|
|
4356
|
+
formLabels,
|
|
4357
|
+
onSuccess: submit.on_success
|
|
4078
4358
|
});
|
|
4079
4359
|
if (notificationType === "SUCCESS") {
|
|
4080
4360
|
setSubmitMessage(message || "Completado");
|
|
@@ -4090,6 +4370,7 @@ function useReactiveForm(props) {
|
|
|
4090
4370
|
const message = err instanceof Error ? err.message : "Error al enviar formulario";
|
|
4091
4371
|
setSubmitMessage(message);
|
|
4092
4372
|
setFormStatus("error");
|
|
4373
|
+
setIsSubmitting(false);
|
|
4093
4374
|
}
|
|
4094
4375
|
} else {
|
|
4095
4376
|
console.log("[ReactiveForm] Submit:", values);
|
|
@@ -4118,6 +4399,7 @@ function useReactiveForm(props) {
|
|
|
4118
4399
|
handleNext,
|
|
4119
4400
|
handleBack,
|
|
4120
4401
|
handleSubmit,
|
|
4402
|
+
isSubmitting,
|
|
4121
4403
|
getDependencies,
|
|
4122
4404
|
allDepsResolved,
|
|
4123
4405
|
resolveSummaryRows: resolveRows
|
|
@@ -4149,7 +4431,8 @@ const ReactiveForm = ({ component, eventBus }) => {
|
|
|
4149
4431
|
{
|
|
4150
4432
|
...props,
|
|
4151
4433
|
form,
|
|
4152
|
-
drawerPlacement: isMobile ? "bottom" : "right"
|
|
4434
|
+
drawerPlacement: isMobile ? "bottom" : "right",
|
|
4435
|
+
eventBus
|
|
4153
4436
|
}
|
|
4154
4437
|
);
|
|
4155
4438
|
};
|