diff --git a/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/LatestProjects.module.scss b/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/LatestProjects.module.scss index 620a4f4f3..e841451d5 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/LatestProjects.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/LatestProjects.module.scss @@ -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); + } } } -} \ No newline at end of file +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/index.tsx index d2ebb939e..293ee4be9 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/index.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/index.tsx @@ -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 = (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 = (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 {props.emptyMessage} + function renderLatestProjects(): JSX.Element[] | JSX.Element { + if (!loading && projects.length === 0) { + return ( + + {strings.NoProjectsFoundMessage} + + ) + } const viewCount = viewAll ? projects.length : props.rowLimit return [...projects].slice(0, viewCount).map((site, idx) => { const created = formatDate(site.Created, true) return ( -
-
- - {strings.CreatedText} {created} - -
- { - window.open(site.Path, props.openInNewTab ? '_blank' : '_self') - }} - style={{ cursor: 'pointer' }} - > +
+
) @@ -60,28 +55,30 @@ export const LatestProjects: React.FC = (props) => { return ( - +
- {loading ? ( - - ) : ( - <> - {renderProjectList()} - - - )} + {renderLatestProjects()} +
) } LatestProjects.defaultProps = { - minRowLimit: 5, - maxRowLimit: 15 + showProjectLogo: false, + rowLimit: 5, + minRowLimit: 3, + maxRowLimit: 10 } export * from './types' diff --git a/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/types.ts b/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/types.ts index 6064aae29..208bdb319 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/types.ts @@ -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 } diff --git a/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/useLatestProjects.ts b/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/useLatestProjects.ts new file mode 100644 index 000000000..c4deabd0c --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/useLatestProjects.ts @@ -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({ + 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 }) + } +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/PortfolioInsights/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/PortfolioInsights/index.tsx index f2ab48b31..ec97370a8 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/PortfolioInsights/index.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/PortfolioInsights/index.tsx @@ -83,7 +83,9 @@ export class PortfolioInsights extends Component - {strings.NoProjectsFound} + + {strings.NoProjectsFoundMessage} +
) } diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/List/List.module.scss b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/List/List.module.scss index 294082218..219002469 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/List/List.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/List/List.module.scss @@ -34,6 +34,7 @@ a:hover { height: 80%; width: 80%; object-fit: cover; + transform: translate3d(0, 0, 1px); border-radius: var(--borderRadiusMedium); margin: 5px; } diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectCard/ProjectCardHeader/ProjectCardHeader.module.scss b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectCard/ProjectCardHeader/ProjectCardHeader.module.scss index 315186984..b66e37aa0 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectCard/ProjectCardHeader/ProjectCardHeader.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/ProjectCard/ProjectCardHeader/ProjectCardHeader.module.scss @@ -71,6 +71,7 @@ width: 100%; height: 100%; object-fit: cover; + transform: translate3d(0, 0, 1px); transition: transform 0.5s; } diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/index.tsx index a39cbe0d4..9e5a0ea65 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/index.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/index.tsx @@ -91,7 +91,7 @@ export const ProjectList: FC = (props) => { return (
- {strings.NoProjectsFound} + {strings.NoProjectsFoundMessage}
) diff --git a/SharePointFramework/PortfolioWebParts/src/loc/en-us.js b/SharePointFramework/PortfolioWebParts/src/loc/_/en-us.js similarity index 98% rename from SharePointFramework/PortfolioWebParts/src/loc/en-us.js rename to SharePointFramework/PortfolioWebParts/src/loc/_/en-us.js index 814eeba0e..29b513c04 100644 --- a/SharePointFramework/PortfolioWebParts/src/loc/en-us.js +++ b/SharePointFramework/PortfolioWebParts/src/loc/_/en-us.js @@ -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}**.', @@ -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', @@ -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...', diff --git a/SharePointFramework/PortfolioWebParts/src/loc/mystrings.d.ts b/SharePointFramework/PortfolioWebParts/src/loc/mystrings.d.ts index 816a66de2..2f847265e 100644 --- a/SharePointFramework/PortfolioWebParts/src/loc/mystrings.d.ts +++ b/SharePointFramework/PortfolioWebParts/src/loc/mystrings.d.ts @@ -75,7 +75,7 @@ declare interface IPortfolioWebPartsStrings { ConfirmDeleteProjectContentColumnTitle: string ConfirmDeleteResponseAbort: string ConfirmDeleteResponseConfirm: string - CreatedText: string + CreatedTooltipText: string CustomSortsText: string DataSourceCategoryDescription: string DataSourceCategoryError: string @@ -106,7 +106,6 @@ declare interface IPortfolioWebPartsStrings { EditViewHeaderText: string EditViewText: string EmptyMessageDescription: string - EmptyMessageLabel: string EndDateLabel: string ErrorText: string ExcelExportButtonLabel: string @@ -129,8 +128,6 @@ declare interface IPortfolioWebPartsStrings { ListViewGroupName: string ListViewText: string LoadingText: string - LoadingTextDescription: string - LoadingTextLabel: string MaxWidthDescription: string MaxWidthLabel: string MeasurementAchievementLabel: string @@ -152,7 +149,7 @@ declare interface IPortfolioWebPartsStrings { NoAccessMessage: string NoDefaultViewMessage: string NoProjectData: string - NoProjectsFound: string + NoProjectsFoundMessage: string NotSet: string ParentProjectsHeaderText: string ParentProjectsSearchBoxPlaceholderText: string diff --git a/SharePointFramework/PortfolioWebParts/src/loc/nb-no.js b/SharePointFramework/PortfolioWebParts/src/loc/nb-no.js index 35e940114..72621cef9 100644 --- a/SharePointFramework/PortfolioWebParts/src/loc/nb-no.js +++ b/SharePointFramework/PortfolioWebParts/src/loc/nb-no.js @@ -76,7 +76,7 @@ define([], function () { ConfirmDeleteProjectContentColumnTitle: 'Vil du slette?', ConfirmDeleteResponseAbort: 'Avbryt', ConfirmDeleteResponseConfirm: 'Slett', - CreatedText: 'Opprettet', + CreatedTooltipText: 'Opprettet {0}', CustomSortsText: 'Egendefinert sortering', DataSourceCategoryDescription: 'Angi en datakildekategori for å kunne velge mellom flere datakilder i webdelen.', DataSourceCategoryError: 'Det skjedde en feil under uthenting av datakilder med kategori **{0}**.', @@ -106,8 +106,7 @@ define([], function () { EditViewColumnsPanelHelpText: 'Velg kolonnene som skal vises for nåværende visning. Hvis du vil endre rekkefølgen, kan du dra og slippe eller bruke opp- og ned-pilen ved siden av hver kolonne.', EditViewHeaderText: 'Rediger visning', EditViewText: 'Rediger gjeldende visning', - EmptyMessageDescription: 'Tekst som vises om det ikke er noen prosjekter.', - EmptyMessageLabel: 'Tekst ved ingen elementer', + EmptyMessageDescription: 'Fant ingen nye prosjekter.', EndDateLabel: 'Sluttdato', ErrorText: 'Det skjedde en feil under uthenting av prosjekter.', ExcelExportButtonLabel: 'Eksporter til Excel', @@ -130,8 +129,6 @@ define([], function () { ListViewGroupName: 'Listevisning', ListViewText: 'Liste', LoadingText: 'Laster {0}...', - LoadingTextDescription: 'Tekst som vises mens webdelen lastes.', - LoadingTextLabel: 'Laste-tekst', MaxWidthDescription: 'Maksimum bredde for kolonnen. Kan ikke være mindre enn minimum bredde.', MaxWidthLabel: 'Maks bredde', MeasurementAchievementLabel: 'Måloppnåelse', @@ -153,7 +150,7 @@ define([], function () { NoAccessMessage: 'Du har ikke tilgang til dette prosjektet', NoDefaultViewMessage: 'Det er ikke satt noen standardvisning.', NoProjectData: 'Kan ikke hente alle data fra prosjektområdet. Det kan være at du ikke har tilgang til selve området. Det kan også være at området nylig er opprettet eller at prosjektegenskaper ikke er utfylt.', - NoProjectsFound: 'Ingen prosjekter funnet.', + NoProjectsFoundMessage: 'Ingen prosjekter funnet.', NotSet: 'Ikke satt', ParentProjectsHeaderText: 'Overordnede prosjekter', ParentProjectsSearchBoxPlaceholderText: 'Søk i {0} overordnede prosjekter...', @@ -193,7 +190,7 @@ define([], function () { RevertCustomOrderButtonText: 'Tilbakestill rekkefølge', RevertCustomOrderButtonTooltip: 'Tilbakestill rekkefølge til standard rekkefølge for kolonner spesifisert i Prosjektkolonner-listen.', RoleLabel: 'Rolle', - RowLimitLabel: 'Antall elementer', + RowLimitLabel: 'Antall prosjekter å vise', SaveButtonLabel: 'Lagre', SearchBoxGroupName: 'Søkeboks', SearchBoxPlaceholderFallbackText: 'Søk...', diff --git a/SharePointFramework/PortfolioWebParts/src/webparts/latestProjects/index.ts b/SharePointFramework/PortfolioWebParts/src/webparts/latestProjects/index.ts index dc714405b..030b938cd 100644 --- a/SharePointFramework/PortfolioWebParts/src/webparts/latestProjects/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/webparts/latestProjects/index.ts @@ -1,7 +1,6 @@ import { IPropertyPaneConfiguration, PropertyPaneSlider, - PropertyPaneTextField, PropertyPaneToggle } from '@microsoft/sp-property-pane' import { ILatestProjectsProps, LatestProjects } from 'components/LatestProjects' @@ -25,22 +24,14 @@ export default class LatestProjectsWebPart extends BasePortfolioWebPart = (props: IProjectLogoProps) => { + const [showCustomImage, setShowCustomImage] = useState(true) + const { title, url, hidden } = props + + return ( + + ) +} diff --git a/SharePointFramework/shared-library/src/components/ProjectLogo/types.ts b/SharePointFramework/shared-library/src/components/ProjectLogo/types.ts new file mode 100644 index 000000000..e7e6761d2 --- /dev/null +++ b/SharePointFramework/shared-library/src/components/ProjectLogo/types.ts @@ -0,0 +1,21 @@ +/** + * @category ProjectLogo + */ +export interface IProjectLogoProps { + /** + * Project title + * + */ + title: string + + /** + * Project URL + * + */ + url: string + + /** + * Hide content + */ + hidden?: boolean +} diff --git a/SharePointFramework/shared-library/src/components/WebPartTitle/WebPartTitle.module.scss b/SharePointFramework/shared-library/src/components/WebPartTitle/WebPartTitle.module.scss new file mode 100644 index 000000000..ba4a4aae8 --- /dev/null +++ b/SharePointFramework/shared-library/src/components/WebPartTitle/WebPartTitle.module.scss @@ -0,0 +1,8 @@ +.header { + height: 1.3em; + overflow: hidden; + margin-bottom: 0px; + font-size: 20px; + font-weight: 600; + text-overflow: ellipsis; +} diff --git a/SharePointFramework/shared-library/src/components/WebPartTitle/index.tsx b/SharePointFramework/shared-library/src/components/WebPartTitle/index.tsx new file mode 100644 index 000000000..9aec78d19 --- /dev/null +++ b/SharePointFramework/shared-library/src/components/WebPartTitle/index.tsx @@ -0,0 +1,20 @@ +import React, { FC } from 'react' +import { IWebPartTitleProps } from './types' +import styles from './WebPartTitle.module.scss' + +/** + * A component that renders a webpart title properly according to the Fluent UI design guidelines. + * Customizable with the following properties: + * - text: the text to display + * + * @category WebPartTitle + */ +export const WebPartTitle: FC = (props: IWebPartTitleProps) => { + return ( +
+ + {props.text} + +
+ ) +} diff --git a/SharePointFramework/shared-library/src/components/WebPartTitle/types.ts b/SharePointFramework/shared-library/src/components/WebPartTitle/types.ts new file mode 100644 index 000000000..7e81a0547 --- /dev/null +++ b/SharePointFramework/shared-library/src/components/WebPartTitle/types.ts @@ -0,0 +1,10 @@ +/** + * @category WebPartTitle + */ +export interface IWebPartTitleProps { + /** + * Text to show as the title + * + */ + text?: string +} diff --git a/SharePointFramework/shared-library/src/components/index.ts b/SharePointFramework/shared-library/src/components/index.ts index a7c2dcc11..55b9fecdc 100644 --- a/SharePointFramework/shared-library/src/components/index.ts +++ b/SharePointFramework/shared-library/src/components/index.ts @@ -6,3 +6,5 @@ export * from './FilterPanel' export * from './ProjectTimeline' export * from './ConditionalWrapper' export * from './FormFieldContainer' +export * from './WebPartTitle' +export * from './ProjectLogo' diff --git a/SharePointFramework/shared-library/src/models/ItemFieldValues.ts b/SharePointFramework/shared-library/src/models/ItemFieldValues.ts index 955d95223..eebbf0b77 100644 --- a/SharePointFramework/shared-library/src/models/ItemFieldValues.ts +++ b/SharePointFramework/shared-library/src/models/ItemFieldValues.ts @@ -122,7 +122,7 @@ export class ItemFieldValues { if (format === 'object') return (defaultValue ?? {}) as unknown as T return defaultValue } - const value = this._values.get(fieldName) ?? {} + const value = this._values.get(fieldName) ?? {} if (format) return this._getValueInFormat(value, format, options.defaultValue) return value as unknown as T } diff --git a/releasenotes/1.9.0.md b/releasenotes/1.9.0.md index bd8802cb8..457cb7b37 100644 --- a/releasenotes/1.9.0.md +++ b/releasenotes/1.9.0.md @@ -22,6 +22,8 @@ Velkommen til versjon 1.9.0 av Prosjektportalen 365. I denne versjonen er det gj TODO: Skriv om prosjektutlistingswebdelen +TODO: Skriv om siste prosjekter webdelen + ## Footer Det er lagt inn en statisk footer (bunn) i Prosjektportalen, denne vil være synlig på alle Portefølje-sider. Her har man rask tilgang til Områdeinnstillinger og Konfigurasjonsiden av Prosjektportalen. Det er også lagt inn `Nyttige lenker` som er en dialog som dukker opp når du holder over denne, lenkene som vises her kan tilpasses etter eget behov.