From e8a96dd6c419477dfff86df7c035e976627a4218 Mon Sep 17 00:00:00 2001 From: TekH Date: Thu, 3 Jul 2025 16:13:45 +0200 Subject: [PATCH] Add document management features and components Introduces `DocItem`, `DocSearch`, and `DocSort` components for displaying, searching, and sorting documents. The `DocSearchView` component integrates these functionalities, providing a user interface for document management, including a button to create new posts. Updates `index.ts` to export the new `DocSearchView` component. --- .../src/sections/document/doc-item.tsx | 211 ++++++++++++++++++ .../src/sections/document/doc-search.tsx | 59 +++++ .../src/sections/document/doc-sort.tsx | 96 ++++++++ .../document/view/doc-search-view.tsx | 96 ++++++++ .../src/sections/document/view/index.ts | 1 + 5 files changed, 463 insertions(+) create mode 100644 src/client/dd-hub-react/src/sections/document/doc-item.tsx create mode 100644 src/client/dd-hub-react/src/sections/document/doc-search.tsx create mode 100644 src/client/dd-hub-react/src/sections/document/doc-sort.tsx create mode 100644 src/client/dd-hub-react/src/sections/document/view/doc-search-view.tsx create mode 100644 src/client/dd-hub-react/src/sections/document/view/index.ts diff --git a/src/client/dd-hub-react/src/sections/document/doc-item.tsx b/src/client/dd-hub-react/src/sections/document/doc-item.tsx new file mode 100644 index 0000000..7365186 --- /dev/null +++ b/src/client/dd-hub-react/src/sections/document/doc-item.tsx @@ -0,0 +1,211 @@ +import type { CardProps } from '@mui/material/Card'; +import type { IconifyName } from 'src/components/iconify'; + +import { varAlpha } from 'minimal-shared/utils'; + +import Box from '@mui/material/Box'; +import Link from '@mui/material/Link'; +import Card from '@mui/material/Card'; +import Avatar from '@mui/material/Avatar'; +import Typography from '@mui/material/Typography'; + +import { fDate } from 'src/utils/format-time'; +import { fShortenNumber } from 'src/utils/format-number'; + +import { Iconify } from 'src/components/iconify'; +import { SvgColor } from 'src/components/svg-color'; + +// ---------------------------------------------------------------------- + +export type IDocItem = { + id: string; + title: string; + coverUrl: string; + totalViews: number; + description: string; + totalShares: number; + totalComments: number; + totalFavorites: number; + postedAt: string | number | null; + author: { + name: string; + avatarUrl: string; + }; +}; + +export function DocItem({ + sx, + post, + latestDoc, + latestDocLarge, + ...other +}: CardProps & { + post: IDocItem; + latestDoc: boolean; + latestDocLarge: boolean; +}) { + const renderAvatar = ( + + ); + + const renderTitle = ( + + {post.title} + + ); + + const renderInfo = ( + + {[ + { number: post.totalComments, icon: 'solar:chat-round-dots-bold' }, + { number: post.totalViews, icon: 'solar:eye-bold' }, + { number: post.totalShares, icon: 'solar:share-bold' }, + ].map((info, _index) => ( + + + {fShortenNumber(info.number)} + + ))} + + ); + + const renderCover = ( + + ); + + const renderDate = ( + + {fDate(post.postedAt)} + + ); + + const renderShape = ( + + ); + + return ( + + ({ + position: 'relative', + pt: 'calc(100% * 3 / 4)', + ...((latestDocLarge || latestDoc) && { + pt: 'calc(100% * 4 / 3)', + '&:after': { + top: 0, + content: "''", + width: '100%', + height: '100%', + position: 'absolute', + bgcolor: varAlpha(theme.palette.grey['900Channel'], 0.72), + }, + }), + ...(latestDocLarge && { + pt: { + xs: 'calc(100% * 4 / 3)', + sm: 'calc(100% * 3 / 4.66)', + }, + }), + })} + > + {renderShape} + {renderAvatar} + {renderCover} + + + ({ + p: theme.spacing(6, 3, 3, 3), + ...((latestDocLarge || latestDoc) && { + width: 1, + bottom: 0, + position: 'absolute', + }), + })} + > + {renderDate} + {renderTitle} + {renderInfo} + + + ); +} diff --git a/src/client/dd-hub-react/src/sections/document/doc-search.tsx b/src/client/dd-hub-react/src/sections/document/doc-search.tsx new file mode 100644 index 0000000..4d4d753 --- /dev/null +++ b/src/client/dd-hub-react/src/sections/document/doc-search.tsx @@ -0,0 +1,59 @@ +import type { Theme, SxProps } from '@mui/material/styles'; + +import TextField from '@mui/material/TextField'; +import InputAdornment from '@mui/material/InputAdornment'; +import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete'; + +import { Iconify } from 'src/components/iconify'; + +import type { IDocItem } from './doc-item'; + +// ---------------------------------------------------------------------- + +type DocSearchProps = { + posts: IDocItem[]; + sx?: SxProps; +}; + +export function DocSearch({ posts, sx }: DocSearchProps) { + return ( + post.title} + isOptionEqualToValue={(option, value) => option.id === value.id} + renderInput={(params) => ( + + + + ), + }, + }} + /> + )} + /> + ); +} diff --git a/src/client/dd-hub-react/src/sections/document/doc-sort.tsx b/src/client/dd-hub-react/src/sections/document/doc-sort.tsx new file mode 100644 index 0000000..78df396 --- /dev/null +++ b/src/client/dd-hub-react/src/sections/document/doc-sort.tsx @@ -0,0 +1,96 @@ +import type { ButtonProps } from '@mui/material/Button'; + +import { useState, useCallback } from 'react'; +import { varAlpha } from 'minimal-shared/utils'; + +import Button from '@mui/material/Button'; +import Popover from '@mui/material/Popover'; +import MenuList from '@mui/material/MenuList'; +import MenuItem, { menuItemClasses } from '@mui/material/MenuItem'; + +import { Iconify } from 'src/components/iconify'; + +// ---------------------------------------------------------------------- + +type DocSortProps = ButtonProps & { + sortBy: string; + onSort: (newSort: string) => void; + options: { value: string; label: string }[]; +}; + +export function DocSort({ options, sortBy, onSort, sx, ...other }: DocSortProps) { + const [openPopover, setOpenPopover] = useState(null); + + const handleOpenPopover = useCallback((event: React.MouseEvent) => { + setOpenPopover(event.currentTarget); + }, []); + + const handleClosePopover = useCallback(() => { + setOpenPopover(null); + }, []); + + return ( + <> + + + + + {options.map((option) => ( + { + onSort(option.value); + handleClosePopover(); + }} + > + {option.label} + + ))} + + + + ); +} diff --git a/src/client/dd-hub-react/src/sections/document/view/doc-search-view.tsx b/src/client/dd-hub-react/src/sections/document/view/doc-search-view.tsx new file mode 100644 index 0000000..c105da6 --- /dev/null +++ b/src/client/dd-hub-react/src/sections/document/view/doc-search-view.tsx @@ -0,0 +1,96 @@ +import { useState, useCallback } from 'react'; + +import Box from '@mui/material/Box'; +import Grid from '@mui/material/Grid'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; +import Pagination from '@mui/material/Pagination'; + +import { DashboardContent } from 'src/layouts/dashboard'; + +import { Iconify } from 'src/components/iconify'; + +import { DocItem } from '../doc-item'; +import { DocSort } from '../doc-sort'; +import { DocSearch } from '../doc-search'; + +import type { IDocItem } from '../doc-item'; + +// ---------------------------------------------------------------------- + +type Props = { + posts: IDocItem[]; +}; + +export function DocSearchView({ posts }: Props) { + const [sortBy, setSortBy] = useState('latest'); + + const handleSort = useCallback((newSort: string) => { + setSortBy(newSort); + }, []); + + return ( + + + + Document Search + + + + + + + + + + + {posts.map((post, index) => { + const latestDocLarge = index === 0; + const latestDoc = index === 1 || index === 2; + + return ( + + + + ); + })} + + + + + ); +} diff --git a/src/client/dd-hub-react/src/sections/document/view/index.ts b/src/client/dd-hub-react/src/sections/document/view/index.ts new file mode 100644 index 0000000..15992c6 --- /dev/null +++ b/src/client/dd-hub-react/src/sections/document/view/index.ts @@ -0,0 +1 @@ +export * from './doc-search-view';