diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d16acc8a4..66e13b4a47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to - ♻️(frontend) redirect to doc after duplicate #1175 - 🔧(project) change env.d system by using local files #1200 +- ⚡️(frontend) improve tree stability #1207 ### Fixed diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx index 4d53835a6d..b54e710d2b 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx @@ -1,7 +1,7 @@ /* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ +import { useTreeContext } from '@gouvfr-lasuite/ui-kit'; import { Tooltip } from '@openfun/cunningham-react'; -import { useQueryClient } from '@tanstack/react-query'; import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; @@ -12,7 +12,6 @@ import { Doc, KEY_DOC, KEY_LIST_DOC, - KEY_SUB_PAGE, useDocStore, useTrans, useUpdateDoc, @@ -50,10 +49,10 @@ export const DocTitleText = () => { const DocTitleInput = ({ doc }: DocTitleProps) => { const { isDesktop } = useResponsiveStore(); - const queryClient = useQueryClient(); const { t } = useTranslation(); const { colorsTokens } = useCunninghamTheme(); const [titleDisplay, setTitleDisplay] = useState(doc.title); + const treeContext = useTreeContext(); const { untitledDocument } = useTrans(); @@ -64,10 +63,16 @@ const DocTitleInput = ({ doc }: DocTitleProps) => { onSuccess(updatedDoc) { // Broadcast to every user connected to the document broadcast(`${KEY_DOC}-${updatedDoc.id}`); - queryClient.setQueryData( - [KEY_SUB_PAGE, { id: updatedDoc.id }], - updatedDoc, - ); + + if (!treeContext) { + return; + } + + if (treeContext.root?.id === updatedDoc.id) { + treeContext?.setRoot(updatedDoc); + } else { + treeContext?.treeData.updateNode(updatedDoc.id, updatedDoc); + } }, }); diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDoc.tsx index 5365ad4d90..4fd6e07ffc 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDoc.tsx @@ -19,7 +19,6 @@ export const getDoc = async ({ id }: DocParams): Promise => { }; export const KEY_DOC = 'doc'; -export const KEY_SUB_PAGE = 'sub-page'; export const KEY_DOC_VISIBILITY = 'doc-visibility'; export function useDoc( diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocRoleDropdown.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocRoleDropdown.tsx index 4f03f9c95b..fb69453cdc 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocRoleDropdown.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocRoleDropdown.tsx @@ -1,17 +1,10 @@ import { VariantType, useToastProvider } from '@openfun/cunningham-react'; -import { useQueryClient } from '@tanstack/react-query'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; import { DropdownMenu, DropdownMenuOption, Text } from '@/components'; -import { - Access, - Doc, - KEY_SUB_PAGE, - Role, - useTrans, -} from '@/docs/doc-management/'; +import { Access, Doc, Role, useTrans } from '@/docs/doc-management/'; import { useDeleteDocAccess, useDeleteDocInvitation } from '../api'; import { Invitation, isInvitation } from '../types'; @@ -39,19 +32,9 @@ export const DocRoleDropdown = ({ }: DocRoleDropdownProps) => { const { t } = useTranslation(); const { transRole, translatedRoles } = useTrans(); - const queryClient = useQueryClient(); const { toast } = useToastProvider(); const { mutate: removeDocInvitation } = useDeleteDocInvitation({ - onSuccess: () => { - if (!doc) { - return; - } - - void queryClient.invalidateQueries({ - queryKey: [KEY_SUB_PAGE, { id: doc.id }], - }); - }, onError: (error) => { toast( error?.data?.role?.[0] ?? t('Error during delete invitation'), @@ -64,14 +47,6 @@ export const DocRoleDropdown = ({ }); const { mutate: removeDocAccess } = useDeleteDocAccess({ - onSuccess: () => { - if (!doc) { - return; - } - void queryClient.invalidateQueries({ - queryKey: [KEY_SUB_PAGE, { id: doc.id }], - }); - }, onError: () => { toast(t('Error while deleting invitation'), VariantType.ERROR, { duration: 4000, diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAddMemberList.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAddMemberList.tsx index 2e1df3aaeb..351dca4a59 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAddMemberList.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareAddMemberList.tsx @@ -3,7 +3,6 @@ import { VariantType, useToastProvider, } from '@openfun/cunningham-react'; -import { useQueryClient } from '@tanstack/react-query'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; @@ -11,7 +10,7 @@ import { css } from 'styled-components'; import { APIError } from '@/api'; import { Box } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; -import { Doc, KEY_SUB_PAGE, Role } from '@/docs/doc-management'; +import { Doc, Role } from '@/docs/doc-management'; import { User } from '@/features/auth'; import { useCreateDocAccess, useCreateDocInvitation } from '../api'; @@ -45,7 +44,6 @@ export const DocShareAddMemberList = ({ const { spacingsTokens, colorsTokens } = useCunninghamTheme(); const [invitationRole, setInvitationRole] = useState(Role.EDITOR); const canShare = doc.abilities.accesses_manage; - const queryClient = useQueryClient(); const { mutateAsync: createInvitation } = useCreateDocInvitation(); const { mutateAsync: createDocAccess } = useCreateDocAccess(); @@ -91,32 +89,14 @@ export const DocShareAddMemberList = ({ }; return isInvitationMode - ? createInvitation( - { - ...payload, - email: user.email, - }, - { - onSuccess: () => { - void queryClient.invalidateQueries({ - queryKey: [KEY_SUB_PAGE, { id: doc.id }], - }); - }, - }, - ) - : createDocAccess( - { - ...payload, - memberId: user.id, - }, - { - onSuccess: () => { - void queryClient.invalidateQueries({ - queryKey: [KEY_SUB_PAGE, { id: doc.id }], - }); - }, - }, - ); + ? createInvitation({ + ...payload, + email: user.email, + }) + : createDocAccess({ + ...payload, + memberId: user.id, + }); }); const settledPromises = await Promise.allSettled(promises); diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareInvitation.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareInvitation.tsx index 3e959f61f4..c51e138e72 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareInvitation.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareInvitation.tsx @@ -1,5 +1,4 @@ import { VariantType, useToastProvider } from '@openfun/cunningham-react'; -import { useQueryClient } from '@tanstack/react-query'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; @@ -15,7 +14,7 @@ import { } from '@/components'; import { QuickSearchData, QuickSearchGroup } from '@/components/quick-search'; import { useCunninghamTheme } from '@/cunningham'; -import { Doc, KEY_SUB_PAGE, Role } from '@/docs/doc-management'; +import { Doc, Role } from '@/docs/doc-management'; import { User } from '@/features/auth'; import { @@ -38,7 +37,6 @@ export const DocShareInvitationItem = ({ invitation, }: DocShareInvitationItemProps) => { const { t } = useTranslation(); - const queryClient = useQueryClient(); const { spacingsTokens } = useCunninghamTheme(); const invitedUser: User = { id: invitation.email, @@ -52,11 +50,6 @@ export const DocShareInvitationItem = ({ const canUpdate = doc.abilities.accesses_manage; const { mutate: updateDocInvitation } = useUpdateDocInvitation({ - onSuccess: () => { - void queryClient.invalidateQueries({ - queryKey: [KEY_SUB_PAGE, { id: doc.id }], - }); - }, onError: (error) => { toast( error?.data?.role?.[0] ?? t('Error during update invitation'), @@ -69,11 +62,6 @@ export const DocShareInvitationItem = ({ }); const { mutate: removeDocInvitation } = useDeleteDocInvitation({ - onSuccess: () => { - void queryClient.invalidateQueries({ - queryKey: [KEY_SUB_PAGE, { id: doc.id }], - }); - }, onError: (error) => { toast( error?.data?.role?.[0] ?? t('Error during delete invitation'), diff --git a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareMember.tsx b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareMember.tsx index c258a3b877..fa20882772 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareMember.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-share/components/DocShareMember.tsx @@ -1,5 +1,4 @@ import { VariantType, useToastProvider } from '@openfun/cunningham-react'; -import { useQueryClient } from '@tanstack/react-query'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -7,7 +6,7 @@ import { Box } from '@/components'; import { QuickSearchData } from '@/components/quick-search'; import { QuickSearchGroup } from '@/components/quick-search/QuickSearchGroup'; import { useCunninghamTheme } from '@/cunningham'; -import { Access, Doc, KEY_SUB_PAGE, Role } from '@/docs/doc-management/'; +import { Access, Doc, Role } from '@/docs/doc-management/'; import { useDocAccesses, useUpdateDocAccess } from '../api'; import { useWhoAmI } from '../hooks/'; @@ -26,7 +25,6 @@ export const DocShareMemberItem = ({ isInherited = false, }: Props) => { const { t } = useTranslation(); - const queryClient = useQueryClient(); const { isLastOwner } = useWhoAmI(access); const { toast } = useToastProvider(); @@ -39,14 +37,6 @@ export const DocShareMemberItem = ({ : undefined; const { mutate: updateDocAccess } = useUpdateDocAccess({ - onSuccess: () => { - if (!doc) { - return; - } - void queryClient.invalidateQueries({ - queryKey: [KEY_SUB_PAGE, { id: doc.id }], - }); - }, onError: () => { toast(t('Error while updating the member role.'), VariantType.ERROR, { duration: 4000, diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/api/useDocTree.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/api/useDocTree.tsx index c5501cf528..083bccc274 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-tree/api/useDocTree.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/api/useDocTree.tsx @@ -9,11 +9,7 @@ export type DocsTreeParams = { }; export const getDocTree = async ({ docId }: DocsTreeParams): Promise => { - const searchParams = new URLSearchParams(); - - const response = await fetchAPI( - `documents/${docId}/tree/?${searchParams.toString()}`, - ); + const response = await fetchAPI(`documents/${docId}/tree/`); if (!response.ok) { throw new APIError( @@ -29,10 +25,7 @@ export const KEY_DOC_TREE = 'doc-tree'; export function useDocTree( params: DocsTreeParams, - queryConfig?: Omit< - UseQueryOptions, - 'queryKey' | 'queryFn' - >, + queryConfig?: UseQueryOptions, ) { return useQuery({ queryKey: [KEY_DOC_TREE, params], diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx index 8c72e31bc7..273db720ab 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocSubPageItem.tsx @@ -4,17 +4,12 @@ import { useTreeContext, } from '@gouvfr-lasuite/ui-kit'; import { useRouter } from 'next/navigation'; -import { useEffect, useRef, useState } from 'react'; +import { useState } from 'react'; import { css } from 'styled-components'; import { Box, Icon, Text } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; -import { - Doc, - KEY_SUB_PAGE, - useDoc, - useTrans, -} from '@/features/docs/doc-management'; +import { Doc, useTrans } from '@/features/docs/doc-management'; import { useLeftPanelStore } from '@/features/left-panel'; import { useResponsiveStore } from '@/stores'; @@ -31,8 +26,7 @@ const ItemTextCss = css` -webkit-box-orient: vertical; `; -type Props = TreeViewNodeProps; -export const DocSubPageItem = (props: Props) => { +export const DocSubPageItem = (props: TreeViewNodeProps) => { const doc = props.node.data.value as Doc; const treeContext = useTreeContext(); const { untitledDocument } = useTrans(); @@ -44,28 +38,6 @@ export const DocSubPageItem = (props: Props) => { const router = useRouter(); const { togglePanel } = useLeftPanelStore(); - const isInitialLoad = useRef(false); - const { data: docQuery } = useDoc( - { id: doc.id }, - { - initialData: doc, - queryKey: [KEY_SUB_PAGE, { id: doc.id }], - refetchOnMount: false, - refetchOnWindowFocus: false, - }, - ); - - useEffect(() => { - if (docQuery && isInitialLoad.current === true) { - treeContext?.treeData.updateNode(docQuery.id, docQuery); - } - - if (docQuery) { - isInitialLoad.current = true; - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [docQuery]); - const afterCreate = (createdDoc: Doc) => { const actualChildren = node.data.children ?? []; diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx index bee2f48533..ec6817b58c 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx @@ -5,52 +5,46 @@ import { useTreeContext, } from '@gouvfr-lasuite/ui-kit'; import { useRouter } from 'next/navigation'; -import { useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { css } from 'styled-components'; import { Box, StyledLink } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; -import { Doc, KEY_SUB_PAGE, useDoc, useDocStore } from '@/docs/doc-management'; +import { Doc } from '@/docs/doc-management'; import { SimpleDocItem } from '@/docs/docs-grid'; -import { useDocTree } from '../api/useDocTree'; +import { KEY_DOC_TREE, useDocTree } from '../api/useDocTree'; import { useMoveDoc } from '../api/useMove'; +import { findIndexInTree } from '../utils'; import { DocSubPageItem } from './DocSubPageItem'; import { DocTreeItemActions } from './DocTreeItemActions'; type DocTreeProps = { - initialTargetId: string; + currentDoc: Doc; }; -export const DocTree = ({ initialTargetId }: DocTreeProps) => { + +export const DocTree = ({ currentDoc }: DocTreeProps) => { const { spacingsTokens } = useCunninghamTheme(); const [rootActionsOpen, setRootActionsOpen] = useState(false); - const treeContext = useTreeContext(); - const { currentDoc } = useDocStore(); + const treeContext = useTreeContext(); const router = useRouter(); - const previousDocId = useRef(initialTargetId); - - const { data: rootNode } = useDoc( - { id: treeContext?.root?.id ?? '' }, - { - enabled: !!treeContext?.root?.id, - initialData: treeContext?.root ?? undefined, - queryKey: [KEY_SUB_PAGE, { id: treeContext?.root?.id ?? '' }], - refetchOnMount: false, - refetchOnWindowFocus: false, - }, - ); - const [initialOpenState, setInitialOpenState] = useState( undefined, ); const { mutate: moveDoc } = useMoveDoc(); - const { data } = useDocTree({ - docId: initialTargetId, - }); + const { data: tree, isFetching } = useDocTree( + { + docId: currentDoc.id, + }, + { + enabled: !!!treeContext?.root?.id, + queryKey: [KEY_DOC_TREE, { id: currentDoc.id }], + }, + ); const handleMove = (result: TreeViewMoveResult) => { moveDoc({ @@ -61,12 +55,55 @@ export const DocTree = ({ initialTargetId }: DocTreeProps) => { treeContext?.treeData.handleMove(result); }; + /** + * This function resets the tree states. + */ + const resetStateTree = useCallback(() => { + if (!treeContext?.root?.id) { + return; + } + + treeContext?.setRoot(null); + setInitialOpenState(undefined); + }, [treeContext]); + + /** + * This effect is used to reset the tree when a new document + * that is not part of the current tree is loaded. + */ + useEffect(() => { + if (!treeContext?.root?.id) { + return; + } + + const index = findIndexInTree(treeContext.treeData.nodes, currentDoc.id); + if (index === -1 && currentDoc.id !== treeContext.root?.id) { + resetStateTree(); + return; + } + }, [currentDoc, resetStateTree, treeContext]); + + /** + * This effect is used to reset the tree when the component is unmounted. + */ + useEffect(() => { + return () => { + resetStateTree(); + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + /** + * This effect is used to set the initial open state of the tree when the tree is loaded. + * If the treeContext is already set, we do not need to set it again. + */ useEffect(() => { - if (!data) { + if (!tree || treeContext?.root?.id || isFetching) { return; } - const { children: rootChildren, ...root } = data; + const { children: rootChildren, ...root } = tree; const children = rootChildren ?? []; treeContext?.setRoot(root); const initialOpenState: OpenMap = {}; @@ -84,50 +121,30 @@ export const DocTree = ({ initialTargetId }: DocTreeProps) => { treeContext?.treeData.resetTree(children); setInitialOpenState(initialOpenState); - if (initialTargetId === root.id) { - treeContext?.treeData.setSelectedNode(root); - } else { - treeContext?.treeData.selectNodeById(initialTargetId); - } - - // Because treeData change in the treeContext, we have a infinite loop - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [data, initialTargetId]); + }, [tree, treeContext, isFetching]); + /** + * This effect is used to select the current document in the tree + */ useEffect(() => { - if ( - !currentDoc || - (previousDocId.current && previousDocId.current === currentDoc.id) - ) { + if (!treeContext || !treeContext.root?.id) { return; } - const item = treeContext?.treeData.getNode(currentDoc?.id ?? ''); - if (!item && currentDoc.id !== rootNode?.id) { - treeContext?.treeData.resetTree([]); - treeContext?.setRoot(currentDoc); - treeContext?.setInitialTargetId(currentDoc.id); - } else if (item) { - const { children: _children, ...leftDoc } = currentDoc; - treeContext?.treeData.updateNode(currentDoc.id, { - ...leftDoc, - childrenCount: leftDoc.numchild, - }); - } - if (currentDoc?.id && currentDoc?.id !== previousDocId.current) { - previousDocId.current = currentDoc?.id; + if (currentDoc.id === treeContext?.root?.id) { + treeContext?.treeData.setSelectedNode(treeContext?.root); + } else { + treeContext?.treeData.selectNodeById(currentDoc.id); } + }, [currentDoc, treeContext]); - treeContext?.treeData.setSelectedNode(currentDoc); - }, [currentDoc, rootNode?.id, treeContext]); - - const rootIsSelected = - treeContext?.treeData.selectedNode?.id === treeContext?.root?.id; - - if (!initialTargetId || !treeContext) { + if (!treeContext || !treeContext.root) { return null; } + const rootIsSelected = + treeContext.treeData.selectedNode?.id === treeContext.root.id; + return ( { } `} > - {treeContext.root !== null && rootNode && ( - { - e.stopPropagation(); - e.preventDefault(); - treeContext.treeData.setSelectedNode( - treeContext.root ?? undefined, - ); - router.push(`/docs/${treeContext?.root?.id}`); - }} - > - - -
- { - const newDoc = { - ...createdDoc, - children: [], - childrenCount: 0, - parentId: treeContext.root?.id ?? undefined, - }; - treeContext?.treeData.addChild(null, newDoc); - }} - isOpen={rootActionsOpen} - onOpenChange={setRootActionsOpen} - /> -
-
-
- )} + { + e.stopPropagation(); + e.preventDefault(); + treeContext.treeData.setSelectedNode( + treeContext.root ?? undefined, + ); + router.push(`/docs/${treeContext?.root?.id}`); + }} + > + + + { + const newDoc = { + ...createdDoc, + children: [], + childrenCount: 0, + parentId: treeContext.root?.id ?? undefined, + }; + treeContext?.treeData.addChild(null, newDoc); + }} + isOpen={rootActionsOpen} + onOpenChange={setRootActionsOpen} + /> + +
@@ -227,20 +240,17 @@ export const DocTree = ({ initialTargetId }: DocTreeProps) => { undefined } canDrop={({ parentNode }) => { - if (!rootNode) { - return false; - } const parentDoc = parentNode?.data.value as Doc; if (!parentDoc) { - return rootNode?.abilities.move; + return currentDoc.abilities.move; } - return parentDoc?.abilities.move; + return parentDoc.abilities.move; }} canDrag={(node) => { const doc = node.value as Doc; return doc.abilities.move; }} - rootNodeId={treeContext.root?.id ?? ''} + rootNodeId={treeContext.root.id} renderNode={DocSubPageItem} /> )} diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTreeItemActions.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTreeItemActions.tsx index 1d8d8a9248..0589f9de30 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTreeItemActions.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTreeItemActions.tsx @@ -5,7 +5,6 @@ import { } from '@gouvfr-lasuite/ui-kit'; import { useModal } from '@openfun/cunningham-react'; import { useRouter } from 'next/router'; -import { Fragment } from 'react'; import { useTranslation } from 'react-i18next'; import { css } from 'styled-components'; @@ -25,9 +24,9 @@ import { useTreeUtils } from '../hooks'; type DocTreeItemActionsProps = { doc: Doc; + isOpen?: boolean; parentId?: string | null; onCreateSuccess?: (newDoc: Doc) => void; - isOpen?: boolean; onOpenChange?: (isOpen: boolean) => void; }; @@ -45,10 +44,12 @@ export const DocTreeItemActions = ({ const copyLink = useCopyDocLink(doc.id); const { isCurrentParent } = useTreeUtils(doc); const { mutate: detachDoc } = useDetachDoc(); - const treeContext = useTreeContext(); + const treeContext = useTreeContext(); const { mutate: duplicateDoc } = useDuplicateDoc({ - onSuccess: (data) => { - void router.push(`/docs/${data.id}`); + onSuccess: (duplicatedDoc) => { + // Reset the tree context root will reset the full tree view. + treeContext?.setRoot(null); + void router.push(`/docs/${duplicatedDoc.id}`); }, }); @@ -61,10 +62,13 @@ export const DocTreeItemActions = ({ { documentId: doc.id, rootId: treeContext.root.id }, { onSuccess: () => { - treeContext.treeData.deleteNode(doc.id); if (treeContext.root) { treeContext.treeData.setSelectedNode(treeContext.root); - void router.push(`/docs/${treeContext.root.id}`); + void router.push(`/docs/${treeContext.root.id}`).then(() => { + setTimeout(() => { + treeContext?.treeData.deleteNode(doc.id); + }, 100); + }); } }, }, @@ -124,18 +128,24 @@ export const DocTreeItemActions = ({ const afterDelete = () => { if (parentId) { - treeContext?.treeData.deleteNode(doc.id); - void router.push(`/docs/${parentId}`); + void router.push(`/docs/${parentId}`).then(() => { + setTimeout(() => { + treeContext?.treeData.deleteNode(doc.id); + }, 100); + }); } else if (doc.id === treeContext?.root?.id && !parentId) { void router.push(`/docs/`); } else if (treeContext && treeContext.root) { - treeContext?.treeData.deleteNode(doc.id); - void router.push(`/docs/${treeContext.root.id}`); + void router.push(`/docs/${treeContext.root.id}`).then(() => { + setTimeout(() => { + treeContext?.treeData.deleteNode(doc.id); + }, 100); + }); } }; return ( - + )} - + ); }; diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/components/index.ts b/src/frontend/apps/impress/src/features/docs/doc-tree/components/index.ts new file mode 100644 index 0000000000..29f1533289 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/components/index.ts @@ -0,0 +1 @@ +export * from './DocTree'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/index.ts b/src/frontend/apps/impress/src/features/docs/doc-tree/index.ts index 608f00da51..ec8b404339 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-tree/index.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/index.ts @@ -1,3 +1,4 @@ export * from './api'; +export * from './components'; export * from './hooks'; export * from './utils'; diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/utils.ts b/src/frontend/apps/impress/src/features/docs/doc-tree/utils.ts index 789e9211f5..510baa90bb 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-tree/utils.ts +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/utils.ts @@ -1,4 +1,4 @@ -import { TreeViewDataType } from '@gouvfr-lasuite/ui-kit'; +import { TreeDataItem, TreeViewDataType } from '@gouvfr-lasuite/ui-kit'; import { Doc } from '../doc-management'; @@ -9,3 +9,24 @@ export const subPageToTree = (children: Doc[]): TreeViewDataType[] => { }); return children; }; + +export const findIndexInTree = ( + nodes: TreeDataItem>[], + key: string, +) => { + for (let i = 0; i < nodes.length; i++) { + if (nodes[i].key === key) { + return i; + } + if (nodes[i].children?.length ?? 0 > 0) { + const childIndex: number = nodes[i].children + ? findIndexInTree(nodes[i].children ?? [], key) + : -1; + + if (childIndex !== -1) { + return childIndex; + } + } + } + return -1; +}; diff --git a/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelDocContent.tsx b/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelDocContent.tsx index 928af5ec37..6e67f8a987 100644 --- a/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelDocContent.tsx +++ b/src/frontend/apps/impress/src/features/left-panel/components/LeftPanelDocContent.tsx @@ -2,7 +2,7 @@ import { useTreeContext } from '@gouvfr-lasuite/ui-kit'; import { Box } from '@/components'; import { Doc, useDocStore } from '@/docs/doc-management'; -import { DocTree } from '@/features/docs/doc-tree/components/DocTree'; +import { DocTree } from '@/docs/doc-tree/'; export const LeftPanelDocContent = () => { const { currentDoc } = useDocStore(); @@ -20,9 +20,7 @@ export const LeftPanelDocContent = () => { $css="width: 100%; overflow-y: auto; overflow-x: hidden;" className="--docs--left-panel-doc-content" > - {tree.initialTargetId && ( - - )} + ); };