Skip to content

Commit c421d9f

Browse files
committed
✨(frontend) Move doc modal
We can now move a doc to another doc from a search modal. It will make it easier to move a doc without having to scroll through the doc grid to find the destination doc. We kept most of the logic implemented in the doc grid dnd.
1 parent 749484e commit c421d9f

File tree

16 files changed

+566
-214
lines changed

16 files changed

+566
-214
lines changed

src/frontend/apps/impress/src/components/quick-search/QuickSearch.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export const QuickSearch = ({
3636
onFilter,
3737
inputContent,
3838
inputValue,
39-
loading,
4039
showInput = true,
4140
label,
4241
placeholder,
@@ -75,7 +74,6 @@ export const QuickSearch = ({
7574
>
7675
{showInput && (
7776
<QuickSearchInput
78-
loading={loading}
7977
withSeparator={hasChildrens(children)}
8078
inputValue={inputValue}
8179
onFilter={onFilter}

src/frontend/apps/impress/src/components/quick-search/QuickSearchInput.tsx

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { Loader } from '@gouvfr-lasuite/cunningham-react';
21
import { Command } from 'cmdk';
3-
import { ReactNode } from 'react';
2+
import { PropsWithChildren } from 'react';
43
import { useTranslation } from 'react-i18next';
54

65
import { HorizontalSeparator } from '@/components';
@@ -9,19 +8,16 @@ import { useCunninghamTheme } from '@/cunningham';
98
import { Box } from '../Box';
109
import { Icon } from '../Icon';
1110

12-
type Props = {
13-
loading?: boolean;
11+
type QuickSearchInputProps = {
1412
inputValue?: string;
1513
onFilter?: (str: string) => void;
1614
placeholder?: string;
17-
children?: ReactNode;
1815
withSeparator?: boolean;
1916
listId?: string;
2017
onUserInteract?: () => void;
2118
isExpanded?: boolean;
2219
};
2320
export const QuickSearchInput = ({
24-
loading,
2521
inputValue,
2622
onFilter,
2723
placeholder,
@@ -30,7 +26,7 @@ export const QuickSearchInput = ({
3026
listId,
3127
onUserInteract,
3228
isExpanded,
33-
}: Props) => {
29+
}: PropsWithChildren<QuickSearchInputProps>) => {
3430
const { t } = useTranslation();
3531
const { spacingsTokens } = useCunninghamTheme();
3632

@@ -52,14 +48,7 @@ export const QuickSearchInput = ({
5248
$gap={spacingsTokens['2xs']}
5349
$padding={{ horizontal: 'base', vertical: 'sm' }}
5450
>
55-
{!loading && (
56-
<Icon iconName="search" $variation="secondary" aria-hidden="true" />
57-
)}
58-
{loading && (
59-
<div>
60-
<Loader size="small" />
61-
</div>
62-
)}
51+
<Icon iconName="search" $variation="secondary" aria-hidden="true" />
6352
<Command.Input
6453
autoFocus={true}
6554
aria-label={t('Quick search input')}

src/frontend/apps/impress/src/components/quick-search/QuickSearchStyle.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const QuickSearchStyle = createGlobalStyle`
2222
padding: var(--c--globals--spacings--xs);
2323
background: white;
2424
outline: none;
25-
color: var(--c--globals--colors--gray-1000);
25+
color: var(--c--contextuals--content--semantic--neutral--primary);
2626
border-radius: var(--c--globals--spacings--0);
2727
2828
&::placeholder {

src/frontend/apps/impress/src/features/docs/doc-management/api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export * from './useDocOptions';
77
export * from './useDocs';
88
export * from './useDocsFavorite';
99
export * from './useDuplicateDoc';
10+
export * from './useMoveDoc';
1011
export * from './useRestoreDoc';
1112
export * from './useSubDocs';
1213
export * from './useUpdateDoc';
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { TreeViewMoveModeEnum } from '@gouvfr-lasuite/ui-kit';
2+
import { useMutation, useQueryClient } from '@tanstack/react-query';
3+
4+
import { APIError, errorCauses, fetchAPI } from '@/api';
5+
import {
6+
getDocAccesses,
7+
getDocInvitations,
8+
useDeleteDocAccess,
9+
useDeleteDocInvitation,
10+
} from '@/docs/doc-share';
11+
12+
import { KEY_LIST_DOC } from './useDocs';
13+
14+
export type MoveDocParam = {
15+
sourceDocumentId: string;
16+
targetDocumentId: string;
17+
position: TreeViewMoveModeEnum;
18+
};
19+
20+
export const moveDoc = async ({
21+
sourceDocumentId,
22+
targetDocumentId,
23+
position,
24+
}: MoveDocParam): Promise<void> => {
25+
const response = await fetchAPI(`documents/${sourceDocumentId}/move/`, {
26+
method: 'POST',
27+
body: JSON.stringify({
28+
target_document_id: targetDocumentId,
29+
position,
30+
}),
31+
});
32+
33+
if (!response.ok) {
34+
throw new APIError('Failed to move the doc', await errorCauses(response));
35+
}
36+
37+
return response.json() as Promise<void>;
38+
};
39+
40+
export function useMoveDoc(deleteAccessOnMove = false) {
41+
const queryClient = useQueryClient();
42+
const { mutate: handleDeleteInvitation } = useDeleteDocInvitation();
43+
const { mutate: handleDeleteAccess } = useDeleteDocAccess();
44+
45+
return useMutation<void, APIError, MoveDocParam>({
46+
mutationFn: moveDoc,
47+
async onSuccess(_data, variables, _onMutateResult, _context) {
48+
if (!deleteAccessOnMove) {
49+
return;
50+
}
51+
52+
void queryClient.invalidateQueries({
53+
queryKey: [KEY_LIST_DOC],
54+
});
55+
const accesses = await getDocAccesses({
56+
docId: variables.sourceDocumentId,
57+
});
58+
59+
const invitationsResponse = await getDocInvitations({
60+
docId: variables.sourceDocumentId,
61+
page: 1,
62+
});
63+
64+
const invitations = invitationsResponse.results;
65+
66+
await Promise.all([
67+
...invitations.map((invitation) =>
68+
handleDeleteInvitation({
69+
docId: variables.sourceDocumentId,
70+
invitationId: invitation.id,
71+
}),
72+
),
73+
...accesses.map((access) =>
74+
handleDeleteAccess({
75+
docId: variables.sourceDocumentId,
76+
accessId: access.id,
77+
}),
78+
),
79+
]);
80+
},
81+
});
82+
}

src/frontend/apps/impress/src/features/docs/doc-search/components/DocSearchContent.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,19 @@ import { DocSearchItem } from './DocSearchItem';
1212
type DocSearchContentProps = {
1313
search: string;
1414
filters: DocSearchFiltersValues;
15+
filterResults?: (doc: Doc) => boolean;
1516
onSelect: (doc: Doc) => void;
1617
onLoadingChange?: (loading: boolean) => void;
18+
renderSearchElement?: (doc: Doc) => React.ReactNode;
1719
};
1820

1921
export const DocSearchContent = ({
2022
search,
2123
filters,
24+
filterResults,
2225
onSelect,
2326
onLoadingChange,
27+
renderSearchElement,
2428
}: DocSearchContentProps) => {
2529
const {
2630
data,
@@ -38,7 +42,11 @@ export const DocSearchContent = ({
3842
const loading = isFetching || isRefetching || isLoading;
3943

4044
const docsData: QuickSearchData<Doc> = useMemo(() => {
41-
const docs = data?.pages.flatMap((page) => page.results) || [];
45+
let docs = data?.pages.flatMap((page) => page.results) || [];
46+
47+
if (filterResults) {
48+
docs = docs.filter(filterResults);
49+
}
4250

4351
return {
4452
groupName: docs.length > 0 ? t('Select a document') : '',
@@ -52,7 +60,7 @@ export const DocSearchContent = ({
5260
]
5361
: [],
5462
};
55-
}, [search, data?.pages, fetchNextPage, hasNextPage]);
63+
}, [search, data?.pages, fetchNextPage, hasNextPage, filterResults]);
5664

5765
useEffect(() => {
5866
onLoadingChange?.(loading);
@@ -62,7 +70,9 @@ export const DocSearchContent = ({
6270
<QuickSearchGroup
6371
onSelect={onSelect}
6472
group={docsData}
65-
renderElement={(doc) => <DocSearchItem doc={doc} />}
73+
renderElement={
74+
renderSearchElement ?? ((doc) => <DocSearchItem doc={doc} />)
75+
}
6676
/>
6777
);
6878
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './DocSearchContent';
12
export * from './DocSearchModal';
23
export * from './DocSearchFilters';
34
export * from './DocSearchSubPageContent';
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
export * from './useDocChildren';
22
export * from './useDocTree';
3-
export * from './useMove';

src/frontend/apps/impress/src/features/docs/doc-tree/api/useMove.tsx

Lines changed: 0 additions & 36 deletions
This file was deleted.

src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@ import { css } from 'styled-components';
1212

1313
import { Box, Overlayer, StyledLink } from '@/components';
1414
import { useCunninghamTheme } from '@/cunningham';
15-
import { Doc, SimpleDocItem } from '@/docs/doc-management';
15+
import {
16+
Doc,
17+
SimpleDocItem,
18+
useMoveDoc,
19+
useTrans,
20+
} from '@/docs/doc-management';
1621

1722
import { KEY_DOC_TREE, useDocTree } from '../api/useDocTree';
18-
import { useMoveDoc } from '../api/useMove';
1923
import { findIndexInTree } from '../utils';
2024

2125
import { DocSubPageItem } from './DocSubPageItem';
@@ -28,6 +32,7 @@ type DocTreeProps = {
2832
export const DocTree = ({ currentDoc }: DocTreeProps) => {
2933
const { spacingsTokens } = useCunninghamTheme();
3034
const { isDesktop } = useResponsive();
35+
const { untitledDocument } = useTrans();
3136
const [treeRoot, setTreeRoot] = useState<HTMLElement | null>(null);
3237
const treeContext = useTreeContext<Doc | null>();
3338
const router = useRouter();
@@ -265,7 +270,7 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
265270
ref={rootItemRef}
266271
data-testid="doc-tree-root-item"
267272
role="treeitem"
268-
aria-label={`${t('Root document {{title}}', { title: treeContext.root?.title || t('Untitled document') })}`}
273+
aria-label={`${t('Root document {{title}}', { title: treeContext.root?.title || untitledDocument })}`}
269274
aria-selected={rootIsSelected}
270275
tabIndex={0}
271276
onFocus={handleRootFocus}
@@ -325,7 +330,7 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
325330
);
326331
router.push(`/docs/${treeContext?.root?.id}`);
327332
}}
328-
aria-label={`${t('Open root document')}: ${treeContext.root?.title || t('Untitled document')}`}
333+
aria-label={`${t('Open root document')}: ${treeContext.root?.title || untitledDocument}`}
329334
tabIndex={-1} // avoid double tabstop
330335
>
331336
<Box $direction="row" $align="center" $width="100%">

0 commit comments

Comments
 (0)