From e13e26e07ae1991a071e13f90f9cb153ffede623 Mon Sep 17 00:00:00 2001
From: fch-aa <21101725+fch-aa@users.noreply.github.com>
Date: Fri, 14 Aug 2026 16:18:32 +0200
Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20expose=20the=20full=20las?=
=?UTF-8?q?t-update=20date?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Keep the relative timestamp in the document header while exposing the
localized full date through the existing tooltip on hover and keyboard focus.
Signed-off-by: fch-aa <21101725+fch-aa@users.noreply.github.com>
---
CHANGELOG.md | 1 +
.../__tests__/DocHeaderInfo.spec.tsx | 89 +++++++++++++++++++
.../doc-header/components/DocHeaderInfo.tsx | 21 ++++-
.../docs-grid/components/DocsGridItem.tsx | 17 +++-
.../__tests__/DocsGridItemDate.test.tsx | 36 +++++++-
5 files changed, 156 insertions(+), 8 deletions(-)
create mode 100644 src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocHeaderInfo.spec.tsx
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 48ade5220..93a86bd81 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to
### Added
- 🔧(backend) fine tune redis cache options
+- ✨(frontend) make the full last-update date available #1215
### Changed
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 000000000..5c367372d
--- /dev/null
+++ b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocHeaderInfo.spec.tsx
@@ -0,0 +1,89 @@
+import { act, fireEvent, render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import i18next from 'i18next';
+import { DateTime } from 'luxon';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+
+import { Doc, LinkReach, Role } from '@/docs/doc-management';
+import { AppWrapper } from '@/tests/utils';
+
+import { DocHeaderInfo } from '../components/DocHeaderInfo';
+
+vi.mock('@/core', async () => {
+ const actual = await vi.importActual('@/core');
+
+ return {
+ ...actual,
+ useConfig: () => ({ data: {} }),
+ };
+});
+
+const updatedAt = '2026-08-14T14:23:00';
+const doc = {
+ id: 'doc-1',
+ abilities: {
+ partial_update: true,
+ },
+ ancestors_link_reach: LinkReach.RESTRICTED,
+ computed_link_reach: LinkReach.RESTRICTED,
+ deleted_at: null,
+ link_reach: LinkReach.RESTRICTED,
+ nb_accesses_ancestors: 0,
+ nb_accesses_direct: 1,
+ updated_at: updatedAt,
+ user_role: Role.OWNER,
+} as Doc;
+
+describe('', () => {
+ beforeEach(() => {
+ const now = DateTime.now().set({
+ year: 2026,
+ month: 8,
+ day: 14,
+ hour: 14,
+ minute: 28,
+ second: 0,
+ millisecond: 0,
+ });
+ vi.spyOn(DateTime, 'now').mockReturnValue(now);
+ });
+
+ afterEach(async () => {
+ await act(async () => {
+ await i18next.changeLanguage('en');
+ });
+ vi.restoreAllMocks();
+ });
+
+ it('uses the current locale for the relative and full dates', async () => {
+ const user = userEvent.setup();
+
+ await act(async () => {
+ await i18next.changeLanguage('fr');
+ });
+
+ render(, { wrapper: AppWrapper });
+
+ const relativeDate = screen.getByText('il y a 5 minutes');
+ fireEvent.pointerMove(relativeDate, { pointerType: 'mouse' });
+ await user.hover(relativeDate);
+
+ expect(await screen.findByRole('tooltip')).toHaveTextContent(
+ '14/08/2026 14:23',
+ );
+ });
+
+ it('exposes the full date on hover', async () => {
+ const user = userEvent.setup();
+
+ render(, { wrapper: AppWrapper });
+
+ const relativeDate = screen.getByText('5 minutes ago');
+ fireEvent.pointerMove(relativeDate, { pointerType: 'mouse' });
+ await user.hover(relativeDate);
+
+ expect(await screen.findByRole('tooltip')).toHaveTextContent(
+ '08/14/2026, 02:23 PM',
+ );
+ });
+});
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 9f38b10a5..a88821ea9 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,3 +1,4 @@
+import { Tooltip } from '@gouvfr-lasuite/ui-components';
import { t } from 'i18next';
import { Box, Icon, Text } from '@/components';
@@ -22,10 +23,11 @@ interface DocHeaderInfoProps {
export const DocHeaderInfo = ({ doc }: DocHeaderInfoProps) => {
const { transRole } = useTrans();
const { isEditable } = useIsCollaborativeEditable(doc);
- const { relativeDate, calculateDaysLeft } = useDate();
+ const { relativeDate, formatDate, calculateDaysLeft } = useDate();
const { data: config } = useConfig();
const relativeOnly = relativeDate(doc.updated_at);
+ const fullDate = formatDate(doc.updated_at);
const trashbinCutoff = config?.TRASHBIN_CUTOFF_DAYS;
@@ -87,8 +89,21 @@ export const DocHeaderInfo = ({ doc }: DocHeaderInfoProps) => {
{dateLabel}
-
- {dateValue}
+
+ {trashbinCutoff && doc.deleted_at ? (
+ dateValue
+ ) : (
+
+
+
+ )}
);
diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridItem.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridItem.tsx
index 37757c4d4..9fdb94b34 100644
--- a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridItem.tsx
+++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridItem.tsx
@@ -37,7 +37,7 @@ export const DocsGridItem = ({
const { t } = useTranslation();
const { isSmallMobile, isLargeScreen } = useResponsiveStore();
- const dateToDisplay = useDateToDisplay(doc, isInTrashbin);
+ const { dateToDisplay } = useDateToDisplay(doc, isInTrashbin);
const { openPanel } = useLeftPanelStore();
const handleKeyDown = (e: KeyboardEvent) => {
@@ -247,6 +247,7 @@ const useDateToDisplay = (doc: Doc, isInTrashbin: boolean) => {
const { relativeDate, calculateDaysLeft } = useDate();
let dateToDisplay = relativeDate(doc.updated_at);
+ let isRelativeDate = true;
if (isInTrashbin && config?.TRASHBIN_CUTOFF_DAYS && doc.deleted_at) {
const daysLeft = calculateDaysLeft(
@@ -255,9 +256,10 @@ const useDateToDisplay = (doc: Doc, isInTrashbin: boolean) => {
);
dateToDisplay = `${daysLeft} ${t('days', { count: daysLeft })}`;
+ isRelativeDate = false;
}
- return dateToDisplay;
+ return { dateToDisplay, isRelativeDate };
};
export const DocsGridItemDate = ({
@@ -267,7 +269,8 @@ export const DocsGridItemDate = ({
doc: Doc;
isInTrashbin: boolean;
}) => {
- const dateToDisplay = useDateToDisplay(doc, isInTrashbin);
+ const { dateToDisplay, isRelativeDate } = useDateToDisplay(doc, isInTrashbin);
+ const { formatDate } = useDate();
return (
- {dateToDisplay}
+ {isRelativeDate ? (
+
+
+
+ ) : (
+ dateToDisplay
+ )}
);
};
diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/__tests__/DocsGridItemDate.test.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/__tests__/DocsGridItemDate.test.tsx
index 2e78d0f58..89af851c4 100644
--- a/src/frontend/apps/impress/src/features/docs/docs-grid/components/__tests__/DocsGridItemDate.test.tsx
+++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/__tests__/DocsGridItemDate.test.tsx
@@ -1,4 +1,11 @@
-import { act, render, screen, waitFor } from '@testing-library/react';
+import {
+ act,
+ fireEvent,
+ render,
+ screen,
+ waitFor,
+} from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import fetchMock from 'fetch-mock';
import i18next from 'i18next';
import { DateTime } from 'luxon';
@@ -79,6 +86,33 @@ describe('DocsGridItemDate', () => {
});
});
+ it('should expose the full updated_at date on hover', async () => {
+ const user = userEvent.setup();
+ const updatedAt = DateTime.now().minus({ minutes: 1 });
+
+ render(
+ ,
+ { wrapper: AppWrapper },
+ );
+
+ const relativeDate = screen.getByText('1 minute ago');
+ fireEvent.pointerMove(relativeDate, { pointerType: 'mouse' });
+ await user.hover(relativeDate);
+
+ expect(await screen.findByRole('tooltip')).toHaveTextContent(
+ updatedAt.setLocale('en').toLocaleString({
+ month: '2-digit',
+ day: '2-digit',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ }),
+ );
+ });
+
[
{
deleted_at: DateTime.now().toISO(),