Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 130 additions & 14 deletions shesha-reactjs/src/designer-components/wizard/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import { componentsTreeToFlatStructure, useAvailableConstantsData } from '@/providers/form/utils';
import { clearWizardStep, getStepDescritpion, getWizardStep, loadWizardStep, saveWizardStep } from './utils';
import {
clearWizardState,
getStepDescritpion,
getWizardStep,
loadWizardState,
saveWizardState
} from './utils';
import { IConfigurableActionConfiguration } from '@/interfaces/configurableAction';
import { IConfigurableFormComponent, useForm, useSheshaApplication, ShaForm } from '@/providers';
import { IWizardComponentProps, IWizardStepProps } from './models';
import { useConfigurableAction } from '@/providers/configurableActionsDispatcher';
import { useDataContext } from '@/providers/dataContextProvider/contexts';
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useFormExpression } from '@/hooks';
import { useDeepCompareEffect } from '@/hooks/useDeepCompareEffect';
import { useFormDesignerComponents } from '@/providers/form/hooks';
import { useValidator } from '@/providers/validateProvider';
import { set } from 'lodash';

interface IWizardComponent {
back: () => void;
Expand All @@ -30,7 +37,7 @@ export const useWizard = (model: Omit<IWizardComponentProps, 'size'>): IWizardCo
const toolbox = useFormDesignerComponents();
const validator = useValidator(false);

const formMode = useForm(false).formMode;
const { formMode, formData: currentFormData, setFormData } = useForm(false);

const { executeBooleanExpression, executeAction } = useFormExpression();

Expand Down Expand Up @@ -96,10 +103,14 @@ export const useWizard = (model: Omit<IWizardComponentProps, 'size'>): IWizardCo
const getInitialStep = useMemo(() => {
// If persistStep is enabled and we're not in designer mode, try to load from sessionStorage
if (persistStep && formMode !== 'designer') {
const savedStepId = loadWizardStep(actionsOwnerId, actionOwnerName);
if (savedStepId) {
// Find the index of the saved step in visibleSteps array (not tabs)
const stepIndex = visibleSteps.findIndex(step => step.id === savedStepId);
const savedState = loadWizardState(actionsOwnerId, actionOwnerName);
if (savedState) {
// Restore form data
if (savedState.formData && setFormData) {
setFormData({ values: savedState.formData, mergeValues: false });
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}
// Find the index of the saved step in visibleSteps array
const stepIndex = visibleSteps.findIndex(step => step.id === savedState.stepId);
if (stepIndex !== -1) {
return stepIndex;
}
Expand All @@ -112,8 +123,30 @@ export const useWizard = (model: Omit<IWizardComponentProps, 'size'>): IWizardCo
}, []);

const [current, setCurrent] = useState(getInitialStep);
const [visitedSteps, setVisitedSteps] = useState<Set<string>>(() => {
// Try to restore visited steps from persisted state
if (persistStep && formMode !== 'designer') {
const savedState = loadWizardState(actionsOwnerId, actionOwnerName);
if (savedState?.visitedSteps) {
return new Set(savedState.visitedSteps);
}
}
return new Set();
});

const currentStep = visibleSteps[current];

// Use refs to capture latest values without causing effect re-runs
const currentFormDataRef = useRef(currentFormData);
const visitedStepsRef = useRef(visitedSteps);
// Flag to prevent re-saving after intentional clears (done/reset)
const hasBeenClearedRef = useRef(false);

useEffect(() => {
currentFormDataRef.current = currentFormData;
visitedStepsRef.current = visitedSteps;
});
Comment thread
HackGenesis marked this conversation as resolved.

const components = currentStep?.components;
const componentsNames = useMemo(() => {
if (!components) return null;
Expand All @@ -128,6 +161,26 @@ export const useWizard = (model: Omit<IWizardComponentProps, 'size'>): IWizardCo
return properties;
}, [currentStep]);

// Get all field names across ALL wizard steps (for reset functionality)
// Use tabs (all steps) not visibleSteps to include hidden steps
const allWizardFieldNames = useMemo(() => {
const allFields: string[] = [];
tabs.forEach(step => {
if (step.components) {
const flat = componentsTreeToFlatStructure(toolbox, step.components);
for (const comp in flat.allComponents) {
if (Object.hasOwn(flat.allComponents, comp)) {
const component = flat.allComponents[comp];
if (component.propertyName && !component.context) {
allFields.push(component.propertyName);
}
}
}
}
});
return allFields;
}, [tabs, toolbox]);

useEffect(() => {
if (validator)
validator.registerValidator({
Expand All @@ -146,13 +199,59 @@ export const useWizard = (model: Omit<IWizardComponentProps, 'size'>): IWizardCo

const argumentsEvaluationContext = { ...allData, fieldsToValidate: componentsNames, validate: validator?.validate };

// Persist current step to sessionStorage when it changes
// Track visited steps and persist state when step changes
useEffect(() => {
if (persistStep && formMode !== 'designer' && currentStep?.id) {
saveWizardStep(actionsOwnerId, currentStep.id, actionOwnerName);
if (!currentStep?.id) return;

// Update visited steps immediately
const nextVisited = new Set(visitedStepsRef.current);
nextVisited.add(currentStep.id);

visitedStepsRef.current = nextVisited;
setVisitedSteps(nextVisited);

// Persist state with fresh visited steps
if (persistStep && formMode !== 'designer') {
saveWizardState(
actionsOwnerId,
currentStep.id,
currentFormDataRef.current,
actionOwnerName,
Array.from(nextVisited)
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}, [currentStep?.id, persistStep, actionsOwnerId, actionOwnerName, formMode]);

// Save state before page unload or component unmount (catch mid-step data)
useEffect(() => {
const handleBeforeUnload = () => {
if (persistStep && formMode !== 'designer' && currentStep?.id && !hasBeenClearedRef.current) {
saveWizardState(
actionsOwnerId,
currentStep.id,
currentFormDataRef.current,
actionOwnerName,
Array.from(visitedStepsRef.current)
);
}
};

window.addEventListener('beforeunload', handleBeforeUnload);
return () => {
// Save state before unmounting (SPA navigation), but only if not intentionally cleared
if (persistStep && formMode !== 'designer' && currentStep?.id && !hasBeenClearedRef.current) {
saveWizardState(
actionsOwnerId,
currentStep.id,
currentFormDataRef.current,
actionOwnerName,
Array.from(visitedStepsRef.current)
);
}
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, [persistStep, formMode, currentStep?.id, actionsOwnerId, actionOwnerName]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

useEffect(() => {
const actionConfiguration = currentStep?.onBeforeRenderActionConfiguration;

Expand Down Expand Up @@ -259,9 +358,11 @@ export const useWizard = (model: Omit<IWizardComponentProps, 'size'>): IWizardCo
(tab) => tab.beforeDoneActionConfiguration,
(tab) => tab.afterDoneActionConfiguration,
() => {
// Clear persisted step when wizard is completed
// Clear persisted state when wizard is completed
if (persistStep) {
clearWizardStep(actionsOwnerId, actionOwnerName);
clearWizardState(actionsOwnerId, actionOwnerName);
// Prevent cleanup from re-saving after intentional clear
hasBeenClearedRef.current = true;
}
}
);
Expand Down Expand Up @@ -342,9 +443,24 @@ export const useWizard = (model: Omit<IWizardComponentProps, 'size'>): IWizardCo
ownerUid: actionsOwnerId,
hasArguments: false,
executer: () => {
// Clear persisted step when wizard is reset
// Clear persisted state when wizard is reset
if (persistStep) {
clearWizardStep(actionsOwnerId, actionOwnerName);
clearWizardState(actionsOwnerId, actionOwnerName);
// Prevent cleanup from re-saving after intentional clear
hasBeenClearedRef.current = true;
}
// Clear visited steps
setVisitedSteps(new Set());
// Clear only wizard fields, preserve other form fields
if (setFormData && allWizardFieldNames.length > 0) {
const clearedWizardData: Record<string, unknown> = {};

// Set wizard fields to undefined to clear them (handle nested paths)
allWizardFieldNames.forEach(fieldName => {
set(clearedWizardData, fieldName, undefined);
});

setFormData({ values: clearedWizardData, mergeValues: true });
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
successCallback('reset');
return Promise.resolve();
Expand Down
Loading