diff --git a/CHANGELOG.md b/CHANGELOG.md index 671c0a47d..fcde9673b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ and this project adheres to - ๐Ÿ’„(frontend) Add left panel #420 - ๐Ÿ’„(frontend) updating the header and leftpanel for responsive #421 - โœจ(backend) config endpoint #425 +- ๐Ÿ’„(frontend) update DocsGrid component #431 - โœจ(backend) whitelist pod's IP address #443 - โœจ(backend) config endpoint #425 - โœจ(frontend) config endpoint #424 diff --git a/src/frontend/apps/impress/src/components/Card.tsx b/src/frontend/apps/impress/src/components/Card.tsx index 951225949..5e3d5ed7d 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 cdda0bbe3..b5d443818 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 7982d1b41..2764f74cc 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 0de4513eb..3bc853a2b 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 9487684e7..f3339aa11 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 21c70ced2..c64ee3b20 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 000000000..cccfed373 --- /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 000000000..ad4cfcef1 --- /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 50069ee3d..2f8536c1e 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 6a57a2fb0..76ed43d8d 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 000000000..c9c74de21 --- /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 0953fae5b..6e6188747 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 729c97a83..1af902fa2 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 ( - + );