diff --git a/CHANGELOG.md b/CHANGELOG.md
index ac490d64ab..29645d353b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,13 +11,14 @@ and this project adheres to
- ♿️(frontend) restore skip to content link after header redesign #2510
- 🌐(i18n) rename cn_CN to zh_CN, add eo_PL and zh_TW locales #2486
-### Fixed
-
-- 🐛(frontend) redirect homepage to login when homepage feat is disabled #2521
-
### Changed
- ♿️(frontend) use semantic `
` structure in document info card #2379
+- ♻️(frontend) refacto of the grid documents #2534
+
+### Fixed
+
+- 🐛(frontend) redirect homepage to login when homepage feat is disabled #2521
## [v5.4.1] - 2026-07-09
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts
index 59096517b9..3f91787fa3 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts
@@ -18,10 +18,9 @@ test.describe('Doc Create', () => {
test('it creates a doc', async ({ page, browserName }) => {
const [docTitle] = await createDoc(page, 'my-new-doc', browserName, 1);
- await page.waitForFunction(
- () => document.title.match(/my-new-doc - Docs/),
- { timeout: 5000 },
- );
+ await page.waitForFunction(() => document.title.match(/my-new-doc/), {
+ timeout: 5000,
+ });
await page.getByRole('button', { name: 'Back to homepage' }).click();
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid-move.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid-move.spec.ts
index 85ffe6e6e1..c57fe07431 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid-move.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid-move.spec.ts
@@ -22,31 +22,32 @@ test.describe('Doc grid move', () => {
browserName,
}) => {
await page.goto('/');
- await createDoc(page, 'Draggable doc', browserName, 1);
- await page.getByRole('button', { name: 'Back to homepage' }).click();
- await createDoc(page, 'Droppable doc', browserName, 1);
- await page.getByRole('button', { name: 'Back to homepage' }).click();
-
- const response = await page.waitForResponse(
- (response) =>
- response.url().endsWith('documents/?page=1') &&
- response.status() === 200,
+ const [draggableTitle] = await createDoc(
+ page,
+ 'Draggable doc',
+ browserName,
+ 1,
);
- const responseJson = await response.json();
+ const [droppableTitle] = await createDoc(
+ page,
+ 'Droppable doc',
+ browserName,
+ 1,
+ );
+ await page.getByRole('button', { name: 'Back to homepage' }).click();
- const items = responseJson.results;
+ const draggableRow = await getGridRow(page, draggableTitle);
+ const droppableRow = await getGridRow(page, droppableTitle);
const docsGrid = page.getByTestId('docs-grid');
await expect(docsGrid).toBeVisible();
await expect(page.getByTestId('grid-loader')).toBeHidden();
- const draggableElement = page.getByTestId(`draggable-doc-${items[1].id}`);
- const dropZone = page.getByTestId(`droppable-doc-${items[0].id}`);
- await expect(draggableElement).toBeVisible();
- await expect(dropZone).toBeVisible();
+ await expect(draggableRow).toBeVisible();
+ await expect(droppableRow).toBeVisible();
// Get the position of the elements
- const draggableBoundingBox = await draggableElement.boundingBox();
- const dropZoneBoundingBox = await dropZone.boundingBox();
+ const draggableBoundingBox = await draggableRow.boundingBox();
+ const dropZoneBoundingBox = await droppableRow.boundingBox();
expect(draggableBoundingBox).toBeDefined();
expect(dropZoneBoundingBox).toBeDefined();
@@ -71,10 +72,11 @@ test.describe('Doc grid move', () => {
const dragOverlay = page.getByTestId('drag-doc-overlay');
await expect(dragOverlay).toBeVisible();
- await expect(dragOverlay).toHaveText(items[1].title as string);
+ await expect(dragOverlay).toHaveText(draggableTitle);
await page.mouse.up();
await expect(dragOverlay).toBeHidden();
+ await expect(page.getByText(draggableTitle)).toBeHidden();
});
test("it checks can't drop when we have not the minimum role", async ({
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts
index 3cef7cd755..49cb3209ae 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts
@@ -298,34 +298,6 @@ test.describe('Documents filters', () => {
});
test.describe('Documents Grid', () => {
- test('checks all the elements are visible', async ({ page }) => {
- void page.goto('/');
-
- let docs: SmallDoc[];
- const response = await page.waitForResponse(
- (response) =>
- response.url().endsWith('documents/?page=1') &&
- response.status() === 200,
- );
- const result = await response.json();
- docs = result.results as SmallDoc[];
-
- await expect(page.getByTestId('grid-loader')).toBeHidden();
- await expect(page.locator('h2').getByText('All docs')).toBeVisible();
-
- const thead = page.getByTestId('docs-grid-header');
- await expect(thead.getByText(/Name/i)).toBeVisible();
- await expect(thead.getByText(/Updated at/i)).toBeVisible();
-
- await Promise.all(
- docs.map(async (doc) => {
- await expect(
- page.getByTestId(`docs-grid-name-${doc.id}`),
- ).toBeVisible();
- }),
- );
- });
-
test('opens a document with keyboard (Tab + Enter)', async ({
page,
browserName,
@@ -353,14 +325,14 @@ test.describe('Documents Grid', () => {
let docs: SmallDoc[];
const responsePromisePage1 = page.waitForResponse((response) => {
return (
- response.url().endsWith(`/documents/?page=1`) &&
+ response.url().endsWith(`/documents/?page=1&ordering=-updated_at`) &&
response.status() === 200
);
});
const responsePromisePage2 = page.waitForResponse(
(response) =>
- response.url().endsWith(`/documents/?page=2`) &&
+ response.url().endsWith(`/documents/?page=2&ordering=-updated_at`) &&
response.status() === 200,
);
@@ -390,4 +362,42 @@ test.describe('Documents Grid', () => {
}),
);
});
+
+ test('it checks the sorting feature', async ({ page, browserName }) => {
+ await page.goto('/');
+
+ const [docA] = await createDoc(page, 'a-sorting-feat-aaa', browserName);
+ const [docB] = await createDoc(page, 'b-sorting-feat-bbb', browserName);
+ const [docZ] = await createDoc(page, 'z-sorting-feat-zzz', browserName);
+
+ await page.getByRole('button', { name: 'Back to homepage' }).click();
+
+ const rowFilter = (text: string) =>
+ page.getByTestId('docs-grid').getByRole('listitem').filter({
+ hasText: text,
+ });
+
+ const row = rowFilter('sorting-feat');
+
+ // By default, the documents are sorted by descending order (last modified first)
+ await expect(row.nth(0).getByTestId('doc-title')).toHaveText(docZ);
+ await expect(row.nth(1).getByTestId('doc-title')).toHaveText(docB);
+ await expect(row.nth(2).getByTestId('doc-title')).toHaveText(docA);
+
+ // Sort by ascending order - should be empty
+ await page.getByRole('button', { name: 'Sorted by Last modified' }).click();
+ await expect(row).toHaveCount(0);
+
+ // Sort by title ascending
+ await page.getByRole('button', { name: 'Sort by Name' }).click();
+ await expect(rowFilter(docA)).toHaveCount(1);
+ await expect(rowFilter(docB)).toHaveCount(1);
+ await expect(rowFilter(docZ)).toHaveCount(0);
+
+ // Sort by title descending
+ await page.getByRole('button', { name: 'Sorted by Name' }).click();
+ await expect(rowFilter(docZ)).toHaveCount(1);
+ await expect(rowFilter(docA)).toHaveCount(0);
+ await expect(rowFilter(docB)).toHaveCount(0);
+ });
});
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts
index 37817bc126..351f6f393d 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts
@@ -104,7 +104,7 @@ test.describe('Document create member', () => {
).toBeVisible();
// Select email and verify tag
- const email = randomName('test@test.fr', browserName, 1)[0];
+ const email = randomName('test@test.fr', browserName, 1, true)[0];
await inputSearch.fill(email);
await quickSearchContent.getByText(email).click();
await expect(list.getByText(email)).toBeVisible();
@@ -164,7 +164,7 @@ test.describe('Document create member', () => {
const inputSearch = page.getByTestId('quick-search-input');
- const [email] = randomName('test@test.fr', browserName, 1);
+ const [email] = randomName('test@test.fr', browserName, 1, true);
await inputSearch.fill(email);
await page.getByTestId(`search-user-row-${email}`).click();
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts
index b42785719e..2e9bf818e6 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts
@@ -88,9 +88,16 @@ export const getOtherBrowserName = (browserName: BrowserName) => {
return otherBrowserName;
};
-export const randomName = (name: string, browserName: string, length: number) =>
+export const randomName = (
+ name: string,
+ browserName: string,
+ length: number,
+ reverseName = false,
+) =>
Array.from({ length }, (_el, index) => {
- return `${browserName}-${Math.floor(Math.random() * 10000)}-${index}-${name}`;
+ return reverseName
+ ? `${browserName}-${Math.floor(Math.random() * 10000)}-${index}-${name}`
+ : `${name}-${browserName}-${Math.floor(Math.random() * 10000)}-${index}`;
});
export const openHeaderMenu = async (page: Page) => {
@@ -203,9 +210,8 @@ export const getGridRow = async (page: Page, title: string) => {
await expect(docsGrid).toBeVisible();
await expect(page.getByTestId('grid-loader')).toBeHidden();
- const rows = docsGrid.getByRole('listitem');
-
- const row = rows
+ const row = docsGrid
+ .getByRole('listitem')
.filter({
hasText: title,
})
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-signin.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-signin.ts
index 1c7efeadf2..9dd7c28fa8 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/utils-signin.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-signin.ts
@@ -24,15 +24,7 @@ export const customSignIn = async (
fromHome = true,
) => {
// Check if already signed in (Silent login or session still valid)
- if (
- await page
- .locator('header')
- .first()
- .getByRole('button', {
- name: 'Logout',
- })
- .isVisible()
- ) {
+ if (await page.getByLabel('User menu').isVisible()) {
return;
}
diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/Docs.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/Docs.svg
new file mode 100644
index 0000000000..e465be1eca
--- /dev/null
+++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/Docs.svg
@@ -0,0 +1,311 @@
+
diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/Subdocs.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/Subdocs.svg
new file mode 100644
index 0000000000..c249407867
--- /dev/null
+++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/Subdocs.svg
@@ -0,0 +1,153 @@
+
diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/arrow-up-down.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/arrow-up-down.svg
index 5567837d9c..e451b510c9 100644
--- a/src/frontend/apps/impress/src/assets/icons/ui-kit/arrow-up-down.svg
+++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/arrow-up-down.svg
@@ -1,4 +1,10 @@
-