From dea6a196f5411ddb70d7214d8ed2a58ceaa43eb8 Mon Sep 17 00:00:00 2001 From: Cyril Date: Thu, 30 Oct 2025 10:53:16 +0100 Subject: [PATCH 1/7] =?UTF-8?q?=E2=9C=A8(frontend)=20improve=20mobile=20UX?= =?UTF-8?q?=20by=20showing=20subdocs=20count?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit helps users notice root documents have children in mobile view Signed-off-by: Cyril --- CHANGELOG.md | 1 + .../docs/doc-header/components/DocHeader.tsx | 50 +--------- .../doc-header/components/DocHeaderInfo.tsx | 98 +++++++++++++++++++ 3 files changed, 102 insertions(+), 47 deletions(-) create mode 100644 src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 152a39463e..574dacad51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to - ✨(frontend) create skeleton component for DocEditor #1491 - ✨(frontend) add an EmojiPicker in the document tree and title #1381 - ✨(frontend) ajustable left panel #1456 +- ✨(frontend) improve mobile UX by showing subdocs count #1540 ### Changed diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx index 90a2a8034a..8e3b0cd334 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeader.tsx @@ -1,23 +1,20 @@ import { useTranslation } from 'react-i18next'; -import { Box, HorizontalSeparator, Text } from '@/components'; -import { useConfig } from '@/core'; +import { Box, HorizontalSeparator } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; import { Doc, LinkReach, - Role, getDocLinkReach, useIsCollaborativeEditable, - useTrans, } from '@/docs/doc-management'; -import { useDate } from '@/hook'; import { useResponsiveStore } from '@/stores'; import { AlertNetwork } from './AlertNetwork'; import { AlertPublic } from './AlertPublic'; import { AlertRestore } from './AlertRestore'; import { BoutonShare } from './BoutonShare'; +import { DocHeaderInfo } from './DocHeaderInfo'; import { DocTitle } from './DocTitle'; import { DocToolBox } from './DocToolBox'; @@ -29,27 +26,11 @@ export const DocHeader = ({ doc }: DocHeaderProps) => { const { spacingsTokens } = useCunninghamTheme(); const { isDesktop } = useResponsiveStore(); const { t } = useTranslation(); - const { transRole } = useTrans(); const { isEditable } = useIsCollaborativeEditable(doc); const docIsPublic = getDocLinkReach(doc) === LinkReach.PUBLIC; const docIsAuth = getDocLinkReach(doc) === LinkReach.AUTHENTICATED; - const { relativeDate, calculateDaysLeft } = useDate(); - const { data: config } = useConfig(); const isDeletedDoc = !!doc.deleted_at; - let dateToDisplay = t('Last update: {{update}}', { - update: relativeDate(doc.updated_at), - }); - - if (config?.TRASHBIN_CUTOFF_DAYS && doc.deleted_at) { - const daysLeft = calculateDaysLeft( - doc.deleted_at, - config.TRASHBIN_CUTOFF_DAYS, - ); - - dateToDisplay = `${t('Days remaining:')} ${daysLeft} ${t('days', { count: daysLeft })}`; - } - return ( <> { > - - {isDesktop && ( - <> - - {transRole( - isEditable - ? doc.user_role || doc.link_role - : Role.READER, - )} -  ·  - - - {dateToDisplay} - - - )} - {!isDesktop && ( - - {dateToDisplay} - - )} + {!isDeletedDoc && } diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx new file mode 100644 index 0000000000..934c97ba2e --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx @@ -0,0 +1,98 @@ +import { useTreeContext } from '@gouvfr-lasuite/ui-kit'; +import { t } from 'i18next'; +import React from 'react'; + +import { Text } from '@/components'; +import { useConfig } from '@/core'; +import { useDate } from '@/hook'; +import { useResponsiveStore } from '@/stores'; + +import { + Doc, + Role, + useIsCollaborativeEditable, + useTrans, +} from '../../doc-management'; +import { useDocChildren, useDocTree } from '../../doc-tree'; + +interface DocHeaderInfoProps { + doc: Doc; +} + +export const DocHeaderInfo = ({ doc }: DocHeaderInfoProps) => { + const { data: tree } = useDocTree({ docId: doc.id }); + const { isDesktop } = useResponsiveStore(); + const treeContext = useTreeContext(); + const { transRole } = useTrans(); + const { isEditable } = useIsCollaborativeEditable(doc); + const { relativeDate, calculateDaysLeft } = useDate(); + const { data: config } = useConfig(); + + const { data: childrenPage } = useDocChildren( + { docId: doc.id, page_size: 1 }, + { enabled: true }, + ); + const countFromTreeContext = + treeContext?.root?.id === doc.id + ? treeContext?.treeData?.nodes?.length + : undefined; + + const childrenCount = + countFromTreeContext ?? + childrenPage?.count ?? + doc.numchild ?? + tree?.children?.length ?? + 0; + + let dateToDisplay = t('Last update: {{update}}', { + update: relativeDate(doc.updated_at), + }); + const relativeOnly = relativeDate(doc.updated_at); + + if (config?.TRASHBIN_CUTOFF_DAYS && doc.deleted_at) { + const daysLeft = calculateDaysLeft( + doc.deleted_at, + config.TRASHBIN_CUTOFF_DAYS, + ); + + dateToDisplay = `${t('Days remaining:')} ${daysLeft} ${t('days', { count: daysLeft })}`; + } + + const hasChildren = childrenCount > 0; + return ( + <> + {isDesktop ? ( + <> + + {transRole( + isEditable ? doc.user_role || doc.link_role : Role.READER, + )} +  ·  + + + {dateToDisplay} + + + ) : ( + <> + + {hasChildren ? relativeOnly : dateToDisplay} + + {hasChildren ? ( + +  •  + {t('Contains {{count}} sub-documents', { + count: childrenCount, + })} + + ) : null} + + )} + + ); +}; From bbc06c4224db502f025023f10a098a994d3a94a6 Mon Sep 17 00:00:00 2001 From: Cyril Date: Mon, 10 Nov 2025 13:45:48 +0100 Subject: [PATCH 2/7] =?UTF-8?q?fixup!=20=E2=9C=A8(frontend)=20improve=20mo?= =?UTF-8?q?bile=20UX=20by=20showing=20subdocs=20count?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doc-header/components/DocHeaderInfo.tsx | 63 +++++++++---------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx index 934c97ba2e..a553e1c11d 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx @@ -59,40 +59,39 @@ export const DocHeaderInfo = ({ doc }: DocHeaderInfoProps) => { } const hasChildren = childrenCount > 0; + + if (isDesktop) { + return ( + <> + + {transRole(isEditable ? doc.user_role || doc.link_role : Role.READER)} +  ·  + + + {dateToDisplay} + + + ); + } + return ( <> - {isDesktop ? ( - <> - - {transRole( - isEditable ? doc.user_role || doc.link_role : Role.READER, - )} -  ·  - - - {dateToDisplay} - - - ) : ( - <> - - {hasChildren ? relativeOnly : dateToDisplay} - - {hasChildren ? ( - -  •  - {t('Contains {{count}} sub-documents', { - count: childrenCount, - })} - - ) : null} - - )} + + {hasChildren ? relativeOnly : dateToDisplay} + + {hasChildren ? ( + +  •  + {t('Contains {{count}} sub-documents', { + count: childrenCount, + })} + + ) : null} ); }; From 7b22bbfb8f0e9bcfaa7ee6bcfc8842fb220ccb40 Mon Sep 17 00:00:00 2001 From: Cyril Date: Mon, 10 Nov 2025 14:23:05 +0100 Subject: [PATCH 3/7] =?UTF-8?q?fixup!=20=E2=9C=A8(frontend)=20improve=20mo?= =?UTF-8?q?bile=20UX=20by=20showing=20subdocs=20count?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs/doc-header/components/DocHeaderInfo.tsx | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx index a553e1c11d..47d13bd699 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx @@ -13,14 +13,12 @@ import { useIsCollaborativeEditable, useTrans, } from '../../doc-management'; -import { useDocChildren, useDocTree } from '../../doc-tree'; interface DocHeaderInfoProps { doc: Doc; } export const DocHeaderInfo = ({ doc }: DocHeaderInfoProps) => { - const { data: tree } = useDocTree({ docId: doc.id }); const { isDesktop } = useResponsiveStore(); const treeContext = useTreeContext(); const { transRole } = useTrans(); @@ -28,21 +26,12 @@ export const DocHeaderInfo = ({ doc }: DocHeaderInfoProps) => { const { relativeDate, calculateDaysLeft } = useDate(); const { data: config } = useConfig(); - const { data: childrenPage } = useDocChildren( - { docId: doc.id, page_size: 1 }, - { enabled: true }, - ); const countFromTreeContext = treeContext?.root?.id === doc.id ? treeContext?.treeData?.nodes?.length : undefined; - const childrenCount = - countFromTreeContext ?? - childrenPage?.count ?? - doc.numchild ?? - tree?.children?.length ?? - 0; + const childrenCount = countFromTreeContext ?? doc.numchild ?? 0; let dateToDisplay = t('Last update: {{update}}', { update: relativeDate(doc.updated_at), From ea3dbc96b1dce9938bab63838bc27fc0a3fde4e9 Mon Sep 17 00:00:00 2001 From: Cyril Date: Mon, 10 Nov 2025 14:24:49 +0100 Subject: [PATCH 4/7] =?UTF-8?q?fixup!=20=E2=9C=A8(frontend)=20improve=20mo?= =?UTF-8?q?bile=20UX=20by=20showing=20subdocs=20count?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/docs/doc-header/components/DocHeaderInfo.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx index 47d13bd699..07e6d6b3c8 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx @@ -33,10 +33,11 @@ export const DocHeaderInfo = ({ doc }: DocHeaderInfoProps) => { const childrenCount = countFromTreeContext ?? doc.numchild ?? 0; + const relativeOnly = relativeDate(doc.updated_at); + let dateToDisplay = t('Last update: {{update}}', { - update: relativeDate(doc.updated_at), + update: relativeOnly, }); - const relativeOnly = relativeDate(doc.updated_at); if (config?.TRASHBIN_CUTOFF_DAYS && doc.deleted_at) { const daysLeft = calculateDaysLeft( From 968c463feed8c0474b0efcf4936bb51db2a97b0b Mon Sep 17 00:00:00 2001 From: Cyril Date: Mon, 10 Nov 2025 14:27:21 +0100 Subject: [PATCH 5/7] =?UTF-8?q?fixup!=20=E2=9C=A8(frontend)=20improve=20mo?= =?UTF-8?q?bile=20UX=20by=20showing=20subdocs=20count?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/docs/doc-header/components/DocHeaderInfo.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx index 07e6d6b3c8..0a5a79a014 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx @@ -74,14 +74,14 @@ export const DocHeaderInfo = ({ doc }: DocHeaderInfoProps) => { {hasChildren ? relativeOnly : dateToDisplay} - {hasChildren ? ( + {hasChildren && (  •  {t('Contains {{count}} sub-documents', { count: childrenCount, })} - ) : null} + )} ); }; From 2054445b6c728355acc8ca482ec1f463ec850405 Mon Sep 17 00:00:00 2001 From: Cyril Date: Wed, 12 Nov 2025 11:14:51 +0100 Subject: [PATCH 6/7] =?UTF-8?q?fixup!=20=E2=9C=A8(frontend)=20improve=20mo?= =?UTF-8?q?bile=20UX=20by=20showing=20subdocs=20count?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs/doc-header/components/DocHeaderInfo.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx index 0a5a79a014..998dce3faf 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocHeaderInfo.tsx @@ -1,4 +1,3 @@ -import { useTreeContext } from '@gouvfr-lasuite/ui-kit'; import { t } from 'i18next'; import React from 'react'; @@ -20,18 +19,12 @@ interface DocHeaderInfoProps { export const DocHeaderInfo = ({ doc }: DocHeaderInfoProps) => { const { isDesktop } = useResponsiveStore(); - const treeContext = useTreeContext(); const { transRole } = useTrans(); const { isEditable } = useIsCollaborativeEditable(doc); const { relativeDate, calculateDaysLeft } = useDate(); const { data: config } = useConfig(); - const countFromTreeContext = - treeContext?.root?.id === doc.id - ? treeContext?.treeData?.nodes?.length - : undefined; - - const childrenCount = countFromTreeContext ?? doc.numchild ?? 0; + const childrenCount = doc.numchild ?? 0; const relativeOnly = relativeDate(doc.updated_at); From 45dba4838810b3f98f46bfd83707414bd475e84c Mon Sep 17 00:00:00 2001 From: Cyril Date: Wed, 12 Nov 2025 11:23:03 +0100 Subject: [PATCH 7/7] =?UTF-8?q?=E2=9C=85(frontend)=20add=20unit=20test=20f?= =?UTF-8?q?or=20mobile=20=20rendering=20in=20docheaderinfo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ensures numchild count is displayed correctly on mobile interface Signed-off-by: Cyril --- .../__tests__/DocHeaderInfo.spec.tsx | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocHeaderInfo.spec.tsx diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocHeaderInfo.spec.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocHeaderInfo.spec.tsx new file mode 100644 index 0000000000..93f1a49913 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocHeaderInfo.spec.tsx @@ -0,0 +1,46 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { describe, expect, test, vi } from 'vitest'; + +import { AppWrapper } from '@/tests/utils'; + +// Force mobile layout so the children count is rendered +vi.mock('@/stores', () => ({ + useResponsiveStore: () => ({ isDesktop: false }), +})); + +// Provide stable mocks for hooks used by the component +vi.mock('../../doc-management', async () => { + const actual = await vi.importActual('../../doc-management'); + return { + ...actual, + useTrans: () => ({ transRole: vi.fn((r) => String(r)) }), + useIsCollaborativeEditable: () => ({ isEditable: true }), + }; +}); + +vi.mock('@/core', () => ({ + useConfig: () => ({ data: {} }), +})); + +vi.mock('@/hook', () => ({ + useDate: () => ({ + relativeDate: () => 'yesterday', + calculateDaysLeft: () => 5, + }), +})); + +import { DocHeaderInfo } from '../components/DocHeaderInfo'; + +describe('DocHeaderInfo', () => { + test('renders the number of sub-documents when numchild is provided (mobile layout)', () => { + const doc = { + numchild: 3, + updated_at: new Date().toISOString(), + } as any; + + render(, { wrapper: AppWrapper }); + + expect(screen.getByText(/Contains 3 sub-documents/i)).toBeInTheDocument(); + }); +});