From 4c297e292ad982ba7e6628928a0daff89aeff304 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Wed, 16 Aug 2023 17:02:26 +0200 Subject: [PATCH 1/4] Add sortBy button for card view --- .../ProjectCard/ProjectCardHeader/index.tsx | 4 +- .../ProjectList/ProjectList.module.scss | 7 +++- .../src/components/ProjectList/index.tsx | 42 +++++++++++++++++++ .../src/components/ProjectList/types.ts | 5 +++ .../src/webparts/projectList/index.ts | 14 +++++++ .../src/webparts/projectList/manifest.json | 1 + 6 files changed, 71 insertions(+), 2 deletions(-) diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectCard/ProjectCardHeader/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectCard/ProjectCardHeader/index.tsx index 0849467ee..4f0e1ba19 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectCard/ProjectCardHeader/index.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectCard/ProjectCardHeader/index.tsx @@ -79,7 +79,9 @@ export const ProjectCardHeader: FC = (props) => { props.onImageLoad setShowCustomImage( (image.target as HTMLImageElement).naturalHeight !== 648 - ? (image.target as HTMLImageElement).naturalHeight !== 96 ? true : false + ? (image.target as HTMLImageElement).naturalHeight !== 96 + ? true + : false : false ) }} diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectList.module.scss b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectList.module.scss index b60b59113..c58cbda0e 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectList.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectList.module.scss @@ -14,11 +14,12 @@ .commandBar { display: flex; flex-direction: row; + max-width: 996px; gap: 15px; .search { width: 100%; - max-width: 820px; + max-width: 100%; .searchBox { box-shadow: var(--shadow2); @@ -26,6 +27,10 @@ max-width: inherit; } } + + .sortBy { + box-shadow: var(--shadow2); + } } .emptyMessage { diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/index.tsx index 74196a473..a7037090e 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/index.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/index.tsx @@ -1,10 +1,12 @@ import React, { FC } from 'react' import { IColumn, SelectionMode, ShimmeredDetailsList } from '@fluentui/react' import { + Button, FluentProvider, SelectTabData, Tab, TabList, + Tooltip, webLightTheme } from '@fluentui/react-components' import { Alert } from '@fluentui/react-components/unstable' @@ -22,6 +24,7 @@ import { RenderModeDropdown } from './RenderModeDropdown' import { IProjectListProps } from './types' import { useProjectList } from './useProjectList' import { ProjectListModel } from 'pp365-shared-library/lib/models' +import { TextSortAscendingRegular, TextSortDescendingRegular } from '@fluentui/react-icons' export const ProjectList: FC = (props) => { const { @@ -43,6 +46,14 @@ export const ProjectList: FC = (props) => { function renderProjects(projects: ProjectListModel[]) { switch (state.renderMode) { case 'tiles': { + props.columns.map((col) => { + col.isSorted = col.key === state.sort?.fieldName + if (col.isSorted) { + col.isSortedDescending = state.sort?.isSortedDescending + } + return col + }) + return projects.map((project, idx) => ( = (props) => { onOptionSelect={(renderAs) => setState({ renderMode: renderAs })} /> + {state.isDataLoaded && isEmpty(projects) && (
@@ -187,6 +228,7 @@ ProjectList.defaultProps = { sortBy: 'Title', showSearchBox: true, showRenderModeSelector: true, + showSortBy: true, defaultRenderMode: 'tiles', defaultVertical: 'my_projects', verticals: ProjectListVerticals, diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/types.ts b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/types.ts index d90110283..679e62011 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/types.ts @@ -55,6 +55,11 @@ export interface IProjectListProps extends IBaseComponentProps { */ showRenderModeSelector?: boolean + /** + * Show sort by button + */ + showSortBy?: boolean + /** * Show Project Logo on the project card */ diff --git a/SharePointFramework/PortfolioWebParts/src/webparts/projectList/index.ts b/SharePointFramework/PortfolioWebParts/src/webparts/projectList/index.ts index 0c72da366..3c4997b80 100644 --- a/SharePointFramework/PortfolioWebParts/src/webparts/projectList/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/webparts/projectList/index.ts @@ -71,6 +71,20 @@ export default class ProjectListWebPart extends BasePortfolioWebPart Date: Thu, 17 Aug 2023 15:54:15 +0200 Subject: [PATCH 2/4] Add new list experience (normal and compact) --- .../ProjectList/List/List.module.scss | 45 ++++ .../components/ProjectList/List/context.ts | 17 ++ .../src/components/ProjectList/List/index.tsx | 231 ++++++++++++++++++ .../ProjectList/ProjectCard/context.ts | 12 +- .../ProjectList/ProjectList.module.scss | 1 + .../ProjectList/ProjectListColumns.ts | 38 ++- .../RenderModeDropdown.module.scss | 2 +- .../ProjectList/RenderModeDropdown/index.tsx | 10 +- .../ProjectList/RenderModeDropdown/types.ts | 12 +- .../src/components/ProjectList/index.tsx | 45 ++-- .../src/components/ProjectList/types.ts | 5 +- .../components/ProjectList/useProjectList.ts | 13 +- .../PortfolioWebParts/src/loc/en-us.js | 1 + .../PortfolioWebParts/src/loc/mystrings.d.ts | 1 + .../PortfolioWebParts/src/loc/nb-no.js | 1 + .../src/webparts/projectList/index.ts | 8 +- 16 files changed, 378 insertions(+), 64 deletions(-) create mode 100644 SharePointFramework/PortfolioWebParts/src/components/ProjectList/List/List.module.scss create mode 100644 SharePointFramework/PortfolioWebParts/src/components/ProjectList/List/context.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/ProjectList/List/index.tsx diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/List/List.module.scss b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/List/List.module.scss new file mode 100644 index 000000000..294082218 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/List/List.module.scss @@ -0,0 +1,45 @@ +@import "~@fluentui/react/dist/sass/References.scss"; + +a { + color: var(--colorBrandForegroundLink); + outline: none; + text-decoration: none; +} + +a:hover { + color: var(--colorBrandForegroundLinkHover); + text-decoration: underline; +} + +.list { + width: 996px; + + div:first-child { + max-width: inherit; + } + + .logo { + width: 48px; + height: 48px; + + .projectAvatar { + height: 80%; + width: 80%; + object-fit: cover; + border-radius: var(--borderRadiusMedium); + margin: 5px; + } + + img { + height: 80%; + width: 80%; + object-fit: cover; + border-radius: var(--borderRadiusMedium); + margin: 5px; + } + } + + .avatar:hover { + filter: brightness(0.9); + } +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/List/context.ts b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/List/context.ts new file mode 100644 index 000000000..f0a60c444 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/List/context.ts @@ -0,0 +1,17 @@ +import { createContext } from 'react' +import { IProjectListProps } from '../types' +import { ProjectListModel } from 'pp365-shared-library/lib/models' + +export interface IListContext extends IProjectListProps { + /** + * Projects + */ + projects?: ProjectListModel[] + + /** + * Size that determines the list appearance + */ + size?: 'extra-small' | 'small' | 'medium' +} + +export const ListContext = createContext(null) diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/List/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/List/index.tsx new file mode 100644 index 000000000..05e2ad30e --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/List/index.tsx @@ -0,0 +1,231 @@ +import * as React from 'react' +import { + DataGridBody, + DataGridRow, + DataGrid, + DataGridProps, + DataGridHeader, + DataGridHeaderCell, + DataGridCell, + TableColumnDefinition, + TableCellLayout, + Avatar, + Menu, + MenuButton, + MenuItem, + MenuList, + MenuPopover, + MenuTrigger, + Tooltip, + Link +} from '@fluentui/react-components' +import { useContext } from 'react' +import { ListContext } from './context' +import { ProjectListModel } from 'pp365-shared-library/lib/models' +import styles from './List.module.scss' +import { CalendarMonthRegular } from '@fluentui/react-icons' + +export const List = () => { + const context = useContext(ListContext) + const refMap = React.useRef>({}) + + const columns: TableColumnDefinition[] = context.columns.map((col) => { + switch (col.fieldName) { + case 'logo': + return { + columnId: col.fieldName, + compare: () => { + return + }, + renderHeaderCell: () => { + return + }, + renderCell: (item) => { + const [showCustomImage, setShowCustomImage] = React.useState(true) + + return ( +
+ + {`Logo { + setShowCustomImage( + (image.target as HTMLImageElement).naturalHeight !== 648 + ? (image.target as HTMLImageElement).naturalHeight !== 96 + ? true + : false + : false + ) + }} + /> +
+ ) + } + } + case 'owner': + case 'manager': + return { + columnId: col.fieldName, + compare: (a, b) => { + return (a[col.fieldName]?.name || '').localeCompare(b[col.fieldName]?.name || '') + }, + renderHeaderCell: () => { + return col.name + }, + renderCell: (item) => { + return ( + + {' '} + {item[col.fieldName]?.name} + + ) + } + } + case 'title': + return { + columnId: col.fieldName, + compare: (a, b) => { + return (a[col.fieldName] || '').localeCompare(b[col.fieldName] || '') + }, + renderHeaderCell: () => { + return col.name + }, + renderCell: (item) => { + return ( + + {item.hasUserAccess ? ( + + {item[col.fieldName]} + + ) : ( + <>{item[col.fieldName]} + )} + + ) + } + } + case 'action': + return { + columnId: 'actions', + compare: () => { + return + }, + renderHeaderCell: () => { + return '' + }, + renderCell: (item) => { + return ( + + + + } + size={context.size !== 'medium' ? 'small' : 'medium'} + /> + + + + + + Prosjektstatus + + + Dokumentbibliotek + + + Fasesjekkliste + + + Oppgaver + + + + + ) + } + } + default: + return { + columnId: col.fieldName, + compare: (a, b) => { + return (a[col.fieldName] || '').localeCompare(b[col.fieldName] || '') + }, + renderHeaderCell: () => { + return col.name + }, + renderCell: (item) => { + return {item[col.fieldName] || ''} + } + } + } + }) + + const columnSizingOptions = context.columns.reduce( + (options, col) => ( + (options[col.fieldName] = { + minWidth: col.minWidth, + defaultWidth: 120, + idealWidth: col.idealWidth + }), + options + ), + {} + ) + + const defaultSortState = React.useMemo>[1]>( + () => ({ sortColumn: 'title', sortDirection: 'ascending' }), + [] + ) + + return ( +
+ + + + {({ renderHeaderCell, columnId }) => ( + (refMap.current[columnId] = el)}> + {renderHeaderCell()} + + )} + + + > + {({ item, rowId }) => ( + key={rowId}> + {({ renderCell }) => {renderCell(item)}} + + )} + + +
+ ) +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectCard/context.ts b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectCard/context.ts index 0765a332a..319ca765c 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectCard/context.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectCard/context.ts @@ -3,17 +3,7 @@ import { IProjectListProps } from '../types' import { ProjectListModel } from 'pp365-shared-library/lib/models' import { ButtonProps } from '@fluentui/react-components' -export interface IProjectCardContext - extends Pick< - IProjectListProps, - | 'showProjectLogo' - | 'showProjectOwner' - | 'showProjectManager' - | 'showProjectServiceArea' - | 'showProjectType' - | 'showProjectPhase' - | 'useDynamicColors' - > { +export interface IProjectCardContext extends IProjectListProps { /** * Project model */ diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectList.module.scss b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectList.module.scss index c58cbda0e..994bc7d99 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectList.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectList.module.scss @@ -44,6 +44,7 @@ display: flex; flex-wrap: wrap; flex-direction: row; + max-width: 996px; gap: 12px; } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectListColumns.ts b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectListColumns.ts index dc54b39a3..5119b072b 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectListColumns.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectListColumns.ts @@ -1,29 +1,47 @@ import * as strings from 'PortfolioWebPartsStrings' -import { IColumn } from '@fluentui/react/lib/DetailsList' -export const PROJECTLIST_COLUMNS: IColumn[] = [ +export const PROJECTLIST_COLUMNS: any[] = [ + { + key: 'logo', + fieldName: 'logo', + name: '', + iconName: 'PictureCenter', + isIconOnly: true, + minWidth: 64, + idealWidth: 64 + }, { key: 'title', fieldName: 'title', name: strings.TitleLabel, - minWidth: 150 + minWidth: 120, + idealWidth: 240 }, { key: 'phase', fieldName: 'phase', name: strings.PhaseLabel, - minWidth: 100 + minWidth: 100, + idealWidth: 120 }, { - key: 'owner.text', - fieldName: 'owner.text', + key: 'owner', + fieldName: 'owner', name: strings.ProjectOwner, - minWidth: 100 + minWidth: 120, + idealWidth: 180 }, { - key: 'manager.text', - fieldName: 'manager.text', + key: 'manager', + fieldName: 'manager', name: strings.ProjectManager, - minWidth: 100 + minWidth: 120, + idealWidth: 180 + }, + { + key: 'actions', + fieldName: 'action', + minWidth: 40, + idealWidth: 40 } ] diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/RenderModeDropdown/RenderModeDropdown.module.scss b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/RenderModeDropdown/RenderModeDropdown.module.scss index 5c2945e6e..655f9ddf0 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/RenderModeDropdown/RenderModeDropdown.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/RenderModeDropdown/RenderModeDropdown.module.scss @@ -2,5 +2,5 @@ .renderModeDropdown { box-shadow: var(--shadow2); - min-width: 160px !important; + min-width: 170px !important; } diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/RenderModeDropdown/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/RenderModeDropdown/index.tsx index aefe970a0..679e8b500 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/RenderModeDropdown/index.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/RenderModeDropdown/index.tsx @@ -1,14 +1,18 @@ import React, { FC, useEffect, useState } from 'react' import { Dropdown, Option } from '@fluentui/react-components' import { IRenderModeDropdownProps } from './IRenderModeDropdownProps' -import { listOption, tileOption } from './types' +import { listOption, tileOption, compactListOption } from './types' import { ProjectListRenderMode } from '../types' import styles from './RenderModeDropdown.module.scss' export const RenderModeDropdown: FC = (props) => { - const options = [tileOption, listOption] + const options = [tileOption, listOption, compactListOption] const [selectedOption, setSelectedOption] = useState( - props.renderAs === 'tiles' ? tileOption : listOption + props.renderAs === 'tiles' + ? tileOption + : props.renderAs === 'list' + ? listOption + : compactListOption ) useEffect( diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/RenderModeDropdown/types.ts b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/RenderModeDropdown/types.ts index 7dc9983af..e14138c4e 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/RenderModeDropdown/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/RenderModeDropdown/types.ts @@ -1,4 +1,8 @@ -import { GridFilled, TextBulletListSquareFilled } from '@fluentui/react-icons' +import { + GridFilled, + TextBulletListSquareFilled, + TextBulletListLtrRegular +} from '@fluentui/react-icons' import strings from 'PortfolioWebPartsStrings' import { ProjectListRenderMode } from '../types' @@ -14,6 +18,12 @@ export const listOption = { icon: TextBulletListSquareFilled } +export const compactListOption = { + value: 'compactList', + text: strings.RenderModeCompactListText, + icon: TextBulletListLtrRegular +} + export interface IRenderModeDropdownProps { renderAs: ProjectListRenderMode onChange: (renderMode: ProjectListRenderMode) => void diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/index.tsx index a7037090e..e9e96cd50 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/index.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/index.tsx @@ -1,5 +1,4 @@ import React, { FC } from 'react' -import { IColumn, SelectionMode, ShimmeredDetailsList } from '@fluentui/react' import { Button, FluentProvider, @@ -13,10 +12,10 @@ import { Alert } from '@fluentui/react-components/unstable' import { SearchBox } from '@fluentui/react-search-preview' import * as strings from 'PortfolioWebPartsStrings' import { ProjectInformationPanel } from 'pp365-projectwebparts/lib/components/ProjectInformationPanel' -import { getObjectValue } from 'pp365-shared-library/lib/util/getObjectValue' import { find, isEmpty } from 'underscore' import { ProjectCard } from './ProjectCard' import { ProjectCardContext } from './ProjectCard/context' +import { List } from './List' import styles from './ProjectList.module.scss' import { PROJECTLIST_COLUMNS } from './ProjectListColumns' import { ProjectListVerticals } from './ProjectListVerticals' @@ -25,6 +24,7 @@ import { IProjectListProps } from './types' import { useProjectList } from './useProjectList' import { ProjectListModel } from 'pp365-shared-library/lib/models' import { TextSortAscendingRegular, TextSortDescendingRegular } from '@fluentui/react-icons' +import { ListContext } from './List/context' export const ProjectList: FC = (props) => { const { @@ -68,7 +68,10 @@ export const ProjectList: FC = (props) => { )) } - case 'list': { + case 'list': + case 'compactList': { + const size = state.renderMode === 'list' ? 'medium' : 'extra-small' + const columns = props.columns.map((col) => { col.isSorted = col.key === state.sort?.fieldName if (col.isSorted) { @@ -77,35 +80,21 @@ export const ProjectList: FC = (props) => { return col }) return ( - + + + ) } } } - /** - * On render item column - * - * @param project - Project - * @param _index - Index - * @param column - Column - */ - function onRenderItemColumn(project: ProjectListModel, _index: number, column: IColumn) { - const colValue = getObjectValue(project, column.fieldName, null) - if (column.fieldName === 'title') { - if (project.isUserMember) return {colValue} - return <>{colValue} - } - return colValue - } - if (state.projects.length === 0) { return ( @@ -171,7 +160,7 @@ export const ProjectList: FC = (props) => { onOptionSelect={(renderAs) => setState({ renderMode: renderAs })} />
-