Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Visuell oppgradering av "siste prosjekter" webdel #1243

Merged
merged 10 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
.root {
.container {
padding: 5px;
margin: 3px 0 0 0;
.emptyMessage {
margin: 15px 0 0 0;
}

.projectItem {
padding: 15px 2px;
border-bottom: 1px solid rgb(234, 234, 234);
.itemContainer {
height: 100%;
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
position: relative;
display: flex;
flex-direction: row;
align-items: center;
padding: 12px 0;
height: 100%;
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
position: relative;

&.withLogo {
.container {
padding-left: 12px;
}
}
}

.actions {
margin: 15px 0 0 0;
.container {
.title {
margin: 0;
}
}

&:hover {
background-color: var(--colorNeutralBackground1Hover);
border-radius: var(--borderRadiusMedium);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,57 +1,52 @@
import { Link, MessageBar } from '@fluentui/react'
import { Caption2, FluentProvider, Spinner, Text, webLightTheme } from '@fluentui/react-components'
import { DisplayMode } from '@microsoft/sp-core-library'
import { SortDirection } from '@pnp/sp/search'
import { WebPartTitle } from '@pnp/spfx-controls-react/lib/WebPartTitle'
import { format } from '@fluentui/react'
import { Button, Caption1, FluentProvider, Link, webLightTheme } from '@fluentui/react-components'
import { Alert } from '@fluentui/react-components/unstable'
import { ChevronDownFilled, ChevronUpFilled } from '@fluentui/react-icons'
import strings from 'PortfolioWebPartsStrings'
import { ProjectLogo, WebPartTitle } from 'pp365-shared-library/lib/components'
import { formatDate } from 'pp365-shared-library/lib/util/formatDate'
import React, { useEffect, useState } from 'react'
import React, { FC } from 'react'
import styles from './LatestProjects.module.scss'
import { ILatestProjectsProps } from './types'
import { useLatestProjects } from './useLatestProjects'

export const LatestProjects: React.FC<ILatestProjectsProps> = (props) => {
const [projects, setProjects] = useState([])
const [loading, setLoading] = useState(true)
const [viewAll, setViewAll] = useState(false)

useEffect(() => {
props.dataAdapter
.fetchProjectSites(props.maxRowLimit, 'Created', SortDirection.Descending)
.then((projects) => {
setProjects(projects)
setLoading(false)
})
.catch(() => {
setProjects([])
setLoading(false)
})
}, [])
/**
* Renders a list of the latest projects. The list is sorted by the Created date
* by default.
*
* @param props - The component props.
* @param props.dataAdapter - The data adapter used to fetch project sites.
* @param props.maxRowLimit - The maximum number of rows to fetch.
* @param props.rowLimit - The number of rows to display.
* @param props.showProjectLogo - Whether to show the project logo.
*/
export const LatestProjects: FC<ILatestProjectsProps> = (props) => {
const { className, loading, projects, viewAll, toggleViewAll } = useLatestProjects(props)

/**
* Render project list
* Function to render the latest projects.
*/
function renderProjectList() {
if (projects.length === 0) return <MessageBar>{props.emptyMessage}</MessageBar>
function renderLatestProjects(): JSX.Element[] | JSX.Element {
if (!loading && projects.length === 0) {
return (
<Alert className={styles.emptyMessage} intent='info'>
{strings.NoProjectsFoundMessage}
</Alert>
)
}
const viewCount = viewAll ? projects.length : props.rowLimit
return [...projects].slice(0, viewCount).map((site, idx) => {
const created = formatDate(site.Created, true)
return (
<div key={idx} className={styles.projectItem}>
<div className={styles.itemContainer}>
<Caption2>
{strings.CreatedText} {created}
</Caption2>
<div>
<Text
as='h2'
onClick={() => {
window.open(site.Path, props.openInNewTab ? '_blank' : '_self')
}}
style={{ cursor: 'pointer' }}
>
<div key={idx} className={className}>
<ProjectLogo title={site.Title} url={site.Path} hidden={!props.showProjectLogo} />
<div className={styles.container}>
<div className={styles.title}>
<Link href={site.Path} target='_blank' title={site.Title}>
{site.Title}
</Text>
</Link>
</div>
<Caption1 title={format(strings.CreatedTooltipText, created)}>{created}</Caption1>
</div>
</div>
)
Expand All @@ -60,28 +55,30 @@ export const LatestProjects: React.FC<ILatestProjectsProps> = (props) => {

return (
<FluentProvider className={styles.root} theme={webLightTheme}>
<WebPartTitle displayMode={DisplayMode.Read} title={props.title} updateProperty={undefined} />
<WebPartTitle text={props.title} />
<div className={styles.container}>
{loading ? (
<Spinner size='extra-tiny' label={props.loadingText} />
) : (
<>
{renderProjectList()}
<div className={styles.actions} hidden={projects.length <= props.rowLimit}>
<Link onClick={() => setViewAll(!viewAll)} appearance='subtle'>
{viewAll ? strings.ViewLessText : strings.ViewMoreText}
</Link>
</div>
</>
)}
{renderLatestProjects()}
<div hidden={projects.length <= props.rowLimit}>
<Button
appearance='subtle'
size='small'
icon={viewAll ? <ChevronUpFilled /> : <ChevronDownFilled />}
title={viewAll ? strings.ViewLessText : strings.ViewMoreText}
onClick={toggleViewAll}
>
{viewAll ? strings.ViewLessText : strings.ViewMoreText}
</Button>
</div>
</div>
</FluentProvider>
)
}

LatestProjects.defaultProps = {
minRowLimit: 5,
maxRowLimit: 15
showProjectLogo: false,
rowLimit: 5,
minRowLimit: 3,
maxRowLimit: 10
}

export * from './types'
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,42 @@ import { IBaseComponentProps } from '../types'

export interface ILatestProjectsProps extends IBaseComponentProps {
/**
* Loading text
* Number of projects to show
*/
loadingText: string
rowLimit: number

/**
* Empty message
* Min number of projects to show
*/
emptyMessage: string
minRowLimit?: number

/**
* Number of items to show
* Max number of projects to show
*/
rowLimit: number
maxRowLimit: number

/**
* Min number of items to show
* Show project logo for each project
*/
minRowLimit?: number
showProjectLogo?: boolean
}

/**
* Represents the state of the LatestProjects component.
*/
export interface ILatestProjectsState {
/**
* Max number of items to show
* An array of project objects.
*/
maxRowLimit: number
projects: any[]

/**
* A boolean indicating whether the component is currently loading data.
*/
loading: boolean

/**
* Open project sites in a new tab
* A boolean indicating whether to display all projects or just a subset (`props.rowLimit`)
*/
openInNewTab: boolean
viewAll: boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useEffect, useState } from 'react'
import { SortDirection } from '@pnp/sp/search'
import { ILatestProjectsProps, ILatestProjectsState } from './types'
import styles from './LatestProjects.module.scss'

/**
* Custom React hook for fetching and managing the latest projects.
*
* @param props - The props for the LatestProjects component.
*
* @returns An object containing the loading state, the latest projects, a boolean indicating whether to view all projects, and a function to toggle the view all state.
*/
export function useLatestProjects(props: ILatestProjectsProps) {
const [state, setState] = useState<ILatestProjectsState>({
projects: [],
loading: true,
viewAll: false
})

useEffect(() => {
props.dataAdapter
.fetchProjectSites(props.maxRowLimit, 'Created', SortDirection.Descending)
.then((projects) => {
setState({
...state,
projects,
loading: false
})
})
.catch(() => {
setState({
...state,
projects: [],
loading: false
})
})
}, [])

/**
* Conditionally add the 'withLogo' class to the project item container if the showProjectLogo prop is true.
*/
const className = [styles.projectItem, props.showProjectLogo ? styles.withLogo : ''].join(' ')

return {
...state,
className,
toggleViewAll: () => setState({ ...state, viewAll: !state.viewAll })
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ export class PortfolioInsights extends Component<IPortfolioInsightsProps, IPortf
if (this.state.chartData.isEmpty()) {
return (
<div className={styles.inner}>
<MessageBar messageBarType={MessageBarType.info}>{strings.NoProjectsFound}</MessageBar>
<MessageBar messageBarType={MessageBarType.info}>
{strings.NoProjectsFoundMessage}
</MessageBar>
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ a:hover {
height: 80%;
width: 80%;
object-fit: cover;
transform: translate3d(0, 0, 1px);
border-radius: var(--borderRadiusMedium);
margin: 5px;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
width: 100%;
height: 100%;
object-fit: cover;
transform: translate3d(0, 0, 1px);
transition: transform 0.5s;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export const ProjectList: FC<IProjectListProps> = (props) => {
return (
<FluentProvider theme={webLightTheme}>
<section className={styles.root}>
<Alert intent={'info'}>{strings.NoProjectsFound}</Alert>
<Alert intent={'info'}>{strings.NoProjectsFoundMessage}</Alert>
</section>
</FluentProvider>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ define([], function () {
ConfirmDeleteProjectContentColumnTitle: 'Do you want to delete?',
ConfirmDeleteResponseAbort: 'Cancel',
ConfirmDeleteResponseConfirm: 'Delete',
CreatedText: 'Created',
CreatedTooltipText: 'Created {0}',
CustomSortsText: 'Custom sorting',
DataSourceCategoryDescription: 'Enter a data source category to be able to choose between multiple data sources in the web part.',
DataSourceCategoryError: 'An error occurred while retrieving data sources with category **{0}**.',
Expand Down Expand Up @@ -98,8 +98,7 @@ define([], function () {
EditViewColumnsPanelHelpText: 'Select the columns to display for the current view. To change the order, you can drag and drop or use the arrows next to each column.',
EditViewHeaderText: 'Edit view',
EditViewText: 'Edit the view',
EmptyMessageDescription: 'Text to display if there are no projects.',
EmptyMessageLabel: 'Text if there are no elements',
EmptyMessageDescription: 'No new projects found.',
EndDateLabel: 'End date',
ErrorText: 'An error occurred while retrieving projects.',
ExcelExportButtonLabel: 'Eksporter til Excel',
Expand Down Expand Up @@ -145,7 +144,7 @@ define([], function () {
NoAccessMessage: 'You do not have access to this project',
NoDefaultViewMessage: 'No default display has been set.',
NoProjectData: 'Unable to retrieve all data from the project. It may be that you do not have access to the project itself. It may also be that the project has recently been created or that project properties have not been filled in.',
NoProjectsFound: 'No projects found.',
NoProjectsFoundMessage: 'No projects found.',
NotSet: 'Not set',
ParentProjectsHeaderText: 'Parent projects',
ParentProjectsSearchBoxPlaceholderText: 'Search {0} parent projects...',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ declare interface IPortfolioWebPartsStrings {
ConfirmDeleteProjectContentColumnTitle: string
ConfirmDeleteResponseAbort: string
ConfirmDeleteResponseConfirm: string
CreatedText: string
CreatedTooltipText: string
CustomSortsText: string
DataSourceCategoryDescription: string
DataSourceCategoryError: string
Expand Down Expand Up @@ -106,7 +106,6 @@ declare interface IPortfolioWebPartsStrings {
EditViewHeaderText: string
EditViewText: string
EmptyMessageDescription: string
EmptyMessageLabel: string
EndDateLabel: string
ErrorText: string
ExcelExportButtonLabel: string
Expand All @@ -129,8 +128,6 @@ declare interface IPortfolioWebPartsStrings {
ListViewGroupName: string
ListViewText: string
LoadingText: string
LoadingTextDescription: string
LoadingTextLabel: string
MaxWidthDescription: string
MaxWidthLabel: string
MeasurementAchievementLabel: string
Expand All @@ -152,7 +149,7 @@ declare interface IPortfolioWebPartsStrings {
NoAccessMessage: string
NoDefaultViewMessage: string
NoProjectData: string
NoProjectsFound: string
NoProjectsFoundMessage: string
NotSet: string
ParentProjectsHeaderText: string
ParentProjectsSearchBoxPlaceholderText: string
Expand Down
Loading