diff --git a/CHANGELOG.md b/CHANGELOG.md index b544febf..5aab3e34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to - ๐Ÿ—๏ธ(yjs-server) organize yjs server #528 - โ™ป๏ธ(frontend) better separation collaboration process #528 - ๐Ÿ’„(frontend) updating the header and leftpanel for responsive #421 +- ๐Ÿ’„(frontend) update DocsGrid component #431 ## [1.10.0] - 2024-12-17 diff --git a/src/frontend/apps/impress/src/components/Card.tsx b/src/frontend/apps/impress/src/components/Card.tsx index 95122594..5e3d5ed7 100644 --- a/src/frontend/apps/impress/src/components/Card.tsx +++ b/src/frontend/apps/impress/src/components/Card.tsx @@ -17,8 +17,7 @@ export const Card = ({ $background="white" $radius="4px" $css={css` - box-shadow: 2px 2px 5px ${colorsTokens()['greyscale-300']}; - border: 1px solid ${colorsTokens()['card-border']}; + border: 1px solid ${colorsTokens()['greyscale-200']}; ${$css} `} {...props} diff --git a/src/frontend/apps/impress/src/components/Icon.tsx b/src/frontend/apps/impress/src/components/Icon.tsx index cdda0bbe..b5d44381 100644 --- a/src/frontend/apps/impress/src/components/Icon.tsx +++ b/src/frontend/apps/impress/src/components/Icon.tsx @@ -1,12 +1,15 @@ import { Text, TextType } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; -type IconProps = { +type IconProps = TextType & { iconName: string; - className?: string; }; -export const Icon = ({ iconName, className }: IconProps) => { - return {iconName}; +export const Icon = ({ iconName, ...textProps }: IconProps) => { + return ( + + {iconName} + + ); }; interface IconBGProps extends TextType { diff --git a/src/frontend/apps/impress/src/components/Text.tsx b/src/frontend/apps/impress/src/components/Text.tsx index 7982d1b4..2764f74c 100644 --- a/src/frontend/apps/impress/src/components/Text.tsx +++ b/src/frontend/apps/impress/src/components/Text.tsx @@ -33,6 +33,7 @@ export interface TextProps extends BoxProps { | 'greyscale'; $variation?: | 'text' + | '000' | '100' | '200' | '300' @@ -41,7 +42,8 @@ export interface TextProps extends BoxProps { | '600' | '700' | '800' - | '900'; + | '900' + | '1000'; } export type TextType = ComponentPropsWithRef; diff --git a/src/frontend/apps/impress/src/cunningham/useCunninghamTheme.tsx b/src/frontend/apps/impress/src/cunningham/useCunninghamTheme.tsx index 0de4513e..3bc853a2 100644 --- a/src/frontend/apps/impress/src/cunningham/useCunninghamTheme.tsx +++ b/src/frontend/apps/impress/src/cunningham/useCunninghamTheme.tsx @@ -5,6 +5,7 @@ import { tokens } from './cunningham-tokens'; type Tokens = typeof tokens.themes.default & Partial; type ColorsTokens = Tokens['theme']['colors']; +type FontSizesTokens = Tokens['theme']['font']['sizes']; type SpacingsTokens = Tokens['theme']['spacings']; type ComponentTokens = Tokens['components']; export type Theme = keyof typeof tokens.themes; @@ -14,6 +15,7 @@ interface AuthStore { setTheme: (theme: Theme) => void; themeTokens: () => Partial; colorsTokens: () => Partial; + fontSizesTokens: () => Partial; spacingsTokens: () => Partial; componentTokens: () => ComponentTokens; } @@ -31,6 +33,7 @@ export const useCunninghamTheme = create((set, get) => { colorsTokens: () => currentTheme().theme.colors, componentTokens: () => currentTheme().components, spacingsTokens: () => currentTheme().theme.spacings, + fontSizesTokens: () => currentTheme().theme.font.sizes, setTheme: (theme: Theme) => { set({ theme }); }, diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocs.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocs.tsx index 9487684e..f3339aa1 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocs.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useDocs.tsx @@ -1,6 +1,12 @@ import { UseQueryOptions, useQuery } from '@tanstack/react-query'; -import { APIError, APIList, errorCauses, fetchAPI } from '@/api'; +import { + APIError, + APIList, + errorCauses, + fetchAPI, + useAPIInfiniteQuery, +} from '@/api'; import { Doc } from '../types'; @@ -52,3 +58,7 @@ export function useDocs( ...queryConfig, }); } + +export const useInfiniteDocs = (params: DocsParams) => { + return useAPIInfiniteQuery(KEY_LIST_DOC, getDocs, params); +}; diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useRemoveDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useRemoveDoc.tsx index 21c70ced..c64ee3b2 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/useRemoveDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useRemoveDoc.tsx @@ -30,7 +30,7 @@ export const useRemoveDoc = (options?: UseRemoveDocOptions) => { mutationFn: removeDoc, ...options, onSuccess: (data, variables, context) => { - void queryClient.resetQueries({ + void queryClient.invalidateQueries({ queryKey: [KEY_LIST_DOC], }); if (options?.onSuccess) { diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/assets/pinned-document.svg b/src/frontend/apps/impress/src/features/docs/doc-management/assets/pinned-document.svg new file mode 100644 index 00000000..cccfed37 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-management/assets/pinned-document.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/assets/simple-document.svg b/src/frontend/apps/impress/src/features/docs/doc-management/assets/simple-document.svg new file mode 100644 index 00000000..ad4cfcef --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-management/assets/simple-document.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGrid.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGrid.tsx index 50069ee3..2f8536c1 100644 --- a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGrid.tsx +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGrid.tsx @@ -1,220 +1,92 @@ -import { - Column, - DataGrid, - SortModel, - usePagination, -} from '@openfun/cunningham-react'; -import React, { useEffect, useState } from 'react'; +import { Button, Loader } from '@openfun/cunningham-react'; import { useTranslation } from 'react-i18next'; -import { createGlobalStyle } from 'styled-components'; +import { InView } from 'react-intersection-observer'; -import { Card, StyledLink, Text, TextErrors } from '@/components'; -import { useCunninghamTheme } from '@/cunningham'; -import { - Doc, - DocsOrdering, - LinkReach, - currentDocRole, - isDocsOrdering, - useDocs, - useTrans, -} from '@/features/docs/doc-management'; -import { useDate } from '@/hook/'; +import { Box, Card, Text } from '@/components'; import { useResponsiveStore } from '@/stores'; -import { PAGE_SIZE } from '../conf'; +import { useInfiniteDocs } from '../../doc-management'; -import { DocsGridActions } from './DocsGridActions'; - -const DocsGridStyle = createGlobalStyle` - & .c__datagrid thead{ - position: sticky; - top: 0; - background: #fff; - z-index: 1; - } - & .c__pagination__goto{ - display:none; - } -`; - -type SortModelItem = { - field: string; - sort: 'asc' | 'desc' | null; -}; - -function formatSortModel(sortModel: SortModelItem): DocsOrdering | undefined { - const { field, sort } = sortModel; - const orderingField = sort === 'desc' ? `-${field}` : field; - - if (isDocsOrdering(orderingField)) { - return orderingField; - } -} +import { DocsGridItem } from './DocsGridItem'; export const DocsGrid = () => { - const { colorsTokens } = useCunninghamTheme(); - const { transRole } = useTrans(); const { t } = useTranslation(); - const { formatDate } = useDate(); - const pagination = usePagination({ - pageSize: PAGE_SIZE, - }); - const [sortModel, setSortModel] = useState([ - { - field: 'updated_at', - sort: 'desc', - }, - ]); - const { page, pageSize, setPagesCount } = pagination; - const [docs, setDocs] = useState([]); - const { isMobile } = useResponsiveStore(); - const ordering = sortModel.length ? formatSortModel(sortModel[0]) : undefined; + const { isDesktop } = useResponsiveStore(); - const { data, isLoading, error } = useDocs({ - page, - ordering, - }); + const { data, isFetching, isLoading, fetchNextPage, hasNextPage } = + useInfiniteDocs({ + page: 1, + }); + const loading = isFetching || isLoading; - useEffect(() => { - if (isLoading) { + const loadMore = (inView: boolean) => { + if (!inView || loading) { return; } - - setDocs(data?.results || []); - }, [data?.results, t, isLoading]); - - useEffect(() => { - setPagesCount(data?.count ? Math.ceil(data.count / pageSize) : 0); - }, [data?.count, pageSize, setPagesCount]); - - const columns: Column[] = [ - { - headerName: '', - id: 'visibility', - size: 95, - renderCell: ({ row }) => { - return ( - row.link_reach === LinkReach.PUBLIC && ( - - - {t('Public')} - - - ) - ); - }, - }, - { - headerName: t('Document name'), - field: 'title', - renderCell: ({ row }) => { - return ( - - - {row.title} - - - ); - }, - }, - { - headerName: t('Created at'), - field: 'created_at', - renderCell: ({ row }) => { - return ( - - {formatDate(row.created_at)} - - ); - }, - }, - { - headerName: t('Updated at'), - field: 'updated_at', - renderCell: ({ row }) => { - return ( - - {formatDate(row.updated_at)} - - ); - }, - }, - { - headerName: t('Your role'), - id: 'your_role', - renderCell: ({ row }) => { - return ( - - - {transRole(currentDocRole(row.abilities))} - - - ); - }, - }, - { - headerName: t('Members'), - id: 'users_number', - renderCell: ({ row }) => { - return ( - - {row.nb_accesses} - - ); - }, - }, - { - id: 'column-actions', - renderCell: ({ row }) => { - return ; - }, - }, - ]; - - // Inverse columns for mobile to have the most important information first - if (isMobile) { - const tmpCol = columns[0]; - columns[0] = columns[1]; - columns[1] = tmpCol; - } + void fetchNextPage(); + }; return ( - - + - {t('Documents')} + {t('All docs')} - {error && } + + + + + {t('Name')} + + + {isDesktop && ( + + + {t('Updated at')} + + + )} - + + + {/* Body */} + {data?.pages.map((currentPage) => { + return currentPage.results.map((doc) => ( + + )); + })} + + + {loading && ( + + + + )} + {hasNextPage && !loading && ( + + {!isFetching && hasNextPage && ( + + )} + + )} ); }; diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridActions.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridActions.tsx index 6a57a2fb..76ed43d8 100644 --- a/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridActions.tsx +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/DocsGridActions.tsx @@ -1,5 +1,5 @@ import { Button } from '@openfun/cunningham-react'; -import React, { useState } from 'react'; +import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Doc, ModalRemoveDoc } from '@/features/docs/doc-management'; @@ -19,7 +19,10 @@ export const DocsGridActions = ({ doc }: DocsGridActionsProps) => { return ( <> + )} + {isDesktop && !isPublic && isRestricted && isShared && ( + + )} + {isDesktop && !isPublic && isAuthenticated && ( + + )} + + + + ); +}; diff --git a/src/frontend/apps/impress/src/features/docs/docs-grid/components/SimpleDocItem.tsx b/src/frontend/apps/impress/src/features/docs/docs-grid/components/SimpleDocItem.tsx new file mode 100644 index 00000000..c9c74de2 --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/docs-grid/components/SimpleDocItem.tsx @@ -0,0 +1,83 @@ +import { css } from 'styled-components'; + +import { Box, Icon, Text } from '@/components'; +import { useCunninghamTheme } from '@/cunningham'; +import { Doc, LinkReach } from '@/features/docs'; +import PinnedDocumentIcon from '@/features/docs/doc-management/assets/pinned-document.svg'; +import SimpleFileIcon from '@/features/docs/doc-management/assets/simple-document.svg'; +import { useResponsiveStore } from '@/stores'; + +const ItemTextCss = css` + overflow: hidden; + text-overflow: ellipsis; + white-space: initial; + display: -webkit-box; + line-clamp: 1; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; +`; + +type SimpleDocItemProps = { + doc: Doc; + isPinned?: boolean; + subText?: string; +}; + +export const SimpleDocItem = ({ + doc, + isPinned = false, + subText, +}: SimpleDocItemProps) => { + const { spacingsTokens } = useCunninghamTheme(); + const { isDesktop } = useResponsiveStore(); + const spacings = spacingsTokens(); + + const isPublic = doc?.link_reach === LinkReach.PUBLIC; + const isShared = !isPublic && doc.accesses.length > 1; + const accessCount = doc.accesses.length - 1; + const isSharedOrPublic = isShared || isPublic; + + return ( + + + {isPinned ? : } + + + + {doc.title} + + + {!isDesktop && ( + <> + {isPublic && } + {isShared && } + {isSharedOrPublic && accessCount > 0 && ( + {accessCount} + )} + {isSharedOrPublic && ยท} + + )} + + + {subText ?? + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi vel ante libero. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed imperdiet neque quam, sed euismod metus mollis ut. '} + + + + + ); +}; diff --git a/src/frontend/apps/impress/src/features/language/LanguagePicker.tsx b/src/frontend/apps/impress/src/features/language/LanguagePicker.tsx index 0953fae5..6e618874 100644 --- a/src/frontend/apps/impress/src/features/language/LanguagePicker.tsx +++ b/src/frontend/apps/impress/src/features/language/LanguagePicker.tsx @@ -1,4 +1,5 @@ import { Select } from '@openfun/cunningham-react'; +import { Settings } from 'luxon'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; @@ -34,6 +35,7 @@ const SelectStyled = styled(Select)<{ $isSmall?: boolean }>` export const LanguagePicker = () => { const { t, i18n } = useTranslation(); const { preload: languages } = i18n.options; + Settings.defaultLocale = i18n.language; const optionsPicker = useMemo(() => { return (languages || []).map((lang) => ({ diff --git a/src/frontend/apps/impress/src/pages/docs/index.tsx b/src/frontend/apps/impress/src/pages/docs/index.tsx index 729c97a8..1af902fa 100644 --- a/src/frontend/apps/impress/src/pages/docs/index.tsx +++ b/src/frontend/apps/impress/src/pages/docs/index.tsx @@ -7,7 +7,7 @@ import { NextPageWithLayout } from '@/types/next'; const Page: NextPageWithLayout = () => { return ( - + );