From 1d2d51dc56c3076751c7db60d63e81c7c0683519 Mon Sep 17 00:00:00 2001 From: AishDani Date: Wed, 16 Jul 2025 11:20:55 +0530 Subject: [PATCH] refactor:added alert box for refresh and refactored the issue of content mapper checkbox --- .../ContentMapper/contentMapper.interface.ts | 1 + ui/src/components/ContentMapper/index.tsx | 43 ++++++++++++------- .../Actions/LoadLanguageMapper.tsx | 2 +- .../DestinationStack/Actions/LoadStacks.tsx | 7 ++- ui/src/components/DestinationStack/index.tsx | 2 +- .../LegacyCms/Actions/LoadUploadFile.tsx | 10 ++--- ui/src/hooks/useWarnOnrefresh.tsx | 18 ++++++++ ui/src/pages/Migration/index.tsx | 31 ++++++++++++- 8 files changed, 88 insertions(+), 26 deletions(-) create mode 100644 ui/src/hooks/useWarnOnrefresh.tsx diff --git a/ui/src/components/ContentMapper/contentMapper.interface.ts b/ui/src/components/ContentMapper/contentMapper.interface.ts index 86686095..10d096d1 100644 --- a/ui/src/components/ContentMapper/contentMapper.interface.ts +++ b/ui/src/components/ContentMapper/contentMapper.interface.ts @@ -206,4 +206,5 @@ export interface ModifiedField { backupFieldType: string; parentId: string; uid: string; + _canSelect?: boolean; } diff --git a/ui/src/components/ContentMapper/index.tsx b/ui/src/components/ContentMapper/index.tsx index fe9bf917..836af4da 100644 --- a/ui/src/components/ContentMapper/index.tsx +++ b/ui/src/components/ContentMapper/index.tsx @@ -272,7 +272,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R const [searchContentType, setSearchContentType] = useState(''); - const [rowIds, setRowIds] = useState({}); + const [rowIds, setRowIds] = useState>({}); const [selectedEntries, setSelectedEntries] = useState([]); const [contentTypeSchema, setContentTypeSchema] = useState([]); const [showFilter, setShowFilter] = useState(false); @@ -292,7 +292,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R const [isCsCTypeUpdated, setsCsCTypeUpdated] = useState(false); const [isLoadingSaveButton, setisLoadingSaveButton] = useState(false); const [activeFilter, setActiveFilter] = useState(''); - + const [isAllCheck, setIsAllCheck] = useState(false); /** ALL HOOKS Here */ const { projectId = '' } = useParams(); @@ -361,6 +361,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R if (newMigrationData?.content_mapping?.content_type_mapping?.[selectedContentType?.contentstackUid || ''] === otherContentType?.id) { + setIsAllCheck(false); tableData?.forEach((row) => { contentTypeSchema?.forEach((schema) => { @@ -432,7 +433,8 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R }, [tableData, otherContentType]); useEffect(() => { - if (isUpdated) { + if (isUpdated) { + setIsAllCheck(false); setTableData(updatedRows); setExistingField(updatedExstingField); setSelectedOptions(updatedSelectedOptions); @@ -440,6 +442,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R setIsUpdated(false); } else{ + setIsAllCheck(false); setExistingField({}); setSelectedOptions([]); @@ -449,15 +452,15 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R // To make all the fields checked useEffect(() => { const selectedId = tableData?.reduce((acc, item) => { - if(!item?.isDeleted) { + if(!item?.isDeleted && isAllCheck) { acc[item?.id] = true; } return acc; }, {}); - setRowIds(selectedId); - }, [tableData]); + isAllCheck && setRowIds(selectedId); + }, [tableData, isAllCheck]); // To fetch existing content types or global fields as per the type useEffect(() => { @@ -542,6 +545,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R },[contentTypeSchema]); useEffect(() => { if (existingField && isCsCTypeUpdated) { + setIsAllCheck(false); const matchedKeys = new Set(); contentTypeSchema?.forEach((item) => { @@ -672,7 +676,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R setItemStatusMap({ ...itemStatusMap }); const validTableData = data?.fieldMapping?.filter((field: FieldMapType) => field?.otherCmsType !== undefined); - + setIsAllCheck(true); setTableData(validTableData ?? []); setSelectedEntries(validTableData ?? []); setTotalCounts(validTableData?.length); @@ -717,7 +721,8 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R // eslint-disable-next-line no-unsafe-optional-chaining setTableData([...tableData, ...validTableData ?? tableData]); setTotalCounts([...tableData, ...validTableData ?? tableData]?.length); - setIsLoading(false) + setIsLoading(false); + setIsAllCheck(true); } catch (error) { console.error('loadMoreItems -> error', error); } @@ -749,6 +754,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R }; const openContentType = (i: number) => { + setIsAllCheck(true); setIsFieldDeleted(false); setActive(i); const otherTitle = filteredContentTypes?.[i]?.contentstackUid; @@ -821,24 +827,26 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R // add row ids with their data to rowHistoryObj useEffect(() => { + setIsAllCheck(false); Object.keys(rowHistoryObj)?.forEach(key => delete rowHistoryObj[key]); tableData?.forEach(item => { rowHistoryObj[item?.id] = [{checked: true, at: Date.now(), ...modifiedObj(item)}] }); }, [tableData]); - const getParentId = (uid: string) => { - return tableData?.find(i => i?.uid?.toLowerCase() === uid?.toLowerCase())?.id ?? '' + const getParentId = (uid: string) => { + return tableData?.find((i) => i?.uid?.toLowerCase() === uid?.toLowerCase() && i?.backupFieldType?.toLowerCase() === 'group')?.id ?? '' } const modifiedObj = (obj: FieldMapType) => { - const {backupFieldType, uid, id} = obj ?? {} + const {backupFieldType, uid, id, _canSelect} = obj ?? {} const excludeArr = ["group"] return { id, backupFieldType, uid, - parentId : excludeArr?.includes?.(backupFieldType?.toLowerCase()) ? '' : getParentId(uid?.split('.')[0]?.toLowerCase()) + parentId : excludeArr?.includes?.(backupFieldType?.toLowerCase()) ? '' : getParentId(uid?.split('.')[0]?.toLowerCase()), + _canSelect, } } @@ -886,7 +894,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R const handleSelectedEntries = (singleSelectedRowIds: string[]) => { const selectedObj: UidMap = {}; - + setIsAllCheck(false); singleSelectedRowIds?.forEach((uid: string) => { const isId = selectedEntries?.some((item) => item?.id === uid); if (isId) { @@ -940,7 +948,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R }) } } - } else if(latestRow?.parentId && !["title", "url"]?.includes?.(latestRow?.uid?.toLowerCase())){ + } else if(latestRow?.parentId && latestRow?._canSelect === true){ // Extract the group UID if item is child of any group const uidBeforeDot = latestRow?.uid?.split?.('.')?.[0]?.toLowerCase(); const groupItem = tableData?.find((entry) => entry?.uid?.toLowerCase() === uidBeforeDot); @@ -972,7 +980,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R } } - const updatedTableData = tableData?.map?.((tableItem) => { + const updatedTableData = selectedEntries?.map?.((tableItem) => { // Mark the item as deleted if not found in selectedData return { ...tableItem, @@ -988,6 +996,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R const handleValueChange = (value: FieldTypes, rowIndex: string, rowContentstackFieldUid: string) => { setIsDropDownChanged(true); setFieldValue(value); + setIsAllCheck(false); const updatedRows: FieldMapType[] = selectedEntries?.map?.((row) => { if (row?.uid === rowIndex && row?.contentstackFieldUid === rowContentstackFieldUid) { return { ...row, contentstackFieldType: value?.value }; @@ -1010,7 +1019,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R const handleDropDownChange = (value: FieldTypes) => { (value?.id !== otherContentType?.id) && setsCsCTypeUpdated(true); - + setIsAllCheck(false); setOtherContentType(value); }; @@ -1503,6 +1512,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R if (!updatedSelectedOptions?.includes?.(newValue)) { updatedSelectedOptions.push(newValue); } + setIsAllCheck(false); setIsUpdated(true); } @@ -1620,6 +1630,7 @@ const ContentMapper = forwardRef(({handleStepChange}: contentMapperProps, ref: R const handleSaveContentType = async () => { setisLoadingSaveButton(true); + setIsAllCheck(false); const orgId = selectedOrganisation?.uid; const projectID = projectId; if ( diff --git a/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx b/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx index b6511a89..b1ca756f 100644 --- a/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx +++ b/ui/src/components/DestinationStack/Actions/LoadLanguageMapper.tsx @@ -480,7 +480,7 @@ const LanguageMapper = ({stack, uid} :{ stack : IDropDown, uid : string}) => { const [cmsLocaleOptions, setcmsLocaleOptions] = useState<{ label: string; value: string }[]>([]); const [sourceLocales, setsourceLocales] = useState<{ label: string; value: string }[]>([]); const [isLoading, setisLoading] = useState(false); - const [currentStack, setCurrentStack] = useState(); + const [currentStack, setCurrentStack] = useState(stack); const [previousStack, setPreviousStack] = useState(); const [isStackChanged, setisStackChanged] = useState(false); const [stackValue, setStackValue] = useState(stack?.value) diff --git a/ui/src/components/DestinationStack/Actions/LoadStacks.tsx b/ui/src/components/DestinationStack/Actions/LoadStacks.tsx index 4c8a2060..39082685 100644 --- a/ui/src/components/DestinationStack/Actions/LoadStacks.tsx +++ b/ui/src/components/DestinationStack/Actions/LoadStacks.tsx @@ -86,6 +86,7 @@ const LoadStacks = (props: LoadFileFormatProps) => { const [placeholder] = useState('Select a stack'); const [localePlaceholder, setlocalePlaceholder ] = useState('Master Locale will be set after stack selection'); const newMigrationDataRef = useRef(newMigrationData); + const [isStackLoading, setIsStackLoading] = useState(true); useEffect(() => { newMigrationDataRef.current = newMigrationData; @@ -111,6 +112,7 @@ const LoadStacks = (props: LoadFileFormatProps) => { const handleOnSave = async (data: Stack) => { try { // Post data to backend + setIsStackLoading(true); const resp = await createStacksInOrg(selectedOrganisation?.value, { ...data, master_locale: data?.locale @@ -153,6 +155,7 @@ const LoadStacks = (props: LoadFileFormatProps) => { }; dispatch(updateNewMigrationData(newMigrationDataObj)); + setIsStackLoading(false); // call for Step Change props.handleStepChange(props?.currentStep, true); @@ -357,7 +360,7 @@ const LoadStacks = (props: LoadFileFormatProps) => { - {newMigrationData?.destination_stack?.selectedStack?.value && ( + {newMigrationData?.destination_stack?.selectedStack?.value && (!isEmptyString(newMigrationData?.destination_stack?.selectedStack?.value) || !isStackLoading) &&(
Language Mapping
@@ -375,7 +378,7 @@ const LoadStacks = (props: LoadFileFormatProps) => {
+ stack={newMigrationData?.destination_stack?.selectedStack ?? DEFAULT_DROPDOWN} />
)} diff --git a/ui/src/components/DestinationStack/index.tsx b/ui/src/components/DestinationStack/index.tsx index 6e6ace5e..b5721217 100644 --- a/ui/src/components/DestinationStack/index.tsx +++ b/ui/src/components/DestinationStack/index.tsx @@ -101,7 +101,7 @@ const DestinationStackComponent = ({ },[newMigrationData?.isprojectMapped]); useEffect(()=>{ - if(! isEmptyString(newMigrationData?.destination_stack?.selectedStack?.value ) + if(!isEmptyString(newMigrationData?.destination_stack?.selectedStack?.value ) ){ handleAllStepsComplete(true); } diff --git a/ui/src/components/LegacyCms/Actions/LoadUploadFile.tsx b/ui/src/components/LegacyCms/Actions/LoadUploadFile.tsx index f5927d09..03335f95 100644 --- a/ui/src/components/LegacyCms/Actions/LoadUploadFile.tsx +++ b/ui/src/components/LegacyCms/Actions/LoadUploadFile.tsx @@ -26,7 +26,7 @@ interface UploadState { fileFormat?: string; isConfigLoading: boolean; isLoading: boolean; - isValidated: boolean; + isValidated?: boolean; isDisabled?: boolean; processing: string; progressPercentage: number; @@ -190,7 +190,7 @@ const LoadUploadFile = (props: LoadUploadFileProps) => { { isLoading, isConfigLoading, - isValidated, + //isValidated, validationMessgae, isDisabled, cmsType, @@ -290,7 +290,7 @@ const LoadUploadFile = (props: LoadUploadFileProps) => { if (savedState) { setIsLoading(savedState.isLoading); setIsConfigLoading(savedState.isConfigLoading); - setIsValidated(savedState?.isValidated); + //setIsValidated(savedState?.isValidated); setValidationMessage(savedState?.validationMessage); //setIsDisabled(savedState?.isDisabled); setCmsType(savedState.cmsType); @@ -315,7 +315,7 @@ const LoadUploadFile = (props: LoadUploadFileProps) => { { isLoading, isConfigLoading, - isValidated, + //isValidated, validationMessgae, //isDisabled, cmsType, @@ -331,7 +331,7 @@ const LoadUploadFile = (props: LoadUploadFileProps) => { }, [ isLoading, isConfigLoading, - isValidated, + //isValidated, validationMessgae, //isDisabled, cmsType, diff --git a/ui/src/hooks/useWarnOnrefresh.tsx b/ui/src/hooks/useWarnOnrefresh.tsx new file mode 100644 index 00000000..ec4acbf9 --- /dev/null +++ b/ui/src/hooks/useWarnOnrefresh.tsx @@ -0,0 +1,18 @@ +import { useEffect } from "react"; + +export function useWarnOnRefresh(isUnsaved : boolean){ + useEffect(() => { + const handleBeforeUnload = (e: BeforeUnloadEvent) => { + if (isUnsaved) { + e.preventDefault(); + e.returnValue = ''; + } + }; + + window.addEventListener('beforeunload', handleBeforeUnload); + + return () => { + window.removeEventListener('beforeunload', handleBeforeUnload); + }; +}, [isUnsaved]); +} \ No newline at end of file diff --git a/ui/src/pages/Migration/index.tsx b/ui/src/pages/Migration/index.tsx index 2666f0e9..1d5be5d9 100644 --- a/ui/src/pages/Migration/index.tsx +++ b/ui/src/pages/Migration/index.tsx @@ -7,6 +7,7 @@ import { cbModal, Notification } from '@contentstack/venus-components'; // Redux files import { RootState } from '../../store'; import { updateMigrationData, updateNewMigrationData } from '../../store/slice/migrationDataSlice'; +import { useWarnOnRefresh } from '../../hooks/useWarnOnrefresh'; // Services import { @@ -100,10 +101,38 @@ const Migration = () => { const saveRef = useRef(null); const newMigrationDataRef = useRef(newMigrationData); + const [isSaved, setIsSaved] = useState(false); + useEffect(() => { fetchData(); }, [params?.stepId, params?.projectId, selectedOrganisation?.value]); + useWarnOnRefresh(isSaved); + + useEffect(()=>{ + const hasNonEmptyMapping = + newMigrationData?.destination_stack?.localeMapping && + Object.entries(newMigrationData?.destination_stack?.localeMapping || {})?.every( + ([label, value]: [string, string]) => + Boolean(label?.trim()) && + value !== '' && + value !== null && + value !== undefined + ); + if(legacyCMSRef?.current && !isCompleted && newMigrationData?.project_current_step === 1){ + setIsSaved(true); + } + else if ((isCompleted && !isEmptyString(newMigrationData?.destination_stack?.selectedStack?.value) && newMigrationData?.project_current_step === 2)){ + setIsSaved(true); + } + else if(newMigrationData?.content_mapping?.isDropDownChanged){ + setIsSaved(true); + } + else{ + setIsSaved(false); + } + },[isCompleted, newMigrationData]) + /** * Dispatches the isprojectMapped key to redux */ @@ -220,7 +249,7 @@ const Migration = () => { const newMigrationDataObj = { name: data?.localPath, url: data?.localPath, - isValidated: data?.localPath !== newMigrationData?.legacy_cms?.uploadedFile?.file_details?.localPath ? false : newMigrationData?.legacy_cms?.uploadedFile?.isValidated, + isValidated: false, file_details: { isLocalPath: data?.isLocalPath, cmsType: data?.cmsType,