mirror of
https://github.com/suitenumerique/docs.git
synced 2026-08-17 21:25:43 +02:00
✨(frontend) add sorting on docs list column name
We can now sort the documents list by clicking on the column name. The sorting is done in ascending or descending order.
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
@@ -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 ({
|
||||
|
||||
@@ -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(/Last modified/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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.46627 6.36542C8.70616 6.36542 8.90776 6.44686 9.0703 6.60929L11.8929 9.51314C12.0474 9.6679 12.1243 9.85786 12.1243 10.0822C12.1242 10.3142 12.0512 10.5042 11.9042 10.6512C11.7648 10.7982 11.5821 10.8714 11.3577 10.8714C11.1334 10.8712 10.9434 10.7935 10.7887 10.6388L9.48804 9.28056L9.2532 9.01637L9.26788 9.53572V16.8631C9.26788 17.0954 9.19014 17.2897 9.0353 17.4445C8.88053 17.5914 8.69062 17.6647 8.46627 17.6647C8.23424 17.6646 8.04069 17.5915 7.88595 17.4445C7.73886 17.2897 7.66579 17.0954 7.66579 16.8631V9.54701L7.6737 9.03669L7.46821 9.28056L6.15516 10.6388C6.00807 10.7936 5.81835 10.8714 5.58613 10.8714C5.36203 10.8712 5.17644 10.7979 5.02952 10.6512C4.88251 10.5042 4.80832 10.3142 4.80823 10.0822C4.80823 9.87314 4.8942 9.68346 5.06452 9.51314L7.88595 6.60929C8.04069 6.44685 8.23423 6.36551 8.46627 6.36542Z" fill="#222631"/>
|
||||
<path d="M15.4922 6.35413C15.7166 6.35413 15.9065 6.42739 16.0612 6.57429C16.2161 6.72137 16.2938 6.90754 16.2938 7.13203V14.4718L16.2791 14.981L16.514 14.7157L17.827 13.3676C17.9817 13.2132 18.1673 13.1363 18.3836 13.1362C18.6158 13.1362 18.802 13.2094 18.9414 13.3563C19.0884 13.5034 19.1615 13.6932 19.1615 13.9254C19.1615 14.1498 19.0848 14.3396 18.9301 14.4944L16.0962 17.3983C15.9414 17.5607 15.7434 17.6421 15.5035 17.6421C15.2638 17.642 15.0701 17.5606 14.9232 17.3983L12.1017 14.4944C11.9314 14.3241 11.8454 14.1344 11.8454 13.9254C11.8455 13.6932 11.9197 13.5034 12.0667 13.3563C12.2138 13.2094 12.4 13.1362 12.6245 13.1362C12.8565 13.1363 13.0465 13.2129 13.1935 13.3676L14.5054 14.7157L14.7064 14.9573L14.703 14.4594V7.13203C14.703 6.90754 14.7761 6.72137 14.9232 6.57429C15.0779 6.42729 15.2678 6.35418 15.4922 6.35413Z" fill="#222631"/>
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M8.46627 6.36542C8.70616 6.36542 8.90776 6.44686 9.0703 6.60929L11.8929 9.51314C12.0474 9.6679 12.1243 9.85786 12.1243 10.0822C12.1242 10.3142 12.0512 10.5042 11.9042 10.6512C11.7648 10.7982 11.5821 10.8714 11.3577 10.8714C11.1334 10.8712 10.9434 10.7935 10.7887 10.6388L9.48804 9.28056L9.2532 9.01637L9.26788 9.53572V16.8631C9.26788 17.0954 9.19014 17.2897 9.0353 17.4445C8.88053 17.5914 8.69062 17.6647 8.46627 17.6647C8.23424 17.6646 8.04069 17.5915 7.88595 17.4445C7.73886 17.2897 7.66579 17.0954 7.66579 16.8631V9.54701L7.6737 9.03669L7.46821 9.28056L6.15516 10.6388C6.00807 10.7936 5.81835 10.8714 5.58613 10.8714C5.36203 10.8712 5.17644 10.7979 5.02952 10.6512C4.88251 10.5042 4.80832 10.3142 4.80823 10.0822C4.80823 9.87314 4.8942 9.68346 5.06452 9.51314L7.88595 6.60929C8.04069 6.44685 8.23423 6.36551 8.46627 6.36542Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M15.4922 6.35413C15.7166 6.35413 15.9065 6.42739 16.0612 6.57429C16.2161 6.72137 16.2938 6.90754 16.2938 7.13203V14.4718L16.2791 14.981L16.514 14.7157L17.827 13.3676C17.9817 13.2132 18.1673 13.1363 18.3836 13.1362C18.6158 13.1362 18.802 13.2094 18.9414 13.3563C19.0884 13.5034 19.1615 13.6932 19.1615 13.9254C19.1615 14.1498 19.0848 14.3396 18.9301 14.4944L16.0962 17.3983C15.9414 17.5607 15.7434 17.6421 15.5035 17.6421C15.2638 17.642 15.0701 17.5606 14.9232 17.3983L12.1017 14.4944C11.9314 14.3241 11.8454 14.1344 11.8454 13.9254C11.8455 13.6932 11.9197 13.5034 12.0667 13.3563C12.2138 13.2094 12.4 13.1362 12.6245 13.1362C12.8565 13.1363 13.0465 13.2129 13.1935 13.3676L14.5054 14.7157L14.7064 14.9573L14.703 14.4594V7.13203C14.703 6.90754 14.7761 6.72137 14.9232 6.57429C15.0779 6.42729 15.2678 6.35418 15.4922 6.35413Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -1,3 +1,12 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.30566 13.4614C7.09538 13.4614 6.91911 13.3903 6.77686 13.248C6.6346 13.1058 6.56348 12.9295 6.56348 12.7192C6.56348 12.509 6.6346 12.3358 6.77686 12.1997C6.91911 12.0575 7.09538 11.9863 7.30566 11.9863H11.2485V6.64258C11.2485 6.43848 11.3197 6.2653 11.4619 6.12305C11.6042 5.98079 11.7773 5.90967 11.9814 5.90967C12.1917 5.90967 12.368 5.98079 12.5103 6.12305C12.6525 6.2653 12.7236 6.43848 12.7236 6.64258V12.7192C12.7236 12.9295 12.6525 13.1058 12.5103 13.248C12.368 13.3903 12.1917 13.4614 11.9814 13.4614H7.30566ZM11.9907 21.8853C10.6672 21.8853 9.42708 21.6348 8.27051 21.1338C7.11393 20.639 6.09652 19.9525 5.21826 19.0742C4.34001 18.2021 3.65039 17.1878 3.14941 16.0312C2.65462 14.8685 2.40723 13.6253 2.40723 12.3018C2.40723 10.9782 2.65462 9.73812 3.14941 8.58154C3.65039 7.41878 4.34001 6.39827 5.21826 5.52002C6.09652 4.64176 7.11393 3.95524 8.27051 3.46045C9.42708 2.96566 10.6672 2.71826 11.9907 2.71826C13.3143 2.71826 14.5544 2.96566 15.7109 3.46045C16.8737 3.95524 17.8942 4.64176 18.7725 5.52002C19.6507 6.39827 20.3372 7.41878 20.832 8.58154C21.333 9.73812 21.5835 10.9782 21.5835 12.3018C21.5835 13.6253 21.333 14.8685 20.832 16.0312C20.3372 17.1878 19.6507 18.2021 18.7725 19.0742C17.8942 19.9525 16.8737 20.639 15.7109 21.1338C14.5544 21.6348 13.3143 21.8853 11.9907 21.8853ZM11.9907 19.9927C13.0545 19.9927 14.0503 19.7917 14.978 19.3896C15.9058 18.9938 16.7222 18.4434 17.4272 17.7383C18.1385 17.0332 18.6921 16.2168 19.0879 15.2891C19.4837 14.3613 19.6816 13.3656 19.6816 12.3018C19.6816 11.238 19.4837 10.2422 19.0879 9.31445C18.6921 8.38053 18.1385 7.56413 17.4272 6.86523C16.7222 6.16016 15.9058 5.6097 14.978 5.21387C14.0503 4.81185 13.0545 4.61084 11.9907 4.61084C10.9331 4.61084 9.93734 4.81185 9.00342 5.21387C8.07568 5.6097 7.25928 6.16016 6.5542 6.86523C5.84912 7.56413 5.29557 8.38053 4.89355 9.31445C4.49772 10.2422 4.2998 11.238 4.2998 12.3018C4.2998 13.3656 4.49772 14.3613 4.89355 15.2891C5.29557 16.2168 5.84912 17.0332 6.5542 17.7383C7.25928 18.4434 8.07568 18.9938 9.00342 19.3896C9.93734 19.7917 10.9331 19.9927 11.9907 19.9927Z" fill="#222631"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M8 3.333c.368 0 .666.299.666.667v3.333h2a.667.667 0 1 1 0 1.334H7.999A.667.667 0 0 1 7.333 8V4c0-.368.298-.667.666-.667Z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
d="M8 .667a7.333 7.333 0 1 1 0 14.666A7.333 7.333 0 0 1 8 .667ZM8 2a6 6 0 1 0 0 12A6 6 0 0 0 8 2Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 434 B |
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
DocDefaultFilter,
|
||||
DocsOrdering,
|
||||
useInfiniteDocs,
|
||||
} from '../../doc-management';
|
||||
|
||||
import { useInfiniteDocsTrashbin } from './useDocsTrashbin';
|
||||
|
||||
export const useDocsGridQuery = (
|
||||
target: DocDefaultFilter,
|
||||
ordering: DocsOrdering,
|
||||
) => {
|
||||
const trashbinQuery = useInfiniteDocsTrashbin(
|
||||
{
|
||||
page: 1,
|
||||
},
|
||||
{
|
||||
enabled: target === DocDefaultFilter.TRASHBIN,
|
||||
},
|
||||
);
|
||||
|
||||
const docsQuery = useInfiniteDocs(
|
||||
{
|
||||
page: 1,
|
||||
ordering,
|
||||
...(target &&
|
||||
target !== DocDefaultFilter.ALL_DOCS && {
|
||||
is_creator_me: target === DocDefaultFilter.MY_DOCS,
|
||||
}),
|
||||
},
|
||||
{
|
||||
enabled: target !== DocDefaultFilter.TRASHBIN,
|
||||
},
|
||||
);
|
||||
|
||||
return target === DocDefaultFilter.TRASHBIN ? trashbinQuery : docsQuery;
|
||||
};
|
||||
@@ -6,16 +6,17 @@ import { css } from 'styled-components';
|
||||
|
||||
import AllDocs from '@/assets/icons/doc-all.svg';
|
||||
import { Box, Card, Icon, Loading, Text } from '@/components';
|
||||
import { useInfiniteDocs } from '@/docs/doc-management/api/useDocs';
|
||||
import { FadeComponent } from '@/components/Effect';
|
||||
import { useImport } from '@/docs/doc-management/hooks/useImport';
|
||||
import { DocDefaultFilter } from '@/docs/doc-management/types';
|
||||
import { DocDefaultFilter, DocsOrdering } from '@/docs/doc-management/types';
|
||||
import DocsIcon from '@/icons/Docs.svg';
|
||||
import BinIcon from '@/icons/bin.svg';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { useInfiniteDocsTrashbin } from '../api';
|
||||
import { useDocsGridQuery } from '../api/useDocsGridQuery';
|
||||
|
||||
import { DocGridContentList } from './DocGridContentList';
|
||||
import { DocsGridColumnName } from './DocsGridColumnName';
|
||||
|
||||
type DocsGridProps = {
|
||||
target?: DocDefaultFilter;
|
||||
@@ -44,8 +45,11 @@ export const DocsGrid = ({
|
||||
|
||||
const { isDesktop, isSmallMobile } = useResponsiveStore();
|
||||
|
||||
const [ordering, setOrdering] = useState<DocsOrdering>('-updated_at');
|
||||
const canSort = target !== DocDefaultFilter.TRASHBIN;
|
||||
|
||||
const { data, isFetching, isLoading, fetchNextPage, hasNextPage } =
|
||||
useDocsQuery(target);
|
||||
useDocsGridQuery(target, canSort ? ordering : undefined);
|
||||
|
||||
const docs = useMemo(() => {
|
||||
const allDocs = data?.pages.flatMap((page) => page.results) ?? [];
|
||||
@@ -107,11 +111,11 @@ export const DocsGrid = ({
|
||||
>
|
||||
<DocGridTitleBar target={target} isImportPending={isImportPending} />
|
||||
{!hasDocs && !loading && <DocGridNoDocs target={target} />}
|
||||
{hasDocs && (
|
||||
<Box
|
||||
$gap="6px"
|
||||
$padding={{ vertical: 'sm', horizontal: isDesktop ? 'md' : 'xs' }}
|
||||
>
|
||||
<FadeComponent isVisible={!!hasDocs}>
|
||||
<Box
|
||||
aria-label={t('Documents grid')}
|
||||
$display="grid"
|
||||
@@ -124,39 +128,18 @@ export const DocsGrid = ({
|
||||
column-gap: 20px;
|
||||
`}
|
||||
>
|
||||
<Box
|
||||
$display="grid"
|
||||
$css={css`
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
`}
|
||||
data-testid="docs-grid-header"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Box $padding={{ all: '3xs' }}>
|
||||
<Text $size="xs" $variation="secondary" $weight="500">
|
||||
{t('Name')}
|
||||
</Text>
|
||||
</Box>
|
||||
{!isSmallMobile && (
|
||||
<Box $padding={{ vertical: '3xs' }}>
|
||||
<Text $size="xs" $weight="500" $variation="secondary">
|
||||
{DocDefaultFilter.TRASHBIN === target
|
||||
? t('Days remaining')
|
||||
: t('Last modified')}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<DocsGridColumnName
|
||||
target={target}
|
||||
ordering={ordering}
|
||||
setOrdering={setOrdering}
|
||||
/>
|
||||
<Box role="list" $display="contents">
|
||||
<DocGridContentList docs={docs} />
|
||||
</Box>
|
||||
</Box>
|
||||
</FadeComponent>
|
||||
{loading && (
|
||||
<Loading
|
||||
loaderProps={{ size: 'small' }}
|
||||
$margin={{ top: 'sm' }}
|
||||
/>
|
||||
<Loading loaderProps={{ size: 'small' }} $margin={{ top: 'sm' }} />
|
||||
)}
|
||||
{hasNextPage && !loading && (
|
||||
<InView
|
||||
@@ -165,19 +148,17 @@ export const DocsGrid = ({
|
||||
onChange={loadMore}
|
||||
style={{ margin: 'auto' }}
|
||||
>
|
||||
{!isFetching && hasNextPage && (
|
||||
<Button
|
||||
onClick={() => void fetchNextPage()}
|
||||
color="brand"
|
||||
variant="tertiary"
|
||||
className="sr-only"
|
||||
>
|
||||
{t('More docs')}
|
||||
</Button>
|
||||
)}
|
||||
</InView>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
@@ -270,29 +251,3 @@ const DocGridNoDocs = ({ target }: { target: DocDefaultFilter }) => {
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const useDocsQuery = (target: DocDefaultFilter) => {
|
||||
const trashbinQuery = useInfiniteDocsTrashbin(
|
||||
{
|
||||
page: 1,
|
||||
},
|
||||
{
|
||||
enabled: target === DocDefaultFilter.TRASHBIN,
|
||||
},
|
||||
);
|
||||
|
||||
const docsQuery = useInfiniteDocs(
|
||||
{
|
||||
page: 1,
|
||||
...(target &&
|
||||
target !== DocDefaultFilter.ALL_DOCS && {
|
||||
is_creator_me: target === DocDefaultFilter.MY_DOCS,
|
||||
}),
|
||||
},
|
||||
{
|
||||
enabled: target !== DocDefaultFilter.TRASHBIN,
|
||||
},
|
||||
);
|
||||
|
||||
return target === DocDefaultFilter.TRASHBIN ? trashbinQuery : docsQuery;
|
||||
};
|
||||
|
||||
+1
-1
@@ -291,7 +291,7 @@ const DocsGridDropdown = ({ doc, options }: DocsGridDropdownProps) => {
|
||||
aria-label={t('Open the menu of actions for the document: {{title}}', {
|
||||
title: doc.title || untitledDocument,
|
||||
})}
|
||||
size="small"
|
||||
size="nano"
|
||||
icon={<MoreSVG width={16} height={16} aria-hidden="true" />}
|
||||
color="neutral"
|
||||
variant="tertiary"
|
||||
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
import { Button } from '@gouvfr-lasuite/cunningham-react';
|
||||
import { ReactNode } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, Text } from '@/components';
|
||||
import { DocDefaultFilter, DocsOrdering } from '@/docs/doc-management/types';
|
||||
import ArrowUpDownIcon from '@/icons/arrow-up-down.svg';
|
||||
import ClockIcon from '@/icons/clock.svg';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
type DocsGridColumnNameProps = {
|
||||
ordering: DocsOrdering;
|
||||
setOrdering: React.Dispatch<React.SetStateAction<DocsOrdering>>;
|
||||
target?: DocDefaultFilter;
|
||||
};
|
||||
|
||||
export const DocsGridColumnName = ({
|
||||
target = DocDefaultFilter.ALL_DOCS,
|
||||
ordering,
|
||||
setOrdering,
|
||||
}: DocsGridColumnNameProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { isSmallMobile } = useResponsiveStore();
|
||||
|
||||
const canSort = target !== DocDefaultFilter.TRASHBIN;
|
||||
|
||||
const toggleOrdering = (field: 'title' | 'updated_at') => {
|
||||
setOrdering((prevOrdering) =>
|
||||
prevOrdering === field ? (`-${field}` as DocsOrdering) : field,
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
$display="grid"
|
||||
$css={css`
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
`}
|
||||
data-testid="docs-grid-header"
|
||||
>
|
||||
<Box $padding={{ all: '3xs' }}>
|
||||
{canSort ? (
|
||||
<DocGridSortButton
|
||||
label={t('Name')}
|
||||
ariaLabel={t('Name')}
|
||||
ordering={ordering}
|
||||
field="title"
|
||||
onClick={() => toggleOrdering('title')}
|
||||
/>
|
||||
) : (
|
||||
<Text $size="xs" $variation="secondary" $weight="500">
|
||||
{t('Name')}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
{!isSmallMobile && (
|
||||
<Box $padding={{ vertical: '3xs' }}>
|
||||
{canSort ? (
|
||||
<DocGridSortButton
|
||||
label={
|
||||
<Text
|
||||
$size="xs"
|
||||
$weight="500"
|
||||
$variation="secondary"
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$gap="2xs"
|
||||
>
|
||||
<ClockIcon width={16} height={16} aria-hidden="true" />{' '}
|
||||
{t('Last modified')}
|
||||
</Text>
|
||||
}
|
||||
ariaLabel={t('Last modified')}
|
||||
ordering={ordering}
|
||||
field="updated_at"
|
||||
onClick={() => toggleOrdering('updated_at')}
|
||||
/>
|
||||
) : (
|
||||
<Text $size="xs" $weight="500" $variation="secondary">
|
||||
{t('Days remaining')}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const DocGridSortButton = ({
|
||||
label,
|
||||
ariaLabel,
|
||||
field,
|
||||
ordering,
|
||||
onClick,
|
||||
}: {
|
||||
label: ReactNode;
|
||||
ariaLabel: string;
|
||||
field: 'title' | 'updated_at';
|
||||
ordering: DocsOrdering;
|
||||
onClick: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const isDesc = ordering === `-${field}`;
|
||||
const isActive = ordering === field || isDesc;
|
||||
|
||||
return (
|
||||
<Box $direction="row" $align="center" $gap="4xs">
|
||||
<Text $size="xs" $weight="500" $variation="secondary">
|
||||
{label}
|
||||
</Text>
|
||||
<Button
|
||||
size="nano"
|
||||
onClick={onClick}
|
||||
data-testid={`docs-grid-sort-${field}`}
|
||||
aria-label={
|
||||
isActive
|
||||
? t('Sorted by {{label}}, {{direction}}. Activate to reverse.', {
|
||||
label: ariaLabel,
|
||||
direction: isDesc ? t('descending') : t('ascending'),
|
||||
})
|
||||
: t('Sort by {{label}}', { label: ariaLabel })
|
||||
}
|
||||
iconPosition="right"
|
||||
icon={<ArrowUpDownIcon width={16} height={16} aria-hidden="true" />}
|
||||
variant="tertiary"
|
||||
color={isActive ? 'brand' : 'neutral'}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user