import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { booleanString, useReactorNavigation, useWebAuthn } from "../../_core";
import { FormContext, processIncludeVariables, useValidationSchema, uuid } from "../..";
import objectPath from "object-path";
import { useSelector } from "react-redux";
/**
* Componente que renderiza un formulario con opciones extendidas.
* Todos los input dentro de Form deben contener la propiedad name con el identificador del campo
* @name Reactor.Components.Data.FormExt
* @param {string!} action Nombre de la operación de destino
* @param {JsonString} data Datos para agregarse al form y evitar inputs de tipo hidden
* @param {string?} include_variables Lista de variables que se incluyen en la data (separadas por espacio)
* @param {string?} context Contexto contenedor de las variables que se van a incluir, es requerido si se incluyen variables
* @param {NavigationTarget} [target = "menu"] Destino del resultado
* @param {DirectionType} direction Dirección en la que va a mostrarse el menú
* @param {string?} offcanvas_class Clase css que se va a dar al menú
* @param {string!} to Nombre del FrameDiv de destino. Requerido para el uso de FormExt
* @param {BooleanString} [use_token = "false"] Si es true guarda el token que devuelve Orquesta, sirve para luego poder continuar el flujo con otro form, si es false no se guarda y el flujo termina
* @param {BooleanString} [use_prev_token = "true"] Si es true se envía el token que está en memoria, sirve para continuar la ejecución de un flujo, si es false el token se envía null
* @param {BooleanString} [use_spinner = "true"] Si es true se muestra un spinner mientras se espera la respuesta del servidor
* @param {string} [change_delay = "0"] Establece un tiempo de espera muy corto para que los cambios se vean reflejados en los inputs controlados por React
* @param {BooleanString} [debug = "false"] Establece modo de depuracion
* @param {BooleanString} [webauthn_check = "false"] Establece si debe requerir autenticacion por medio de webauthn para ejecutar el evento submit
* @class
* @example
<form_ext
action="ProductoDepositar"
target="menu"
direction="bottom"
offcanvas_class="rounded-m"
to="ProductoDepositar"
use_token="true"
use_prev_token="false"
use_spinner="false"
include_variables="variable1 variable2"
context="un_contexto"
>
<div class="menu-size" style="height:auto;" >
<div class="content mt-0 mb-2">
<div class="form-custom form-label form-icon">
<i class="bi bi-check-circle font-13"></i>
<select class="form-select rounded-xs" name="medioPago" value="{{opcionSeleccionada}}" aria-label="">
<optgroup label="Medios de Pago">
{{opciones|OpcionCombo}}
</optgroup>
</select>
<label for="medioPago" class="form-label-always-active color-highlight font-11">Medio de pago</label>
</div>
<div class="pb-3"></div>
<div class="form-custom form-label form-icon">
<i class="bi bi-hash font-14"></i>
<input type="number" class="form-control rounded-xs" name="importe" placeholder="ingrese el monto">
<label for="importe" class="form-label-always-active color-highlight font-11">Importe</label>
<span class="font-10">( Moneda: $ ARG )</span>
</div>
<div class="pb-3"></div>
<input
type="submit"
class="form-control rounded-xs btn btn-full gradient-highlight shadow-bg shadow-bg-s"
value="Depositar"
offcanvas_class="rounded-m"
/>
</div>
</div>
</form_ext>
*/
export const FormExt = ({ action, direction, target, children, offcanvas_class, data = "{}", to = "_", use_token = "false", use_prev_token = "true", use_spinner = "true", change_delay = "0", include_variables, context, debug = "false", webauthn_check = "false"}) => {
const variablesState = useSelector(state => context === "_" ? state.app.variables : state.app.variables[context] ) || null;
const navigation = useReactorNavigation({target, to: action, direction, offcanvasClass: offcanvas_class});
const ref = useRef();
const [ formName ] = useState(() => `form_${uuid()}`);
const [ state, setState ] = useState({ send: 0, sender: "", timer: 0, formInputData: null, formInputDataStr: "", sendEvent: false, delay: null })
const validation = useValidationSchema({debug});
const { assertionCheck } = useWebAuthn();
useEffect(()=>{
return ()=>{
ref.current = null;
}
}, [])
const getVariables = useCallback(() => {
return context && processIncludeVariables({variablesState, include_variables});
}, [context, variablesState, include_variables]);
const getDelay = useMemo(() => {
if(state.delay === "0" || state.delay === 0){
return 0;
}
if(state.delay) return state.delay * 1;
if(change_delay){
return change_delay * 1;
}
return 0;
}, [state.delay, change_delay])
useEffect(() => {
if(!to === "_" || !to || state.sender === "" || state.send === 0 || !state.sendEvent ) return;
if(state.timer !== 0){
clearTimeout(state.timer);
}
const delay = getDelay;
let newTimer;
if(delay === 0){
if(state.formInputDataStr !== ""){
const newInputFormData = {...getFormData(ref.current), ...JSON.parse(data), event: "change", element: state.sender, ...getVariables()};
navigation.ToFrameDiv(newInputFormData, action, formName, to, null, use_token === "true", use_prev_token === "true", use_spinner === "true" )
}
newTimer = 0;
}else{
newTimer = setTimeout(() => {
const newInputFormData = {...getFormData(ref.current), ...JSON.parse(data), event: "change", element: state.sender, ...getVariables()};
setState(state=>({...state, formInputData: newInputFormData}));
navigation.ToFrameDiv(newInputFormData, action, formName, to, null, use_token === "true", use_prev_token === "true", use_spinner === "true" )
}, delay);
}
setState(state => ({...state, timer: newTimer}) );
return () => clearTimeout(newTimer);
}, [state.send, state.sendEvent, getDelay, state.sender, state.formInputDataStr])
const submit = (data) => {
if(booleanString(webauthn_check)){
assertionCheck()
.then(()=>{
navigation.execute(data);
})
.catch(()=>{});
}else{
navigation.execute(data);
}
}
const handleSubmit = (event) => {
const allData = {...getFormData(ref.current), ...JSON.parse(data), event: "submit", ...getVariables()};
event?.preventDefault();
validation.isValidFn(allData, submit);
}
const handleChange = (event) => {
const newInputFormData = {...getFormData(ref.current), ...JSON.parse(data), event: "change", element: event.target.name, ...getVariables()};
const newSendEvent = (!event?.target?.getAttribute) || event?.target?.getAttribute("use_change") !== "false";
const newDelay = (newSendEvent && event?.target?.getAttribute) ? (event.target.getAttribute("delay") ? event.target.getAttribute("delay") * 1 : null) : null;
setState(state => ({...state, send: state.send + 1, sender: event && event.target && event.target.name, sendEvent: newSendEvent, delay: newDelay, formInputData: newInputFormData, formInputDataStr: JSON.stringify(newInputFormData)}) );
}
return (
<FormContext.Provider value={{submit: handleSubmit, change: handleChange, formInputData: state.formInputData, validation}}>
<form
ref={ref}
onSubmit={handleSubmit}
onChange={handleChange}
>
{children}
</form>
</FormContext.Provider>
)
}
export const getFormData = (form) => {
if(!form) return {};
const formData = new FormData(form);
const formEntries = Object.fromEntries(formData.entries());
let result = {};
Object.keys( formEntries ).forEach(key => {
objectPath.set(result, key, formEntries[key]);
});
return result;
}