✨(frontend) add trashbin list
List the docs deleted in the trashbin list, it is displayed in the docs grid.
This commit is contained in:
@@ -9,6 +9,7 @@ and this project adheres to
|
||||
### Added
|
||||
|
||||
- ✨(frontend) add pdf block to the editor #1293
|
||||
- ✨List and restore deleted docs #1450
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ test.describe('Document grid item options', () => {
|
||||
const row = await getGridRow(page, docTitle);
|
||||
await row.getByText(`more_horiz`).click();
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Remove' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Delete a doc' }),
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import {
|
||||
clickInGridMenu,
|
||||
createDoc,
|
||||
getGridRow,
|
||||
verifyDocName,
|
||||
} from './utils-common';
|
||||
import { addNewMember } from './utils-share';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test.describe('Doc Trashbin', () => {
|
||||
test('it controls UI and interaction from the grid page', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
const [title1] = await createDoc(page, 'my-trash-doc-1', browserName, 1);
|
||||
const [title2] = await createDoc(page, 'my-trash-doc-2', browserName, 1);
|
||||
await verifyDocName(page, title2);
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
await addNewMember(page, 0, 'Editor');
|
||||
await page.getByRole('button', { name: 'close' }).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Back to homepage' }).click();
|
||||
|
||||
const row1 = await getGridRow(page, title1);
|
||||
await clickInGridMenu(page, row1, 'Delete');
|
||||
await page.getByRole('button', { name: 'Delete document' }).click();
|
||||
|
||||
const row2 = await getGridRow(page, title2);
|
||||
await clickInGridMenu(page, row2, 'Delete');
|
||||
await page.getByRole('button', { name: 'Delete document' }).click();
|
||||
|
||||
await page.getByRole('link', { name: 'Trashbin' }).click();
|
||||
|
||||
const docsGrid = page.getByTestId('docs-grid');
|
||||
await expect(docsGrid.getByText('Days remaining')).toBeVisible();
|
||||
await expect(row1.getByText(title1)).toBeVisible();
|
||||
await expect(row1.getByText('30 days')).toBeVisible();
|
||||
await expect(row2.getByText(title2)).toBeVisible();
|
||||
await expect(
|
||||
row2.getByRole('button', {
|
||||
name: 'Open the sharing settings for the document',
|
||||
}),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
row2.getByRole('button', {
|
||||
name: 'Open the sharing settings for the document',
|
||||
}),
|
||||
).toBeDisabled();
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Page, expect } from '@playwright/test';
|
||||
import { Locator, Page, expect } from '@playwright/test';
|
||||
|
||||
export type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
||||
export const BROWSERS: BrowserName[] = ['chromium', 'webkit', 'firefox'];
|
||||
@@ -326,3 +326,14 @@ export async function waitForLanguageSwitch(
|
||||
|
||||
await page.getByRole('menuitem', { name: lang.label }).click();
|
||||
}
|
||||
|
||||
export const clickInGridMenu = async (
|
||||
page: Page,
|
||||
row: Locator,
|
||||
textButton: string,
|
||||
) => {
|
||||
await row
|
||||
.getByRole('button', { name: /Open the menu of actions for the document/ })
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: textButton }).click();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<svg
|
||||
width="32"
|
||||
height="40"
|
||||
viewBox="0 0 32 40"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g filter="url(#filter0_d_11326_8575)">
|
||||
<rect
|
||||
x="4.35"
|
||||
y="2.35"
|
||||
width="23.3"
|
||||
height="31.3"
|
||||
rx="3.20556"
|
||||
fill="white"
|
||||
/>
|
||||
<rect
|
||||
x="4.35"
|
||||
y="2.35"
|
||||
width="23.3"
|
||||
height="31.3"
|
||||
rx="3.20556"
|
||||
stroke="currentColor"
|
||||
stroke-width="0.7"
|
||||
/>
|
||||
<rect
|
||||
x="4.35"
|
||||
y="2.35"
|
||||
width="23.3"
|
||||
height="31.3"
|
||||
rx="3.20556"
|
||||
stroke="#F6F8F9"
|
||||
stroke-opacity="0.8"
|
||||
stroke-width="0.7"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M7.55664 7.7778C7.55664 7.28689 7.95461 6.88892 8.44553 6.88892H23.5002C23.9911 6.88892 24.3891 7.28689 24.3891 7.7778C24.3891 8.26872 23.9911 8.66669 23.5002 8.66669H8.44553C7.95461 8.66669 7.55664 8.26872 7.55664 7.7778ZM7.55664 10.4445C7.55664 9.95355 7.95461 9.55558 8.44553 9.55558H23.5002C23.9911 9.55558 24.3891 9.95355 24.3891 10.4445C24.3891 10.9354 23.9911 11.3334 23.5002 11.3334H8.44553C7.95461 11.3334 7.55664 10.9354 7.55664 10.4445ZM7.55664 13.1111C7.55664 12.6202 7.95461 12.2222 8.44553 12.2222H23.5002C23.9911 12.2222 24.3891 12.6202 24.3891 13.1111C24.3891 13.6021 23.9911 14 23.5002 14H8.44553C7.95461 14 7.55664 13.6021 7.55664 13.1111ZM7.55664 15.7778C7.55664 15.2869 7.95461 14.8889 8.44553 14.8889H15.5002C15.9911 14.8889 16.3891 15.2869 16.3891 15.7778C16.3891 16.2687 15.9911 16.6667 15.5002 16.6667H8.44553C7.95461 16.6667 7.55664 16.2687 7.55664 15.7778Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M7.55664 7.7778C7.55664 7.28689 7.95461 6.88892 8.44553 6.88892H23.5002C23.9911 6.88892 24.3891 7.28689 24.3891 7.7778C24.3891 8.26872 23.9911 8.66669 23.5002 8.66669H8.44553C7.95461 8.66669 7.55664 8.26872 7.55664 7.7778ZM7.55664 10.4445C7.55664 9.95355 7.95461 9.55558 8.44553 9.55558H23.5002C23.9911 9.55558 24.3891 9.95355 24.3891 10.4445C24.3891 10.9354 23.9911 11.3334 23.5002 11.3334H8.44553C7.95461 11.3334 7.55664 10.9354 7.55664 10.4445ZM7.55664 13.1111C7.55664 12.6202 7.95461 12.2222 8.44553 12.2222H23.5002C23.9911 12.2222 24.3891 12.6202 24.3891 13.1111C24.3891 13.6021 23.9911 14 23.5002 14H8.44553C7.95461 14 7.55664 13.6021 7.55664 13.1111ZM7.55664 15.7778C7.55664 15.2869 7.95461 14.8889 8.44553 14.8889H15.5002C15.9911 14.8889 16.3891 15.2869 16.3891 15.7778C16.3891 16.2687 15.9911 16.6667 15.5002 16.6667H8.44553C7.95461 16.6667 7.55664 16.2687 7.55664 15.7778Z"
|
||||
fill="white"
|
||||
fill-opacity="0.65"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_11326_8575"
|
||||
x="-4"
|
||||
y="0"
|
||||
width="40"
|
||||
height="40"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2" />
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0.0941176 0 0 0 0 0.105882 0 0 0 0 0.141176 0 0 0 0.05 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_11326_8575"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_11326_8575"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -4,9 +4,15 @@ import { css } from 'styled-components';
|
||||
|
||||
import { Box, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Doc, getEmojiAndTitle, useTrans } from '@/docs/doc-management';
|
||||
import {
|
||||
Doc,
|
||||
getEmojiAndTitle,
|
||||
useDocUtils,
|
||||
useTrans,
|
||||
} from '@/docs/doc-management';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import ChildDocument from '../assets/child-document.svg';
|
||||
import PinnedDocumentIcon from '../assets/pinned-document.svg';
|
||||
import SimpleFileIcon from '../assets/simple-document.svg';
|
||||
|
||||
@@ -37,6 +43,7 @@ export const SimpleDocItem = ({
|
||||
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const { untitledDocument } = useTrans();
|
||||
const { isChild } = useDocUtils(doc);
|
||||
|
||||
const { emoji, titleWithoutEmoji: displayTitle } = getEmojiAndTitle(
|
||||
doc.title || untitledDocument,
|
||||
@@ -73,11 +80,19 @@ export const SimpleDocItem = ({
|
||||
<DocIcon
|
||||
emoji={emoji}
|
||||
defaultIcon={
|
||||
<SimpleFileIcon
|
||||
aria-hidden="true"
|
||||
data-testid="doc-simple-icon"
|
||||
color={colorsTokens['primary-500']}
|
||||
/>
|
||||
isChild ? (
|
||||
<ChildDocument
|
||||
aria-hidden="true"
|
||||
data-testid="doc-child-icon"
|
||||
color={colorsTokens['primary-500']}
|
||||
/>
|
||||
) : (
|
||||
<SimpleFileIcon
|
||||
aria-hidden="true"
|
||||
data-testid="doc-simple-icon"
|
||||
color={colorsTokens['primary-500']}
|
||||
/>
|
||||
)
|
||||
}
|
||||
$size="25px"
|
||||
/>
|
||||
|
||||
@@ -56,6 +56,7 @@ export interface Doc {
|
||||
content: Base64;
|
||||
created_at: string;
|
||||
creator: string;
|
||||
deleted_at: string | null;
|
||||
depth: number;
|
||||
path: string;
|
||||
is_favorite: boolean;
|
||||
@@ -107,6 +108,7 @@ export enum DocDefaultFilter {
|
||||
ALL_DOCS = 'all_docs',
|
||||
MY_DOCS = 'my_docs',
|
||||
SHARED_WITH_ME = 'shared_with_me',
|
||||
TRASHBIN = 'trashbin',
|
||||
}
|
||||
|
||||
export type DocsOrdering =
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Box, Card, Text } from '@/components';
|
||||
import { DocDefaultFilter, useInfiniteDocs } from '@/docs/doc-management';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { useInfiniteDocsTrashbin } from '../api';
|
||||
import { useResponsiveDocGrid } from '../hooks/useResponsiveDocGrid';
|
||||
|
||||
import {
|
||||
@@ -33,13 +34,7 @@ export const DocsGrid = ({
|
||||
isLoading,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
} = useInfiniteDocs({
|
||||
page: 1,
|
||||
...(target &&
|
||||
target !== DocDefaultFilter.ALL_DOCS && {
|
||||
is_creator_me: target === DocDefaultFilter.MY_DOCS,
|
||||
}),
|
||||
});
|
||||
} = useDocsQuery(target);
|
||||
|
||||
const docs = data?.pages.flatMap((page) => page.results) ?? [];
|
||||
|
||||
@@ -52,12 +47,20 @@ export const DocsGrid = ({
|
||||
void fetchNextPage();
|
||||
};
|
||||
|
||||
const title =
|
||||
target === DocDefaultFilter.MY_DOCS
|
||||
? t('My docs')
|
||||
: target === DocDefaultFilter.SHARED_WITH_ME
|
||||
? t('Shared with me')
|
||||
: t('All docs');
|
||||
let title = t('All docs');
|
||||
switch (target) {
|
||||
case DocDefaultFilter.MY_DOCS:
|
||||
title = t('My docs');
|
||||
break;
|
||||
case DocDefaultFilter.SHARED_WITH_ME:
|
||||
title = t('Shared with me');
|
||||
break;
|
||||
case DocDefaultFilter.TRASHBIN:
|
||||
title = t('Trashbin');
|
||||
break;
|
||||
default:
|
||||
title = t('All docs');
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -121,7 +124,9 @@ export const DocsGrid = ({
|
||||
role="columnheader"
|
||||
>
|
||||
<Text $size="xs" $weight="500" $variation="600">
|
||||
{t('Updated at')}
|
||||
{DocDefaultFilter.TRASHBIN === target
|
||||
? t('Days remaining')
|
||||
: t('Updated at')}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
@@ -157,3 +162,29 @@ export const DocsGrid = ({
|
||||
</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;
|
||||
};
|
||||
|
||||
@@ -45,6 +45,7 @@ export const DocsGridActions = ({
|
||||
}
|
||||
},
|
||||
testId: `docs-grid-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`,
|
||||
showSeparator: true,
|
||||
},
|
||||
{
|
||||
label: t('Share'),
|
||||
@@ -66,9 +67,10 @@ export const DocsGridActions = ({
|
||||
canSave: false,
|
||||
});
|
||||
},
|
||||
showSeparator: true,
|
||||
},
|
||||
{
|
||||
label: t('Remove'),
|
||||
label: t('Delete'),
|
||||
icon: 'delete',
|
||||
callback: () => deleteModal.open(),
|
||||
disabled: !doc.abilities.destroy,
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { Tooltip, useModal } from '@openfun/cunningham-react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { KeyboardEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, Icon, StyledLink, Text } from '@/components';
|
||||
import { useConfig } from '@/core';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Doc, LinkReach, SimpleDocItem } from '@/docs/doc-management';
|
||||
import { DocShareModal } from '@/docs/doc-share';
|
||||
import { useDate } from '@/hook';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { useResponsiveDocGrid } from '../hooks/useResponsiveDocGrid';
|
||||
|
||||
import { DocsGridActions } from './DocsGridActions';
|
||||
import { DocsGridItemSharedButton } from './DocsGridItemSharedButton';
|
||||
import { DocsGridTrashbinActions } from './DocsGridTrashbinActions';
|
||||
|
||||
type DocsGridItemProps = {
|
||||
doc: Doc;
|
||||
@@ -21,6 +24,10 @@ type DocsGridItemProps = {
|
||||
};
|
||||
|
||||
export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
|
||||
const searchParams = useSearchParams();
|
||||
const target = searchParams.get('target');
|
||||
const isInTrashbin = target === 'trashbin';
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const { flexLeft, flexRight } = useResponsiveDocGrid();
|
||||
@@ -156,22 +163,25 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
|
||||
$gap="32px"
|
||||
role="gridcell"
|
||||
>
|
||||
{isDesktop && (
|
||||
<StyledLink href={`/docs/${doc.id}`}>
|
||||
<Text $variation="600" $size="xs">
|
||||
{DateTime.fromISO(doc.updated_at).toRelative()}
|
||||
</Text>
|
||||
</StyledLink>
|
||||
)}
|
||||
<DocsGridItemDate
|
||||
doc={doc}
|
||||
isDesktop={isDesktop}
|
||||
isInTrashbin={isInTrashbin}
|
||||
/>
|
||||
|
||||
<Box $direction="row" $align="center" $gap={spacingsTokens.lg}>
|
||||
{isDesktop && (
|
||||
<DocsGridItemSharedButton
|
||||
doc={doc}
|
||||
handleClick={handleShareClick}
|
||||
disabled={isInTrashbin}
|
||||
/>
|
||||
)}
|
||||
<DocsGridActions doc={doc} openShareModal={handleShareClick} />
|
||||
{isInTrashbin ? (
|
||||
<DocsGridTrashbinActions doc={doc} />
|
||||
) : (
|
||||
<DocsGridActions doc={doc} openShareModal={handleShareClick} />
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -181,3 +191,40 @@ export const DocsGridItem = ({ doc, dragMode = false }: DocsGridItemProps) => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const DocsGridItemDate = ({
|
||||
doc,
|
||||
isDesktop,
|
||||
isInTrashbin,
|
||||
}: {
|
||||
doc: Doc;
|
||||
isDesktop: boolean;
|
||||
isInTrashbin: boolean;
|
||||
}) => {
|
||||
const { data: config } = useConfig();
|
||||
const { t } = useTranslation();
|
||||
const { relativeDate, calculateDaysLeft } = useDate();
|
||||
|
||||
if (!isDesktop) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let dateToDisplay = relativeDate(doc.updated_at);
|
||||
|
||||
if (isInTrashbin && config?.TRASHBIN_CUTOFF_DAYS && doc.deleted_at) {
|
||||
const daysLeft = calculateDaysLeft(
|
||||
doc.deleted_at,
|
||||
config.TRASHBIN_CUTOFF_DAYS,
|
||||
);
|
||||
|
||||
dateToDisplay = `${daysLeft} ${t('days', { count: daysLeft })}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledLink href={`/docs/${doc.id}`}>
|
||||
<Text $variation="600" $size="xs">
|
||||
{dateToDisplay}
|
||||
</Text>
|
||||
</StyledLink>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,14 +2,18 @@ import { Button, Tooltip } from '@openfun/cunningham-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, Icon, Text } from '@/components';
|
||||
|
||||
import { Doc } from '../../doc-management';
|
||||
import { Doc } from '@/docs/doc-management';
|
||||
|
||||
type Props = {
|
||||
doc: Doc;
|
||||
handleClick: () => void;
|
||||
disabled: boolean;
|
||||
};
|
||||
export const DocsGridItemSharedButton = ({ doc, handleClick }: Props) => {
|
||||
export const DocsGridItemSharedButton = ({
|
||||
doc,
|
||||
handleClick,
|
||||
disabled,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const sharedCount = doc.nb_accesses_direct;
|
||||
const isShared = sharedCount - 1 > 0;
|
||||
@@ -29,7 +33,13 @@ export const DocsGridItemSharedButton = ({ doc, handleClick }: Props) => {
|
||||
className="--docs--doc-tooltip-grid-item-shared-button"
|
||||
>
|
||||
<Button
|
||||
style={{ minWidth: '50px', justifyContent: 'center' }}
|
||||
className="--docs--doc-grid-item-shared-button"
|
||||
aria-label={t('Open the sharing settings for the document')}
|
||||
data-testid={`docs-grid-item-shared-button-${doc.id}`}
|
||||
style={{
|
||||
minWidth: '50px',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -37,7 +47,8 @@ export const DocsGridItemSharedButton = ({ doc, handleClick }: Props) => {
|
||||
}}
|
||||
color="tertiary"
|
||||
size="nano"
|
||||
icon={<Icon $variation="800" $theme="primary" iconName="group" />}
|
||||
icon={<Icon $theme="primary" iconName="group" disabled={disabled} />}
|
||||
disabled={disabled}
|
||||
>
|
||||
{sharedCount}
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import i18next from 'i18next';
|
||||
import { DateTime } from 'luxon';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { Doc } from '@/docs/doc-management';
|
||||
import { AppWrapper } from '@/tests/utils';
|
||||
|
||||
import { DocsGridItemDate } from '../DocsGridItem';
|
||||
|
||||
describe('DocsGridItemDate', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
fetchMock.restore();
|
||||
});
|
||||
|
||||
it('should not render date when not on desktop', () => {
|
||||
render(
|
||||
<DocsGridItemDate
|
||||
doc={{} as Doc}
|
||||
isDesktop={false}
|
||||
isInTrashbin={false}
|
||||
/>,
|
||||
{
|
||||
wrapper: AppWrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
[
|
||||
{ updated_at: DateTime.now().toISO(), rendered: '0 seconds ago' },
|
||||
{
|
||||
updated_at: DateTime.now().minus({ days: 1 }).toISO(),
|
||||
rendered: '1 day ago',
|
||||
},
|
||||
{
|
||||
updated_at: DateTime.now().minus({ days: 5 }).toISO(),
|
||||
rendered: '5 days ago',
|
||||
},
|
||||
{
|
||||
updated_at: DateTime.now().minus({ days: 30 }).toISO(),
|
||||
rendered: '1 month ago',
|
||||
},
|
||||
].forEach(({ updated_at, rendered }) => {
|
||||
it(`should render "${rendered}" from the updated_at field`, () => {
|
||||
render(
|
||||
<DocsGridItemDate
|
||||
doc={
|
||||
{
|
||||
updated_at,
|
||||
} as Doc
|
||||
}
|
||||
isDesktop={true}
|
||||
isInTrashbin={false}
|
||||
/>,
|
||||
{ wrapper: AppWrapper },
|
||||
);
|
||||
|
||||
expect(screen.getByRole('link')).toBeInTheDocument();
|
||||
expect(screen.getByText(rendered)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it(`should render rendered the updated_at field in the correct language`, () => {
|
||||
i18next.changeLanguage('fr');
|
||||
|
||||
render(
|
||||
<DocsGridItemDate
|
||||
doc={
|
||||
{
|
||||
updated_at: DateTime.now().minus({ days: 5 }).toISO(),
|
||||
} as Doc
|
||||
}
|
||||
isDesktop={true}
|
||||
isInTrashbin={false}
|
||||
/>,
|
||||
{ wrapper: AppWrapper },
|
||||
);
|
||||
|
||||
expect(screen.getByRole('link')).toBeInTheDocument();
|
||||
expect(screen.getByText('il y a 5 jours')).toBeInTheDocument();
|
||||
|
||||
i18next.changeLanguage('en');
|
||||
});
|
||||
|
||||
[
|
||||
{
|
||||
deleted_at: DateTime.now().toISO(),
|
||||
rendered: '30 days',
|
||||
trashbin_cutoff_days: 30,
|
||||
updated_at: DateTime.now().toISO(),
|
||||
},
|
||||
{
|
||||
deleted_at: DateTime.now().toISO(),
|
||||
rendered: '1 day',
|
||||
trashbin_cutoff_days: 1,
|
||||
updated_at: DateTime.now().toISO(),
|
||||
},
|
||||
{
|
||||
deleted_at: DateTime.now().toISO(),
|
||||
rendered: '0 seconds ago',
|
||||
trashbin_cutoff_days: 0,
|
||||
updated_at: DateTime.now().toISO(),
|
||||
},
|
||||
].forEach(({ deleted_at, rendered, trashbin_cutoff_days, updated_at }) => {
|
||||
it(`should render "${rendered}" when we are in the trashbin`, async () => {
|
||||
fetchMock.get('http://test.jest/api/v1.0/config/', {
|
||||
body: JSON.stringify({
|
||||
TRASHBIN_CUTOFF_DAYS: trashbin_cutoff_days,
|
||||
}),
|
||||
});
|
||||
|
||||
render(
|
||||
<DocsGridItemDate
|
||||
doc={
|
||||
{
|
||||
deleted_at,
|
||||
updated_at,
|
||||
} as Doc
|
||||
}
|
||||
isDesktop={true}
|
||||
isInTrashbin={true}
|
||||
/>,
|
||||
{ wrapper: AppWrapper },
|
||||
);
|
||||
|
||||
expect(screen.getByRole('link')).toBeInTheDocument();
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(screen.getByText(rendered)).toBeInTheDocument();
|
||||
},
|
||||
{ timeout: 1000 },
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -35,6 +35,11 @@ export const LeftPanelTargetFilters = () => {
|
||||
label: t('Shared with me'),
|
||||
targetQuery: DocDefaultFilter.SHARED_WITH_ME,
|
||||
},
|
||||
{
|
||||
icon: 'delete',
|
||||
label: t('Trashbin'),
|
||||
targetQuery: DocDefaultFilter.TRASHBIN,
|
||||
},
|
||||
];
|
||||
|
||||
const buildHref = (query: DocDefaultFilter) => {
|
||||
|
||||
@@ -172,6 +172,7 @@ export class ApiPlugin implements WorkboxPlugin {
|
||||
content: '',
|
||||
created_at: new Date().toISOString(),
|
||||
creator: 'dummy-id',
|
||||
deleted_at: null,
|
||||
depth: 1,
|
||||
is_favorite: false,
|
||||
nb_accesses_direct: 1,
|
||||
|
||||
@@ -21,5 +21,17 @@ export const useDate = () => {
|
||||
.toLocaleString(format);
|
||||
};
|
||||
|
||||
return { formatDate };
|
||||
const relativeDate = (date: string): string => {
|
||||
return DateTime.fromISO(date).setLocale(i18n.language).toRelative() || '';
|
||||
};
|
||||
|
||||
const calculateDaysLeft = (date: string, daysLimit: number): number =>
|
||||
Math.max(
|
||||
0,
|
||||
Math.ceil(
|
||||
DateTime.fromISO(date).plus({ days: daysLimit }).diffNow('days').days,
|
||||
),
|
||||
);
|
||||
|
||||
return { formatDate, relativeDate, calculateDaysLeft };
|
||||
};
|
||||
|
||||
@@ -434,7 +434,9 @@
|
||||
"Share with {{count}} users_one": "Share with {{count}} user",
|
||||
"Shared with {{count}} users_many": "Shared with {{count}} users",
|
||||
"Shared with {{count}} users_one": "Shared with {{count}} user",
|
||||
"Shared with {{count}} users_other": "Shared with {{count}} users"
|
||||
"Shared with {{count}} users_other": "Shared with {{count}} users",
|
||||
"days_one": "day",
|
||||
"days_other": "days"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
|
||||
Reference in New Issue
Block a user