From efe75728d496609a09fb06fcb68b5b20fa8965c8 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Thu, 17 Oct 2024 10:00:13 +0200 Subject: [PATCH 01/31] Init IdeaModule --- .github/workflows/ci-channel-test.yml | 1 + .../PortfolioWebParts/config/config.json | 58 +------------------ .../IdeaModule/IdeaModule.module.scss | 7 +++ .../src/components/IdeaModule/IdeaModule.tsx | 30 ++++++++++ .../src/components/IdeaModule/context.ts | 17 ++++++ .../src/components/IdeaModule/index.ts | 2 + .../src/components/IdeaModule/types.ts | 25 ++++++++ .../components/IdeaModule/useIdeaModule.ts | 18 ++++++ .../IdeaModule/useIdeaModuleDataFetch.ts | 29 ++++++++++ .../IdeaModule/useIdeaModuleState.ts | 27 +++++++++ .../PortfolioWebParts/src/data/index.ts | 31 ++++++++++ .../PortfolioWebParts/src/data/types.ts | 12 +++- .../src/webparts/ideaModule/index.ts | 38 ++++++++++++ .../src/webparts/ideaModule/manifest.json | 31 ++++++++++ .../Objects/ClientSidePages/Idemodul.xml | 9 +++ Templates/Portfolio/Resources.en-US.resx | 6 ++ Templates/Portfolio/Resources.no-NB.resx | 6 ++ channels/$schema.json | 6 +- channels/main.json | 3 +- channels/test.json | 3 +- 20 files changed, 297 insertions(+), 62 deletions(-) create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/context.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/index.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleDataFetch.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/manifest.json create mode 100644 Templates/Portfolio/Objects/ClientSidePages/Idemodul.xml diff --git a/.github/workflows/ci-channel-test.yml b/.github/workflows/ci-channel-test.yml index c2372faec..11f54f68e 100644 --- a/.github/workflows/ci-channel-test.yml +++ b/.github/workflows/ci-channel-test.yml @@ -4,6 +4,7 @@ on: push: branches: - releases/1.10 + - feat/ideamodule paths: - 'SharePointFramework/**' - 'Install/**' diff --git a/SharePointFramework/PortfolioWebParts/config/config.json b/SharePointFramework/PortfolioWebParts/config/config.json index 86f2015e0..7353a89c0 100644 --- a/SharePointFramework/PortfolioWebParts/config/config.json +++ b/SharePointFramework/PortfolioWebParts/config/config.json @@ -2,22 +2,6 @@ "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json", "version": "2.0", "bundles": { - "latest-projects-web-part": { - "components": [ - { - "entrypoint": "./lib/webparts/latestProjects/index.js", - "manifest": "./src/webparts/latestProjects/manifest.json" - } - ] - }, - "portfolio-insights-web-part": { - "components": [ - { - "entrypoint": "./lib/webparts/portfolioInsights/index.js", - "manifest": "./src/webparts/portfolioInsights/manifest.json" - } - ] - }, "portfolio-overview-web-part": { "components": [ { @@ -25,46 +9,6 @@ "manifest": "./src/webparts/portfolioOverview/manifest.json" } ] - }, - "project-list-web-part": { - "components": [ - { - "entrypoint": "./lib/webparts/projectList/index.js", - "manifest": "./src/webparts/projectList/manifest.json" - } - ] - }, - "resource-allocation-web-part": { - "components": [ - { - "entrypoint": "./lib/webparts/resourceAllocation/index.js", - "manifest": "./src/webparts/resourceAllocation/manifest.json" - } - ] - }, - "project-timeline-web-part": { - "components": [ - { - "entrypoint": "./lib/webparts/projectTimeline/index.js", - "manifest": "./src/webparts/projectTimeline/manifest.json" - } - ] - }, - "portfolio-aggregation-web-part": { - "components": [ - { - "entrypoint": "./lib/webparts/portfolioAggregation/index.js", - "manifest": "./src/webparts/portfolioAggregation/manifest.json" - } - ] - }, - "project-provision-web-part": { - "components": [ - { - "entrypoint": "./lib/webparts/projectProvision/index.js", - "manifest": "./src/webparts/projectProvision/manifest.json" - } - ] } }, "externals": {}, @@ -76,4 +20,4 @@ "ProjectWebPartsStrings": "node_modules/pp365-projectwebparts/lib/loc/{locale}.js", "SharedLibraryStrings": "node_modules/pp365-shared-library/lib/loc/{locale}.js" } -} +} \ No newline at end of file diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss new file mode 100644 index 000000000..b8ad99659 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss @@ -0,0 +1,7 @@ +.ideaModule { + margin: 0; + + .container { + margin: 0; + } +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx new file mode 100644 index 000000000..c35af7a11 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -0,0 +1,30 @@ +import React, { FC } from 'react' +import styles from './IdeaModule.module.scss' +import { IdeaModuleContext } from './context' +import { IIdeaModuleProps } from './types' +import { useIdeaModule } from './useIdeaModule' +import { FluentProvider, IdPrefixProvider } from '@fluentui/react-components' +import { customLightTheme } from 'pp365-shared-library' + +export const IdeaModule: FC = (props) => { + const { state, setState, fluentProviderId } = useIdeaModule(props) + + return ( +
+ + + +
+

Bring your ideas to life. Here you can create, share and collaborate on ideas.

+

With the new Idea module, you can create and share ideas with your team. You can also collaborate on ideas with your team members.

+
+
+
+
+
+ ) +} + +IdeaModule.defaultProps = { + configurationList: 'Idékonfigurasjon' +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/context.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/context.ts new file mode 100644 index 000000000..064d58814 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/context.ts @@ -0,0 +1,17 @@ +import { createContext, useContext } from 'react' +import { IIdeaModuleProps, IIdeaModuleState } from './types' + +export interface IIdeaModuleContext { + props: IIdeaModuleProps + state: IIdeaModuleState + setState: (newState: Partial) => void +} + +export const IdeaModuleContext = createContext(null) + +/** + * Hook to get the `ProjectProvisionContext` + */ +export function useProjectProvisionContext() { + return useContext(IdeaModuleContext) +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/index.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/index.ts new file mode 100644 index 000000000..764ce7a72 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/index.ts @@ -0,0 +1,2 @@ +export * from './IdeaModule' +export * from './types' diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts new file mode 100644 index 000000000..44f6a5e3e --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts @@ -0,0 +1,25 @@ +import { IBaseComponentProps } from '../types' + +export interface IIdeaModuleProps extends IBaseComponentProps { + configurationList: string +} + +export interface IIdeaModuleState { + loading?: boolean + isRefetching?: boolean + configuration?: ConfigurationItem[] + ideas?: Record +} + +export interface IIdeaModuleHashState { + ideaId?: string +} + +export class ConfigurationItem { + title: string + description: string + ideaProcessingList: string + ideaRegistrationList: string + ideaProcessingChoices: string + ideaRegistrationChoices: string +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts new file mode 100644 index 000000000..efa17787b --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts @@ -0,0 +1,18 @@ +import { useId } from '@fluentui/react-components' +import { IIdeaModuleProps } from './types' +import { useIdeaModuleState } from './useIdeaModuleState' + +/** + * Component logic hook for `IdeaModule` component. + * +clear */ +export function useIdeaModule(props: IIdeaModuleProps) { + const { state, setState } = useIdeaModuleState() + const fluentProviderId = useId('fp-idea-module') + + return { + state, + setState, + fluentProviderId + } +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleDataFetch.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleDataFetch.ts new file mode 100644 index 000000000..07b8f5f3b --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleDataFetch.ts @@ -0,0 +1,29 @@ +import { useEffect } from 'react' +import { IIdeaModuleProps, IIdeaModuleState } from './types' + +/** + * Component data fetch hook for `IdeaModule`. This hook is responsible for + * fetching data and setting state. + * + * @param props Props + * @param refetch Timestamp for refetch. Changes to this variable refetches the data in `useEffect` + * @param setState Set state callback + */ +export function useIdeaModuleDataFetch( + props: IIdeaModuleProps, + refetch: number, + setState: (newState: Partial) => void +) { + useEffect(() => { + Promise.all([props.dataAdapter.getConfiguration(props.configurationList)]).then( + ([configuration]) => { + setState({ + configuration, + ideas: configuration, + loading: false, + isRefetching: false + }) + } + ) + }, [refetch]) +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts new file mode 100644 index 000000000..a276d27b7 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts @@ -0,0 +1,27 @@ +/* eslint-disable prefer-spread */ +import { useState } from 'react' +import { IIdeaModuleState } from './types' + +/** + * Component state hook for `IIdeaModule`. + * + * @param props Props + */ +export function useIdeaModuleState() { + const [state, $setState] = useState({ + loading: true, + configuration: [], + ideas: {} + }) + + /** + * Set state like `setState` in class components where + * the new state is merged with the current state. + * + * @param newState New state + */ + const setState = (newState: Partial) => + $setState((currentState) => ({ ...currentState, ...newState })) + + return { state, setState } as const +} diff --git a/SharePointFramework/PortfolioWebParts/src/data/index.ts b/SharePointFramework/PortfolioWebParts/src/data/index.ts index 6861ea5b1..19ee19ab0 100644 --- a/SharePointFramework/PortfolioWebParts/src/data/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/data/index.ts @@ -52,6 +52,7 @@ import { } from './types' import { IPersonaProps, IPersonaSharedProps } from '@fluentui/react' import { IProvisionRequestItem } from 'interfaces/IProvisionRequestItem' +import { ConfigurationItem } from 'components/IdeaModule' /** * Data adapter for Portfolio Web Parts. @@ -1027,6 +1028,36 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { return false } } + + public async getConfiguration(listName: string): Promise { + try { + const configurationList = this._sp.web.lists.getByTitle(listName) + const spItems = await configurationList.items + .select( + 'Id', + 'Title', + 'GtDescription', + 'GtIdeaProcessingList', + 'GtIdeaRegistrationList', + 'GtIdeaProcessingChoices', + 'GtIdeaRegistrationChoices' + ) + .using(DefaultCaching)() + + return spItems.map((item) => { + return { + title: item.Title, + description: item.GtDescription, + ideaProcessingList: item.GtIdeaProcessingList, + ideaRegistrationList: item.GtIdeaRegistrationList, + ideaProcessingChoices: item.GtIdeaProcessingChoices, + ideaRegistrationChoices: item.GtIdeaRegistrationChoices + } + }) + } catch (error) { + return [] + } + } } export * from './types' diff --git a/SharePointFramework/PortfolioWebParts/src/data/types.ts b/SharePointFramework/PortfolioWebParts/src/data/types.ts index 32ac016fc..ef12d86c3 100644 --- a/SharePointFramework/PortfolioWebParts/src/data/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/data/types.ts @@ -16,6 +16,7 @@ import { import { IPortfolioAggregationConfiguration, IPortfolioOverviewConfiguration } from '../components' import { IPersonaSharedProps } from '@fluentui/react' import { IProvisionRequestItem } from 'interfaces/IProvisionRequestItem' +import { ConfigurationItem } from 'components/IdeaModule' export interface IFetchDataForViewItemResult extends ISearchResult { SiteId: string @@ -298,9 +299,9 @@ export interface IPortfolioWebPartsDataAdapter { ): Promise /** - * Retrieves the provision request settings from the "Provisioning Request Settings" list + * Retrieves the configuration from the "Provisioning Request Settings" list * - * @returns A Promise that resolves to an array containing the provision request settings. + * @returns A Promise that resolves to an array containing the configuration. */ getProvisionRequestSettings?(provisionUrl: string): Promise @@ -360,4 +361,11 @@ export interface IPortfolioWebPartsDataAdapter { * */ siteExists?(siteUrl: string): Promise + + /** + * Retrieves the configuration from the "Idékonfigurasjon" list + * + * @returns A Promise that resolves to an array containing the configuration. + */ + getConfiguration?(listName: string): Promise } diff --git a/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts b/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts new file mode 100644 index 000000000..9b922386e --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts @@ -0,0 +1,38 @@ +/* eslint-disable quotes */ +import { IPropertyPaneConfiguration, PropertyPaneTextField } from '@microsoft/sp-property-pane' +import * as strings from 'PortfolioWebPartsStrings' +import { BasePortfolioWebPart } from '../basePortfolioWebPart' +import { IIdeaModuleProps, IdeaModule } from 'components/IdeaModule' + +export default class IdeaModuleWebPart extends BasePortfolioWebPart { + public render(): void { + this.renderComponent(IdeaModule) + } + + public async onInit(): Promise { + await super.onInit() + } + + public getPropertyPaneConfiguration(): IPropertyPaneConfiguration { + return { + pages: [ + { + header: { + description: 'Idémodul' + }, + groups: [ + { + groupName: strings.GeneralGroupName, + groupFields: [ + PropertyPaneTextField('configurationList', { + label: 'Konfigurasjon liste', + description: 'Navn på Idékonfigurasjonsliste' + }) + ] + } + ] + } + ] + } + } +} diff --git a/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/manifest.json b/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/manifest.json new file mode 100644 index 000000000..30fe9372b --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/manifest.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json", + "id": "20f151a9-6891-4408-a6d6-77e749b9e3e7", + "alias": "IdeaModuleWebPart", + "componentType": "WebPart", + "version": "1.10.0", + "manifestVersion": 2, + "requiresCustomScript": false, + "hiddenFromToolbox": true, + "supportedHosts": [ + "SharePointWebPart" + ], + "preconfiguredEntries": [ + { + "groupId": "5c03119e-3074-46fd-976b-c60198311f70", + "group": { + "default": "Other" + }, + "title": { + "default": "Idémodul" + }, + "description": { + "default": " " + }, + "officeFabricIconFontName": "Lightbulb", + "properties": { + "title": "Idémodul" + } + } + ] +} diff --git a/Templates/Portfolio/Objects/ClientSidePages/Idemodul.xml b/Templates/Portfolio/Objects/ClientSidePages/Idemodul.xml new file mode 100644 index 000000000..e24a56768 --- /dev/null +++ b/Templates/Portfolio/Objects/ClientSidePages/Idemodul.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Templates/Portfolio/Resources.en-US.resx b/Templates/Portfolio/Resources.en-US.resx index 482cd0031..1d9e67f4e 100644 --- a/Templates/Portfolio/Resources.en-US.resx +++ b/Templates/Portfolio/Resources.en-US.resx @@ -2672,6 +2672,12 @@ Project Timeline + + Idemodul.aspx + + + Idea module + Låst diff --git a/Templates/Portfolio/Resources.no-NB.resx b/Templates/Portfolio/Resources.no-NB.resx index 68f21bea3..9895c1b80 100644 --- a/Templates/Portfolio/Resources.no-NB.resx +++ b/Templates/Portfolio/Resources.no-NB.resx @@ -2896,6 +2896,12 @@ Prosjekttidslinje + + Idemodul.aspx + + + Idémodul + Redigere prosjektegenskaper diff --git a/channels/$schema.json b/channels/$schema.json index e18c9694d..37e4e03d6 100644 --- a/channels/$schema.json +++ b/channels/$schema.json @@ -102,6 +102,9 @@ }, "ProjectProvisionWebPart": { "$ref": "#/definitions/guid" + }, + "IdeaModuleWebPart": { + "$ref": "#/definitions/guid" } }, "required": [ @@ -112,7 +115,8 @@ "ProjectListWebPart", "PortfolioTimelineWebPart", "ResourceAllocationWebPart", - "ProjectProvisionWebPart" + "ProjectProvisionWebPart", + "IdeaModuleWebPart" ] } }, diff --git a/channels/main.json b/channels/main.json index 57f50edda..8667cd4c2 100644 --- a/channels/main.json +++ b/channels/main.json @@ -26,7 +26,8 @@ "ProjectListWebPart": "54fbeb7d-e463-4dcc-8873-50a3ab2f0f68", "PortfolioTimelineWebPart": "7284c568-f66c-4218-bb2c-3734a3cfa581", "ResourceAllocationWebPart": "2ef269b2-6370-4841-8b35-2185b7ccb22a", - "ProjectProvisionWebPart": "e88cea29-09a0-4ce4-a38c-e0d74b65f619" + "ProjectProvisionWebPart": "e88cea29-09a0-4ce4-a38c-e0d74b65f619", + "IdeaModuleWebPart": "20f151a9-6891-4408-a6d6-77e749b9e3e7" } }, "ProgramWebParts": { diff --git a/channels/test.json b/channels/test.json index 3711e2290..e87bf29a6 100644 --- a/channels/test.json +++ b/channels/test.json @@ -26,7 +26,8 @@ "ProjectListWebPart": "397a0947-7d83-4084-871b-d8e0d1edc770", "PortfolioTimelineWebPart": "58446c7e-4a78-49e0-8ae0-2efee199e58d", "ResourceAllocationWebPart": "29759c81-896f-4128-818c-e2fc60ff0087", - "ProjectProvisionWebPart": "971a1fc0-fb4c-4a63-860d-e08a69af6f9e" + "ProjectProvisionWebPart": "971a1fc0-fb4c-4a63-860d-e08a69af6f9e", + "IdeaModuleWebPart": "c445a5e8-0bce-47e7-b4d0-c519bfaaad77" } }, "ProgramWebParts": { From 4f5b4a9f2107e4caa1034c99619c917c8489deca Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Thu, 17 Oct 2024 11:17:20 +0200 Subject: [PATCH 02/31] Init IdeaModule v2 --- .../PortfolioWebParts/config/config.json | 66 +++- .../PortfolioWebParts/package.json | 1 + .../IdeaList/Commands/Commands.module.scss | 23 ++ .../IdeaModule/IdeaList/Commands/Commands.tsx | 33 ++ .../IdeaModule/IdeaList/Commands/index.ts | 1 + .../IdeaList/Commands/renderModes.tsx | 22 ++ .../IdeaList/Commands/useCommands.tsx | 70 ++++ .../IdeaModule/IdeaList/IdeaList.module.scss | 41 ++ .../IdeaModule/IdeaList/IdeaList.tsx | 56 +++ .../IdeaModule/IdeaList/List/List.module.scss | 23 ++ .../IdeaModule/IdeaList/List/List.tsx | 49 +++ .../IdeaModule/IdeaList/List/context.ts | 20 + .../IdeaModule/IdeaList/List/index.ts | 2 + .../IdeaModule/IdeaList/List/types.ts | 7 + .../IdeaModule/IdeaList/List/useColumns.tsx | 131 ++++++ .../IdeaModule/IdeaList/List/useList.ts | 30 ++ .../components/IdeaModule/IdeaList/index.tsx | 2 + .../components/IdeaModule/IdeaList/types.ts | 10 + .../IdeaModule/IdeaList/useIdeaList.ts | 21 + .../IdeaList/useIdeaListRenderer.tsx | 93 +++++ .../IdeaModule/IdeaModule.module.scss | 11 +- .../src/components/IdeaModule/IdeaModule.tsx | 171 +++++++- .../src/components/IdeaModule/context.ts | 4 +- .../src/components/IdeaModule/types.ts | 12 + .../components/IdeaModule/useIdeaModule.ts | 2 +- .../IdeaModule/useIdeaModuleState.ts | 12 +- .../src/models/IdeaListModel.ts | 13 + .../PortfolioWebParts/src/models/index.ts | 1 + .../Portfolio/Objects/ClientSidePages/@.xml | 1 + common/config/rush/pnpm-lock.yaml | 374 +++++++++++++++++- 30 files changed, 1266 insertions(+), 36 deletions(-) create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/Commands.module.scss create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/Commands.tsx create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/index.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/renderModes.tsx create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/useCommands.tsx create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/IdeaList.module.scss create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/IdeaList.tsx create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/List.module.scss create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/List.tsx create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/context.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/index.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/types.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/useColumns.tsx create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/useList.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/index.tsx create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/types.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/useIdeaList.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/useIdeaListRenderer.tsx create mode 100644 SharePointFramework/PortfolioWebParts/src/models/IdeaListModel.ts diff --git a/SharePointFramework/PortfolioWebParts/config/config.json b/SharePointFramework/PortfolioWebParts/config/config.json index 7353a89c0..8bba29d5e 100644 --- a/SharePointFramework/PortfolioWebParts/config/config.json +++ b/SharePointFramework/PortfolioWebParts/config/config.json @@ -2,6 +2,22 @@ "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json", "version": "2.0", "bundles": { + "latest-projects-web-part": { + "components": [ + { + "entrypoint": "./lib/webparts/latestProjects/index.js", + "manifest": "./src/webparts/latestProjects/manifest.json" + } + ] + }, + "portfolio-insights-web-part": { + "components": [ + { + "entrypoint": "./lib/webparts/portfolioInsights/index.js", + "manifest": "./src/webparts/portfolioInsights/manifest.json" + } + ] + }, "portfolio-overview-web-part": { "components": [ { @@ -9,6 +25,54 @@ "manifest": "./src/webparts/portfolioOverview/manifest.json" } ] + }, + "project-list-web-part": { + "components": [ + { + "entrypoint": "./lib/webparts/projectList/index.js", + "manifest": "./src/webparts/projectList/manifest.json" + } + ] + }, + "resource-allocation-web-part": { + "components": [ + { + "entrypoint": "./lib/webparts/resourceAllocation/index.js", + "manifest": "./src/webparts/resourceAllocation/manifest.json" + } + ] + }, + "project-timeline-web-part": { + "components": [ + { + "entrypoint": "./lib/webparts/projectTimeline/index.js", + "manifest": "./src/webparts/projectTimeline/manifest.json" + } + ] + }, + "portfolio-aggregation-web-part": { + "components": [ + { + "entrypoint": "./lib/webparts/portfolioAggregation/index.js", + "manifest": "./src/webparts/portfolioAggregation/manifest.json" + } + ] + }, + "project-provision-web-part": { + "components": [ + { + "entrypoint": "./lib/webparts/projectProvision/index.js", + "manifest": "./src/webparts/projectProvision/manifest.json" + } + ] + }, + "idea-module-web-part": { + "components": [ + { + "entrypoint": "./lib/webparts/ideaModule/index.js", + "manifest": "./src/webparts/ideaModule/manifest.json" + } + ] } }, "externals": {}, @@ -20,4 +84,4 @@ "ProjectWebPartsStrings": "node_modules/pp365-projectwebparts/lib/loc/{locale}.js", "SharedLibraryStrings": "node_modules/pp365-shared-library/lib/loc/{locale}.js" } -} \ No newline at end of file +} diff --git a/SharePointFramework/PortfolioWebParts/package.json b/SharePointFramework/PortfolioWebParts/package.json index 35c2b75d6..7d941d2ca 100644 --- a/SharePointFramework/PortfolioWebParts/package.json +++ b/SharePointFramework/PortfolioWebParts/package.json @@ -65,6 +65,7 @@ "react-virtualized-auto-sizer": "~1.0.24", "@fluentui/react-datepicker-compat": "~0.4.38", "@fluentui/react-motion-preview": "~0.5.20", + "@fluentui/react-nav-preview": "~0.9.1", "react-gauge-component": "~1.2.61" }, "devDependencies": { diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/Commands.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/Commands.module.scss new file mode 100644 index 000000000..68f1a97cf --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/Commands.module.scss @@ -0,0 +1,23 @@ +@import "~@fluentui/react/dist/sass/References.scss"; + +.commands { + display: flex; + flex-direction: row; + max-width: 1024px; + gap: 15px; + + .search { + width: 100%; + max-width: 100%; + + .searchBox { + box-shadow: var(--shadow2); + width: inherit; + max-width: inherit; + } + } + + .sortBy { + box-shadow: var(--shadow2); + } +} \ No newline at end of file diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/Commands.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/Commands.tsx new file mode 100644 index 000000000..4beab421d --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/Commands.tsx @@ -0,0 +1,33 @@ +import { SearchBox } from '@fluentui/react-components' +import _ from 'lodash' +import { Toolbar } from 'pp365-shared-library' +import React, { FC } from 'react' +import styles from './Commands.module.scss' +import { useCommands } from './useCommands' +import { useIdeaModuleContext } from 'components/IdeaModule/context' + +export const Commands: FC = () => { + const context = useIdeaModuleContext() + const { toolbarItems, searchBoxPlaceholder } = useCommands() + return ( +
+ + +
+ ) +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/index.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/index.ts new file mode 100644 index 000000000..aad501dd2 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/index.ts @@ -0,0 +1 @@ +export * from './Commands' diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/renderModes.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/renderModes.tsx new file mode 100644 index 000000000..1e3c011a1 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/renderModes.tsx @@ -0,0 +1,22 @@ +import strings from 'PortfolioWebPartsStrings' +import { getFluentIcon } from 'pp365-shared-library' +import { IRenderMode } from '../types' +import { FluentIcon } from '@fluentui/react-icons' + +export const renderModes: IRenderMode[] = [ + { + value: 'tiles', + text: strings.RenderModeTilesText, + icon: getFluentIcon('Grid', { jsx: false }) + }, + { + value: 'list', + text: strings.RenderModeListText, + icon: getFluentIcon('AppsList', { jsx: false }) + }, + { + value: 'compactList', + text: strings.RenderModeCompactListText, + icon: getFluentIcon('TextBulletList', { jsx: false }) + } +] diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/useCommands.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/useCommands.tsx new file mode 100644 index 000000000..58235ff2e --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/useCommands.tsx @@ -0,0 +1,70 @@ +import { format } from '@fluentui/react' +import strings from 'PortfolioWebPartsStrings' +import _ from 'lodash' +import { ListMenuItem, getFluentIcon } from 'pp365-shared-library' +import { useContext, useMemo } from 'react' +import { ProjectListContext } from '../context' +import { ProjectListRenderMode } from '../types' +import { renderModes } from './renderModes' + +/** + * Component logic hook for `Commands`. This hook is responsible for + * rendering the toolbar and handling its actions. + */ +export function useCommands() { + const context = useContext(ProjectListContext) + const selectedRenderMode = useMemo( + () => renderModes.find((renderMode) => renderMode.value === context.state.renderMode), + [context.state.renderMode] + ) + const showSortBy = useMemo( + () => context.props.showSortBy && context.state.renderMode === 'tiles', + [context.props.showSortBy, context.state.renderMode] + ) + + const toolbarItems = [ + new ListMenuItem(selectedRenderMode?.text, null) + .setHidden(!context.props.showRenderModeSelector) + .setIcon(selectedRenderMode?.icon) + .setWidth('fit-content') + .setStyle({ minWidth: '145px' }) + .setItems( + renderModes.map(({ value, text, icon }) => + new ListMenuItem(text, null) + .setIcon(icon) + .makeCheckable({ + name: 'renderMode', + value + }) + .setOnClick(() => { + context.setState({ + renderMode: value as ProjectListRenderMode + }) + }) + ), + { renderMode: [context.state.renderMode] } + ), + new ListMenuItem(null, format(strings.SortCardsByLabel, context.props.sortBy)) + .setHidden(!showSortBy) + .setIcon( + context.state.sort?.isSortedDescending + ? getFluentIcon('TextSortAscending', { jsx: false }) + : getFluentIcon('TextSortDescending', { jsx: false }) + ) + .setOnClick(() => { + context.setState({ + sort: { + fieldName: context.state.sort?.fieldName ?? context.props.sortBy, + isSortedDescending: !context.state.sort?.isSortedDescending + } + }) + }) + ] + + const searchBoxPlaceholder = + !context.state.isDataLoaded || _.isEmpty(context.state.projects) + ? '' + : format(context.state.selectedVertical.searchBoxPlaceholder, context.projects.length) + + return { toolbarItems, searchBoxPlaceholder } +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/IdeaList.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/IdeaList.module.scss new file mode 100644 index 000000000..0cc728e17 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/IdeaList.module.scss @@ -0,0 +1,41 @@ +.ideaList { + .tabs { + align-items: flex-start; + display: flex; + flex-direction: column; + justify-content: flex-start; + padding: 15px 0px; + row-gap: 20px; + flex-direction: row; + } + + .emptyMessage { + flex-direction: column; + margin: 30px 0 0 0; + } + + .ideas { + flex-direction: column; + margin: 10px 0 0 0; + display: flex; + flex-wrap: wrap; + flex-direction: row; + max-width: 1024px; + gap: 12px; + + .ideasSection { + width: 100%; + overflow-x: hidden !important; + + .ideaRow { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + align-content: flex-start; + gap: 12px; + left: 4px !important; + } + } + } +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/IdeaList.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/IdeaList.tsx new file mode 100644 index 000000000..8b3d73dd1 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/IdeaList.tsx @@ -0,0 +1,56 @@ +import { FluentProvider, IdPrefixProvider } from '@fluentui/react-components' +import * as strings from 'PortfolioWebPartsStrings' +import { UserMessage, customLightTheme } from 'pp365-shared-library' +import React, { FC } from 'react' +import { isEmpty } from 'underscore' +import styles from './IdeaList.module.scss' +import { useIdeaList } from './useIdeaList' +import { useIdeaListRenderer } from './useIdeaListRenderer' +import { Commands } from './Commands' +import { IIdeaModuleProps } from '../types' + +export const IdeaList: FC = (props) => { + const { context, fluentProviderId } = useIdeaList(props) + const renderIdeas = useIdeaListRenderer(context) + + if (context.state.ideas.length === 0) { + return ( + +
+ +
+
+ ) + } + + if (context.state.error) { + return ( + +
+ +
+
+ ) + } + + return ( + + + + {context.state.loading && isEmpty(context.state.ideas) && ( +
+ +
+ )} +
{renderIdeas(context.state.ideas)}
+
+
+ ) +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/List.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/List.module.scss new file mode 100644 index 000000000..ef7631393 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/List.module.scss @@ -0,0 +1,23 @@ +@import "~@fluentui/react/dist/sass/References.scss"; + +.list { + width: 996px; + + div:first-child { + max-width: inherit; + } + + .logo { + width: 48px; + height: 48px; + + img { + 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/IdeaModule/IdeaList/List/List.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/List.tsx new file mode 100644 index 000000000..0f49e5a6b --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/List.tsx @@ -0,0 +1,49 @@ +import { + DataGrid, + DataGridBody, + DataGridCell, + DataGridHeader, + DataGridHeaderCell, + DataGridRow +} from '@fluentui/react-components' +import { ProjectListModel } from 'pp365-shared-library/lib/models' +import * as React from 'react' +import { useContext } from 'react' +import styles from './List.module.scss' +import { ListContext } from './context' +import { useList } from './useList' + +export const List = () => { + const context = useContext(ListContext) + const { columnSizingOptions, columns, defaultSortState } = useList() + + return ( +
+ + + + {({ renderHeaderCell }) => ( + {renderHeaderCell()} + )} + + + > + {({ item, rowId }) => ( + key={rowId}> + {({ renderCell }) => {renderCell(item)}} + + )} + + +
+ ) +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/context.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/context.ts new file mode 100644 index 000000000..90a04e619 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/context.ts @@ -0,0 +1,20 @@ +import { createContext } from 'react' +import { IdeaListModel } from 'models' +import { IIdeaModuleProps } from 'components/IdeaModule' + +/** + * Interface representing the context for the Project List component. + */ +export interface IListContext extends IIdeaModuleProps { + /** + * Ideas to be rendered in the list. + */ + ideas?: Record + + /** + * Size that determines the list appearance. + */ + size?: 'extra-small' | 'small' | 'medium' +} + +export const ListContext = createContext(null) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/index.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/index.ts new file mode 100644 index 000000000..6f3d09941 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/index.ts @@ -0,0 +1,2 @@ +export * from './List' +export * from './context' diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/types.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/types.ts new file mode 100644 index 000000000..bcd2bd7d7 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/types.ts @@ -0,0 +1,7 @@ +import { TableColumnDefinition } from '@fluentui/react-components' +import { ProjectListModel } from 'pp365-shared-library' + +export interface IListColumn extends TableColumnDefinition { + minWidth?: number + defaultWidth?: number +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/useColumns.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/useColumns.tsx new file mode 100644 index 000000000..f922380cf --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/useColumns.tsx @@ -0,0 +1,131 @@ +import { Avatar, Link, TableCellLayout } from '@fluentui/react-components' +import * as strings from 'PortfolioWebPartsStrings' +import { ProjectLogo } from 'pp365-shared-library' +import React, { useContext } from 'react' +import { ProjectMenu } from '../ProjectMenu' +import { ListContext } from './context' +import { IListColumn } from './types' + +export const useColumns = (): IListColumn[] => { + const context = useContext(ListContext) + return [ + { + columnId: 'logo', + defaultWidth: 48, + minWidth: 48, + compare: () => null, + renderHeaderCell: () => null, + renderCell: (item) => { + return ( + + ) + } + }, + { + columnId: 'title', + defaultWidth: 280, + compare: (a, b) => { + return (a.title ?? '').localeCompare(b.title ?? '') + }, + renderHeaderCell: () => { + return strings.TitleLabel + }, + renderCell: (item) => { + return ( + + {item.hasUserAccess ? ( + + {item.title} + + ) : ( + <>{item.title} + )} + + ) + } + }, + { + columnId: 'phase', + defaultWidth: 120, + compare: (a, b) => { + return (a.phase || '').localeCompare(b.phase || '') + }, + renderHeaderCell: () => { + return strings.PhaseLabel + }, + renderCell: (item) => { + return ( + + {item.phase || ''} + + ) + } + }, + { + columnId: 'owner', + defaultWidth: 180, + compare: (a, b) => { + return a.owner?.name?.localeCompare(b.owner?.name || '') + }, + renderHeaderCell: () => { + return strings.ProjectOwner + }, + renderCell: (item) => { + return ( + + {item.owner?.name} + + ) + } + }, + { + columnId: 'manager', + defaultWidth: 180, + compare: (a, b) => { + return a.manager?.name?.localeCompare(b.manager?.name || '') + }, + renderHeaderCell: () => { + return strings.ProjectManager + }, + renderCell: (item) => { + return ( + + {' '} + {item.manager?.name} + + ) + } + }, + { + columnId: 'actions', + defaultWidth: 40, + minWidth: 40, + compare: () => { + return -1 + }, + renderHeaderCell: () => { + return null + }, + renderCell: (item) => { + return ( + + ) + } + } + ] +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/useList.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/useList.ts new file mode 100644 index 000000000..91bc7c330 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/useList.ts @@ -0,0 +1,30 @@ +import { SortDirection, TableColumnSizingOptions } from '@fluentui/react-components' +import { useColumns } from './useColumns' + +/** + * A hook that provides the necessary data for rendering a list of projects. + * + * @returns An object containing the necessary data for rendering a list of projects. + */ +export function useList() { + const columns = useColumns() + + const columnSizingOptions: TableColumnSizingOptions = columns.reduce( + (options, col) => ({ + ...options, + [col.columnId]: { + defaultWidth: col.defaultWidth, + minWidth: col.minWidth + } + }), + {} + ) + + const defaultSortState = { sortColumn: 'title', sortDirection: 'ascending' as SortDirection } + + return { + columnSizingOptions, + columns, + defaultSortState + } +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/index.tsx new file mode 100644 index 000000000..74e59425d --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/index.tsx @@ -0,0 +1,2 @@ +export * from './IdeaList' +export * from './types' diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/types.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/types.ts new file mode 100644 index 000000000..d01b3fd91 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/types.ts @@ -0,0 +1,10 @@ +import { FluentIcon } from '@fluentui/react-icons/lib/utils/createFluentIcon' +import { IBaseComponentProps } from 'components/types' + +export interface IRenderMode { + value?: string + text?: string + icon?: FluentIcon +} + +export type IIdeaListProps = IBaseComponentProps diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/useIdeaList.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/useIdeaList.ts new file mode 100644 index 000000000..961b8ce4d --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/useIdeaList.ts @@ -0,0 +1,21 @@ +/* eslint-disable prefer-spread */ +import { useId } from '@fluentui/react-components' +import { IIdeaListProps } from './types' +import { useIdeaModuleContext } from '../context' + +/** + * Component logic hook for `IdeaList`. This hook is responsible for + * fetching data, sorting, filtering and other logic. + * + * @param props Props + */ +export const useIdeaList = (props: IIdeaListProps) => { + const context = useIdeaModuleContext() + const fluentProviderId = useId('fp-idea-list') + + return { + context, + props, + fluentProviderId + } +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/useIdeaListRenderer.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/useIdeaListRenderer.tsx new file mode 100644 index 000000000..dff1b1788 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/useIdeaListRenderer.tsx @@ -0,0 +1,93 @@ +import React, { FC, useMemo } from 'react' +import AutoSizer from 'react-virtualized-auto-sizer' +import { FixedSizeList } from 'react-window' +import { List, ListContext } from './List' +import styles from './IdeaList.module.scss' +import { IIdeaModuleContext } from '../context' + +export function useIdeaListRenderer({ props, state }: IIdeaModuleContext) { + /** + * Render ideas based on `state.renderMode`. + * + * @param ideas - ideas to render + */ + const renderIdeas = (ideas: Record) => { + const projectRow: FC<{ + index: number + style: React.CSSProperties + itemsPerRow: number + }> = ({ index, style, itemsPerRow }) => { + const items = useMemo(() => { + const convertedIndex = index * itemsPerRow + const items = [] + + items.push( + ...ideas + .slice(convertedIndex, convertedIndex + itemsPerRow) + .filter(Boolean) + .map((idea, idx) => ( + <> + {idx}: {idea.title} + + // + // + // + )) + ) + return items + }, [itemsPerRow]) + + return ( +
+ {items} +
+ ) + } + + switch (state.renderMode) { + case 'tiles': { + return ( + + {({ width }) => { + const cardWidth = 242 + const itemsPerRow = Math.floor(width / cardWidth) + const itemCount = Math.ceil(ideas.length / itemsPerRow) + const listHeight = Math.ceil(itemCount * 300) + + return ( + + {({ index, style }) => projectRow({ index, style, itemsPerRow })} + + ) + }} + + ) + } + case 'list': + case 'compactList': { + const size = state.renderMode === 'list' ? 'medium' : 'extra-small' + return ( + + + + ) + } + } + } + + return renderIdeas +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss index b8ad99659..4318b194a 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss @@ -1,7 +1,14 @@ .ideaModule { margin: 0; + overflow: hidden; + display: flex; + height: 100%; - .container { - margin: 0; + .content { + flex: 1; + padding: 16px; + display: grid; + justify-content: flex-start; + align-items: flex-start; } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index c35af7a11..55c096521 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -1,22 +1,176 @@ -import React, { FC } from 'react' +import React, { FC, useState } from 'react' import styles from './IdeaModule.module.scss' import { IdeaModuleContext } from './context' import { IIdeaModuleProps } from './types' import { useIdeaModule } from './useIdeaModule' -import { FluentProvider, IdPrefixProvider } from '@fluentui/react-components' +import { FluentProvider, IdPrefixProvider, Tooltip } from '@fluentui/react-components' import { customLightTheme } from 'pp365-shared-library' +import { + Hamburger, + NavCategory, + NavCategoryItem, + NavDrawer, + NavDrawerBody, + NavDrawerHeader, + NavItem, + NavSectionHeader, + NavSubItem, + NavSubItemGroup, + NavSize, + NavDivider, + AppItemStatic +} from '@fluentui/react-nav-preview' +import { + Board20Filled, + Board20Regular, + BoxMultiple20Filled, + BoxMultiple20Regular, + DataArea20Filled, + DataArea20Regular, + DocumentBulletListMultiple20Filled, + DocumentBulletListMultiple20Regular, + HeartPulse20Filled, + HeartPulse20Regular, + MegaphoneLoud20Filled, + MegaphoneLoud20Regular, + NotePin20Filled, + NotePin20Regular, + People20Filled, + People20Regular, + PeopleStar20Filled, + PeopleStar20Regular, + Person20Filled, + PersonLightbulb20Filled, + PersonLightbulb20Regular, + Person20Regular, + PersonSearch20Filled, + PersonSearch20Regular, + PreviewLink20Filled, + PreviewLink20Regular, + bundleIcon, + PersonCircle24Regular +} from '@fluentui/react-icons' + +const Person = bundleIcon(Person20Filled, Person20Regular) +const Dashboard = bundleIcon(Board20Filled, Board20Regular) +const Announcements = bundleIcon(MegaphoneLoud20Filled, MegaphoneLoud20Regular) +const EmployeeSpotlight = bundleIcon(PersonLightbulb20Filled, PersonLightbulb20Regular) +const Search = bundleIcon(PersonSearch20Filled, PersonSearch20Regular) +const PerformanceReviews = bundleIcon(PreviewLink20Filled, PreviewLink20Regular) +const JobPostings = bundleIcon(NotePin20Filled, NotePin20Regular) +const Interviews = bundleIcon(People20Filled, People20Regular) +const HealthPlans = bundleIcon(HeartPulse20Filled, HeartPulse20Regular) +const TrainingPrograms = bundleIcon(BoxMultiple20Filled, BoxMultiple20Regular) +const CareerDevelopment = bundleIcon(PeopleStar20Filled, PeopleStar20Regular) +const Analytics = bundleIcon(DataArea20Filled, DataArea20Regular) +const Reports = bundleIcon(DocumentBulletListMultiple20Filled, DocumentBulletListMultiple20Regular) export const IdeaModule: FC = (props) => { const { state, setState, fluentProviderId } = useIdeaModule(props) + const [size, setNavSize] = useState('small') + const [enabledLinks, setEnabledLinks] = useState(true) + + const linkDestination = enabledLinks ? '#' : '' + return (
-
-

Bring your ideas to life. Here you can create, share and collaborate on ideas.

-

With the new Idea module, you can create and share ideas with your team. You can also collaborate on ideas with your team members.

+ + + + + + + + }>Idémodul + } value='1'> + Dashboard + + } value='2'> + Announcements + + } value='3'> + Employee Spotlight + + } href={linkDestination} value='4'> + Profile Search + + } href={linkDestination} value='5'> + Performance Reviews + + + Employee Management + + }>Job Postings + + + Openings + + + Submissions + + + + } value='9'> + Interviews + + + Benefits + } value='10'> + Health Plans + + + }>Retirement + + + Plan Information + + + Fund Performance + + + + + + } value='15'> + Training Programs + + + }>Career Development + + + Career Paths + + + Planning + + + + } value='19'> + Workforce Data + + } value='20'> + Reports + + + +
+

+ Bring your ideas to life. Here you can create, share and collaborate on ideas. +

+

+ With the new Idea module, you can create and share ideas with your team. You can + also collaborate on ideas with your team members. +

@@ -26,5 +180,10 @@ export const IdeaModule: FC = (props) => { } IdeaModule.defaultProps = { - configurationList: 'Idékonfigurasjon' + configurationList: 'Idékonfigurasjon', + sortBy: 'Title', + showSearchBox: true, + showRenderModeSelector: true, + showSortBy: true, + defaultRenderMode: 'tiles' } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/context.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/context.ts index 064d58814..e62140062 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/context.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/context.ts @@ -10,8 +10,8 @@ export interface IIdeaModuleContext { export const IdeaModuleContext = createContext(null) /** - * Hook to get the `ProjectProvisionContext` + * Hook to get the `IdeaModuleContext` */ -export function useProjectProvisionContext() { +export function useIdeaModuleContext() { return useContext(IdeaModuleContext) } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts index 44f6a5e3e..bdcbe68c0 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts @@ -2,15 +2,27 @@ import { IBaseComponentProps } from '../types' export interface IIdeaModuleProps extends IBaseComponentProps { configurationList: string + sortBy?: string + showSearchBox?: boolean + showRenderModeSelector?: boolean + showSortBy?: boolean + defaultRenderMode?: ProjectListRenderMode } export interface IIdeaModuleState { loading?: boolean isRefetching?: boolean + error?: any configuration?: ConfigurationItem[] ideas?: Record + searchTerm: string + renderMode?: ProjectListRenderMode + isUserInIdeaManagerGroup?: boolean + sort?: { fieldName: string; isSortedDescending: boolean } } +export type ProjectListRenderMode = 'tiles' | 'list' | 'compactList' + export interface IIdeaModuleHashState { ideaId?: string } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts index efa17787b..e3c01ed35 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts @@ -7,7 +7,7 @@ import { useIdeaModuleState } from './useIdeaModuleState' * clear */ export function useIdeaModule(props: IIdeaModuleProps) { - const { state, setState } = useIdeaModuleState() + const { state, setState } = useIdeaModuleState(props) const fluentProviderId = useId('fp-idea-module') return { diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts index a276d27b7..677d59387 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts @@ -1,17 +1,23 @@ /* eslint-disable prefer-spread */ import { useState } from 'react' -import { IIdeaModuleState } from './types' +import { IIdeaModuleProps, IIdeaModuleState } from './types' /** * Component state hook for `IIdeaModule`. * * @param props Props */ -export function useIdeaModuleState() { +export function useIdeaModuleState(props: IIdeaModuleProps) { + const defaultSort = { fieldName: props.sortBy, isSortedDescending: true } + const [state, $setState] = useState({ loading: true, configuration: [], - ideas: {} + ideas: {}, + error: null, + searchTerm: '', + renderMode: props.defaultRenderMode ?? 'tiles', + sort: defaultSort }) /** diff --git a/SharePointFramework/PortfolioWebParts/src/models/IdeaListModel.ts b/SharePointFramework/PortfolioWebParts/src/models/IdeaListModel.ts new file mode 100644 index 000000000..ae8d64639 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/models/IdeaListModel.ts @@ -0,0 +1,13 @@ +export class IdeaListModel { + public ideaId?: string + + /** + * Creates a new instance of IdeaListModel + * + * @param title - Title + * @param item - Item + */ + constructor(public title?: string, item?: any) { + this.ideaId = item.ListItemId + } +} diff --git a/SharePointFramework/PortfolioWebParts/src/models/index.ts b/SharePointFramework/PortfolioWebParts/src/models/index.ts index fb625012f..1ab2c6cff 100644 --- a/SharePointFramework/PortfolioWebParts/src/models/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/models/index.ts @@ -7,3 +7,4 @@ export * from './ChartDataItem' export * from './ChartConfiguration' export * from './DataField' export * from './ProgramItem' +export * from './IdeaListModel' diff --git a/Templates/Portfolio/Objects/ClientSidePages/@.xml b/Templates/Portfolio/Objects/ClientSidePages/@.xml index 93e11399b..a2f573b34 100644 --- a/Templates/Portfolio/Objects/ClientSidePages/@.xml +++ b/Templates/Portfolio/Objects/ClientSidePages/@.xml @@ -2,6 +2,7 @@ + diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 1cffd633e..f9bdc64ba 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -951,12 +951,12 @@ packages: /@fluentui/react-alert/9.0.0-beta.123_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/react-avatar': 9.6.28_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-button': 9.3.82_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-icons': 2.0.242_react@17.0.1 - '@fluentui/react-jsx-runtime': 9.0.38_4865287ef69aa4f3fc3f709d3560286e - '@fluentui/react-tabster': 9.21.4_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-theme': 9.1.19 - '@fluentui/react-utilities': 9.18.9_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-button': 9.3.94_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-icons': 2.0.261_react@17.0.1 + '@fluentui/react-jsx-runtime': 9.0.45_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-tabster': 9.22.9_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-theme': 9.1.21 + '@fluentui/react-utilities': 9.18.16_4865287ef69aa4f3fc3f709d3560286e '@griffel/react': 1.5.23_react@17.0.1 '@swc/helpers': 0.5.11 '@types/react': 17.0.45 @@ -991,6 +991,26 @@ packages: react-dom: '>=16.14.0 <19.0.0' resolution: integrity: sha512-hudoHBG+Uk511PgEp/plZsM1OdhOvyuOmyPZOriqivKwHbFklMZ+Fznjrcbat5XVngD5ehznarj6uqI4AsnCog== + /@fluentui/react-aria/9.13.8_9c907a7ccd27a2e2d0ee9de5366482d1: + dependencies: + '@fluentui/keyboard-keys': 9.0.7 + '@fluentui/react-jsx-runtime': 9.0.45_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-shared-contexts': 9.20.2_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-tabster': 9.22.9_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-utilities': 9.18.16_4865287ef69aa4f3fc3f709d3560286e + '@swc/helpers': 0.5.11 + '@types/react': 17.0.45 + '@types/react-dom': 17.0.17 + react: 17.0.1 + react-dom: 17.0.1_react@17.0.1 + dev: false + peerDependencies: + '@types/react': '>=16.14.0 <19.0.0' + '@types/react-dom': '>=16.9.0 <19.0.0' + react: '>=16.14.0 <19.0.0' + react-dom: '>=16.14.0 <19.0.0' + resolution: + integrity: sha512-OGd4LLW1LrdbCp+GkYLTRVX2IHZ/wnlOwHBX9VmQkY/FgeevDDGRH7HoXZRebmzDWHXzmjbyFxTO2QiJoF9zTQ== /@fluentui/react-avatar/9.6.28_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/react-badge': 9.2.37_9c907a7ccd27a2e2d0ee9de5366482d1 @@ -1111,6 +1131,30 @@ packages: react-dom: '>=16.14.0 <19.0.0' resolution: integrity: sha512-tRJnLpPPm+PRoIGSqfI+cXVE3oLPQOPhl2zrXfT0rkNMWFTZv9xBJ4KCnGVx1CqksV3Xa9eqc94KMJOV5KKT6A== + /@fluentui/react-button/9.3.94_9c907a7ccd27a2e2d0ee9de5366482d1: + dependencies: + '@fluentui/keyboard-keys': 9.0.7 + '@fluentui/react-aria': 9.13.8_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-icons': 2.0.261_react@17.0.1 + '@fluentui/react-jsx-runtime': 9.0.45_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-shared-contexts': 9.20.2_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-tabster': 9.22.9_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-theme': 9.1.21 + '@fluentui/react-utilities': 9.18.16_4865287ef69aa4f3fc3f709d3560286e + '@griffel/react': 1.5.23_react@17.0.1 + '@swc/helpers': 0.5.11 + '@types/react': 17.0.45 + '@types/react-dom': 17.0.17 + react: 17.0.1 + react-dom: 17.0.1_react@17.0.1 + dev: false + peerDependencies: + '@types/react': '>=16.14.0 <19.0.0' + '@types/react-dom': '>=16.9.0 <19.0.0' + react: '>=16.14.0 <19.0.0' + react-dom: '>=16.14.0 <19.0.0' + resolution: + integrity: sha512-8FepyrHjD0c9JQmvFP+N0zMyFW6jmLlwwtg8ThPCQJlInLZA4NrwEDmCl1cshyHBuTaSOBxuPGvTQHQetprMug== /@fluentui/react-calendar-compat/0.1.9_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/keyboard-keys': 9.0.7 @@ -1317,17 +1361,17 @@ packages: dependencies: '@fluentui/react-accordion': 9.3.56_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-alert': 9.0.0-beta.123_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-aria': 9.11.4_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-aria': 9.13.8_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-avatar': 9.6.28_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-badge': 9.2.37_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-breadcrumb': 9.0.28_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-button': 9.3.82_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-button': 9.3.94_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-card': 9.0.81_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-checkbox': 9.2.27_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-combobox': 9.11.6_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-dialog': 9.10.7_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-divider': 9.2.69_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-drawer': 9.4.0_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-dialog': 9.11.18_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-divider': 9.2.76_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-drawer': 9.5.18_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-field': 9.1.66_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-image': 9.1.67_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-infobutton': 9.0.0-beta.102_9c907a7ccd27a2e2d0ee9de5366482d1 @@ -1340,15 +1384,15 @@ packages: '@fluentui/react-overflow': 9.1.20_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-persona': 9.2.87_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-popover': 9.9.10_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-portal': 9.4.26_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-positioning': 9.15.2_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-portal': 9.4.37_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-positioning': 9.15.10_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-progress': 9.1.77_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-provider': 9.16.1_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-radio': 9.2.22_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-rating': 9.0.10_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-search': 9.0.6_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-select': 9.1.77_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-shared-contexts': 9.19.0_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-shared-contexts': 9.20.2_4865287ef69aa4f3fc3f709d3560286e '@fluentui/react-skeleton': 9.1.5_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-slider': 9.1.84_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-spinbutton': 9.2.77_9c907a7ccd27a2e2d0ee9de5366482d1 @@ -1357,18 +1401,18 @@ packages: '@fluentui/react-switch': 9.1.84_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-table': 9.15.6_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-tabs': 9.4.22_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-tabster': 9.21.4_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-tabster': 9.22.9_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-tag-picker': 9.0.4_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-tags': 9.3.7_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-teaching-popover': 9.1.6_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-text': 9.4.19_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-textarea': 9.3.77_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-theme': 9.1.19 + '@fluentui/react-theme': 9.1.21 '@fluentui/react-toast': 9.3.44_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-toolbar': 9.1.85_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-tooltip': 9.4.29_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-tooltip': 9.4.41_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-tree': 9.5.1_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-utilities': 9.18.9_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-utilities': 9.18.16_4865287ef69aa4f3fc3f709d3560286e '@fluentui/react-virtualizer': 9.0.0-alpha.78_9c907a7ccd27a2e2d0ee9de5366482d1 '@griffel/react': 1.5.23_react@17.0.1 '@swc/helpers': 0.5.11 @@ -1418,6 +1462,23 @@ packages: scheduler: '>=0.19.0 <=0.23.0' resolution: integrity: sha512-gm93NSABCVKw6pw9PGd4M5aJUIVrI6u/HlyqBKLB7HjBZwa1RwdNH9PghUJ2TSjfhS8V00Rkt28nCqEwz34Dlw== + /@fluentui/react-context-selector/9.1.68_9c907a7ccd27a2e2d0ee9de5366482d1: + dependencies: + '@fluentui/react-utilities': 9.18.16_4865287ef69aa4f3fc3f709d3560286e + '@swc/helpers': 0.5.11 + '@types/react': 17.0.45 + '@types/react-dom': 17.0.17 + react: 17.0.1 + react-dom: 17.0.1_react@17.0.1 + dev: false + peerDependencies: + '@types/react': '>=16.14.0 <19.0.0' + '@types/react-dom': '>=16.9.0 <19.0.0' + react: '>=16.14.0 <19.0.0' + react-dom: '>=16.14.0 <19.0.0' + scheduler: '>=0.19.0 <=0.23.0' + resolution: + integrity: sha512-PJwmvRevT/oyk/Gs0bnCb9UsQX/pXhM7lAHWq4ssnQLSmrdxJY/cwqAadQydJsA/itUy+FhgiEbPYGiEeB1GGA== /@fluentui/react-datepicker-compat/0.4.39_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/keyboard-keys': 9.0.7 @@ -1474,6 +1535,33 @@ packages: react-dom: '>=16.14.0 <19.0.0' resolution: integrity: sha512-OqgWqe6BjjVeQmEBckEP8DpRdTN1U7lpa4Hmk/Yl9A+qmszefswrgaAUXAxFxAajI5Q23EmZZzMEgZEZ9larzQ== + /@fluentui/react-dialog/9.11.18_9c907a7ccd27a2e2d0ee9de5366482d1: + dependencies: + '@fluentui/keyboard-keys': 9.0.7 + '@fluentui/react-aria': 9.13.8_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-context-selector': 9.1.68_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-icons': 2.0.261_react@17.0.1 + '@fluentui/react-jsx-runtime': 9.0.45_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-motion': 9.6.0_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-portal': 9.4.37_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-shared-contexts': 9.20.2_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-tabster': 9.22.9_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-theme': 9.1.21 + '@fluentui/react-utilities': 9.18.16_4865287ef69aa4f3fc3f709d3560286e + '@griffel/react': 1.5.23_react@17.0.1 + '@swc/helpers': 0.5.11 + '@types/react': 17.0.45 + '@types/react-dom': 17.0.17 + react: 17.0.1 + react-dom: 17.0.1_react@17.0.1 + dev: false + peerDependencies: + '@types/react': '>=16.14.0 <19.0.0' + '@types/react-dom': '>=16.9.0 <19.0.0' + react: '>=16.14.0 <19.0.0' + react-dom: '>=16.14.0 <19.0.0' + resolution: + integrity: sha512-wTH3xfcnSZmUVQV5L0oM1MJqgT4uA/EN8Enf9lRZx+aeyjX7IYywxm9YcIkgn4nZtj66gHFFlcAaR8aJgGUTfw== /@fluentui/react-divider/9.2.69_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/react-jsx-runtime': 9.0.38_4865287ef69aa4f3fc3f709d3560286e @@ -1494,6 +1582,26 @@ packages: react-dom: '>=16.14.0 <19.0.0' resolution: integrity: sha512-D1ajELLS8F7N7rWLRlNCtZa/JhfsTqKC5jYpWYbUZPkYS9VxetPaO3hjcmAOrK5ycqYkUv/zsU7wbnTSIkSEOA== + /@fluentui/react-divider/9.2.76_9c907a7ccd27a2e2d0ee9de5366482d1: + dependencies: + '@fluentui/react-jsx-runtime': 9.0.45_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-shared-contexts': 9.20.2_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-theme': 9.1.21 + '@fluentui/react-utilities': 9.18.16_4865287ef69aa4f3fc3f709d3560286e + '@griffel/react': 1.5.23_react@17.0.1 + '@swc/helpers': 0.5.11 + '@types/react': 17.0.45 + '@types/react-dom': 17.0.17 + react: 17.0.1 + react-dom: 17.0.1_react@17.0.1 + dev: false + peerDependencies: + '@types/react': '>=16.14.0 <19.0.0' + '@types/react-dom': '>=16.9.0 <19.0.0' + react: '>=16.14.0 <19.0.0' + react-dom: '>=16.14.0 <19.0.0' + resolution: + integrity: sha512-r8+637gYDtffENjEUtGu4l9LTceOHF4oV7X8Wf9Cw1ZVHExDVXCnQ2QpNxDrR+tFW0oOHWNfvn6gJ0A/43acmw== /@fluentui/react-drawer/9.4.0_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/react-dialog': 9.10.7_9c907a7ccd27a2e2d0ee9de5366482d1 @@ -1517,6 +1625,29 @@ packages: react-dom: '>=16.14.0 <19.0.0' resolution: integrity: sha512-uY17h7uxOTO+5/Naq2pxEyM8vnDEyPHrnXmSaTwEF3OEjFI1ayHJwOu+OGFjkkeB+O1UWLOQisNO8R4BYPGYZg== + /@fluentui/react-drawer/9.5.18_9c907a7ccd27a2e2d0ee9de5366482d1: + dependencies: + '@fluentui/react-dialog': 9.11.18_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-jsx-runtime': 9.0.45_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-motion': 9.6.0_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-shared-contexts': 9.20.2_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-tabster': 9.22.9_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-theme': 9.1.21 + '@fluentui/react-utilities': 9.18.16_4865287ef69aa4f3fc3f709d3560286e + '@griffel/react': 1.5.23_react@17.0.1 + '@swc/helpers': 0.5.11 + '@types/react': 17.0.45 + '@types/react-dom': 17.0.17 + react: 17.0.1 + react-dom: 17.0.1_react@17.0.1 + dev: false + peerDependencies: + '@types/react': '>=16.14.0 <19.0.0' + '@types/react-dom': '>=16.9.0 <19.0.0' + react: '>=16.14.0 <19.0.0' + react-dom: '>=16.14.0 <19.0.0' + resolution: + integrity: sha512-umNPJcCBGEBXt6MKPaBKHIejjjR/zPbgme3gF3gxHVmelKdbPuLvjnaL0F83e4pSmV/f655/SIYdQ6Lve3ShrQ== /@fluentui/react-field/9.1.66_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/react-context-selector': 9.1.60_9c907a7ccd27a2e2d0ee9de5366482d1 @@ -1728,6 +1859,16 @@ packages: react: '>=16.8.0 <19.0.0' resolution: integrity: sha512-OtVYVcXWuMrqTBRie4eNwPH56mJJq/pmdPhQCQcLFOnupUyXlnlCRnbJYXd+LDEWga6CCwfqZxI6NiPxpbg08Q== + /@fluentui/react-icons/2.0.261_react@17.0.1: + dependencies: + '@griffel/react': 1.5.23_react@17.0.1 + react: 17.0.1 + tslib: 2.6.3 + dev: false + peerDependencies: + react: '>=16.8.0 <19.0.0' + resolution: + integrity: sha512-GNgTbi5b5TmN4Q621+C/Bgiu9BLupkZP7JjzB3TdrFEH9u/URu2RWxKs3Qcap79b0o5OqsDm0oNq5tBA3y8URA== /@fluentui/react-image/9.1.67_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/react-jsx-runtime': 9.0.38_4865287ef69aa4f3fc3f709d3560286e @@ -1828,6 +1969,19 @@ packages: react: '>=16.14.0 <19.0.0' resolution: integrity: sha512-4HGwWshavd9H+SyEkxPriPZLFz7QRa4YMzYi3ibBI0q2iiZZw8wQ3otuM/Pf8xxamJgYpnDl3mAesJU+3hFANw== + /@fluentui/react-jsx-runtime/9.0.45_4865287ef69aa4f3fc3f709d3560286e: + dependencies: + '@fluentui/react-utilities': 9.18.16_4865287ef69aa4f3fc3f709d3560286e + '@swc/helpers': 0.5.11 + '@types/react': 17.0.45 + react: 17.0.1 + react-is: 17.0.2 + dev: false + peerDependencies: + '@types/react': '>=16.14.0 <19.0.0' + react: '>=16.14.0 <19.0.0' + resolution: + integrity: sha512-MJg+Hdkdy8pXn+7nsLYQGSET4ypf+azQIHoFanhm2ZWOSjAcCKcOTsE33Z6KFxZ7dSUyH9njn7qK2Gt4YeW2MA== /@fluentui/react-label/9.1.70_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/react-jsx-runtime': 9.0.38_4865287ef69aa4f3fc3f709d3560286e @@ -1927,9 +2081,9 @@ packages: '@fluentui/react': 8.118.6_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-components': 9.53.0_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-hooks': 8.8.5_4865287ef69aa4f3fc3f709d3560286e - '@fluentui/react-icons': 2.0.242_react@17.0.1 - '@fluentui/react-theme': 9.1.19 - '@fluentui/react-utilities': 9.18.9_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-icons': 2.0.261_react@17.0.1 + '@fluentui/react-theme': 9.1.21 + '@fluentui/react-utilities': 9.18.16_4865287ef69aa4f3fc3f709d3560286e '@griffel/react': 1.5.23_react@17.0.1 '@swc/helpers': 0.5.11 '@types/react': 17.0.45 @@ -1964,6 +2118,24 @@ packages: react-dom: '>=16.14.0 <19.0.0' resolution: integrity: sha512-I2Lj1RFrq9yVKxYlKrbE7SeluZo08eJrs8S5pL6OVxXJND0avW7pFF2ZOs3EsABW1clcp1S55IgJ9D6nVlnbvw== + /@fluentui/react-motion/9.6.0_9c907a7ccd27a2e2d0ee9de5366482d1: + dependencies: + '@fluentui/react-shared-contexts': 9.20.2_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-utilities': 9.18.16_4865287ef69aa4f3fc3f709d3560286e + '@swc/helpers': 0.5.11 + '@types/react': 17.0.45 + '@types/react-dom': 17.0.17 + react: 17.0.1 + react-dom: 17.0.1_react@17.0.1 + react-is: 17.0.2 + dev: false + peerDependencies: + '@types/react': '>=16.8.0 <19.0.0' + '@types/react-dom': '>=16.8.0 <19.0.0' + react: '>=16.14.0 <19.0.0' + react-dom: '>=16.8.0 <19.0.0' + resolution: + integrity: sha512-Jxiz1EXernadWbQ2oMBEzjmKqDeHZXuyeC8GO8ReXo+utupI/pWC1xtn3tQRcIs+2RnI812ertWULcl7n8adqA== /@fluentui/react-motions-preview/0.3.2_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/react-shared-contexts': 9.19.0_4865287ef69aa4f3fc3f709d3560286e @@ -1982,6 +2154,34 @@ packages: react-dom: '>=16.8.0 <19.0.0' resolution: integrity: sha512-xMzoDzjVWzihXB3+r0wwOWI+F8eeYaXM0Cl1y1JVw0lOu2dbitU3ypgpbltR3jAV5wf83iAXBjz1MCixnwgW6g== + /@fluentui/react-nav-preview/0.9.1_9c907a7ccd27a2e2d0ee9de5366482d1: + dependencies: + '@fluentui/react-aria': 9.13.8_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-button': 9.3.94_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-context-selector': 9.1.68_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-divider': 9.2.76_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-drawer': 9.5.18_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-icons': 2.0.261_react@17.0.1 + '@fluentui/react-jsx-runtime': 9.0.45_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-shared-contexts': 9.20.2_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-tabster': 9.22.9_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-theme': 9.1.21 + '@fluentui/react-tooltip': 9.4.41_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-utilities': 9.18.16_4865287ef69aa4f3fc3f709d3560286e + '@griffel/react': 1.5.23_react@17.0.1 + '@swc/helpers': 0.5.11 + '@types/react': 17.0.45 + '@types/react-dom': 17.0.17 + react: 17.0.1 + react-dom: 17.0.1_react@17.0.1 + dev: false + peerDependencies: + '@types/react': '>=16.14.0 <19.0.0' + '@types/react-dom': '>=16.9.0 <19.0.0' + react: '>=16.14.0 <19.0.0' + react-dom: '>=16.14.0 <19.0.0' + resolution: + integrity: sha512-LxgCsnbrN4STA+/03j+E23/XlKoXRuQxby90MTVfQImoMQk9Qp1C3PNPXYWne8GpH1+4k2s/Deghn22lEzOvWQ== /@fluentui/react-northstar-fela-renderer/0.66.5_react-dom@17.0.1+react@17.0.1: dependencies: '@babel/runtime': 7.24.6 @@ -2160,6 +2360,47 @@ packages: react-dom: '>=16.14.0 <19.0.0' resolution: integrity: sha512-C5huZsjIF62KIkOMYLdnDR6A7T+elpGsHVK9aCt5T6RHflceosYz1C8Vy031nJRHGWY5064T1hazb+Y59xMXqA== + /@fluentui/react-portal/9.4.37_9c907a7ccd27a2e2d0ee9de5366482d1: + dependencies: + '@fluentui/react-shared-contexts': 9.20.2_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-tabster': 9.22.9_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-utilities': 9.18.16_4865287ef69aa4f3fc3f709d3560286e + '@griffel/react': 1.5.23_react@17.0.1 + '@swc/helpers': 0.5.11 + '@types/react': 17.0.45 + '@types/react-dom': 17.0.17 + react: 17.0.1 + react-dom: 17.0.1_react@17.0.1 + use-disposable: 1.0.2_9c907a7ccd27a2e2d0ee9de5366482d1 + dev: false + peerDependencies: + '@types/react': '>=16.14.0 <19.0.0' + '@types/react-dom': '>=16.9.0 <19.0.0' + react: '>=16.14.0 <19.0.0' + react-dom: '>=16.14.0 <19.0.0' + resolution: + integrity: sha512-wWjuCP/PAHvHFCyjONYyHE5CRh94WMLtVIAAGlN9GQRo3U2nbvG2V422Vlro1e4zYb2T8Kf2wJ9VFkffD1j7bQ== + /@fluentui/react-positioning/9.15.10_9c907a7ccd27a2e2d0ee9de5366482d1: + dependencies: + '@floating-ui/devtools': 0.2.1_@floating-ui+dom@1.6.5 + '@floating-ui/dom': 1.6.5 + '@fluentui/react-shared-contexts': 9.20.2_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-theme': 9.1.21 + '@fluentui/react-utilities': 9.18.16_4865287ef69aa4f3fc3f709d3560286e + '@griffel/react': 1.5.23_react@17.0.1 + '@swc/helpers': 0.5.11 + '@types/react': 17.0.45 + '@types/react-dom': 17.0.17 + react: 17.0.1 + react-dom: 17.0.1_react@17.0.1 + dev: false + peerDependencies: + '@types/react': '>=16.14.0 <19.0.0' + '@types/react-dom': '>=16.9.0 <19.0.0' + react: '>=16.14.0 <19.0.0' + react-dom: '>=16.14.0 <19.0.0' + resolution: + integrity: sha512-5vWtRO4AEWz9lw5KK191arOWjXzGYffSU6lMtvWsprqwFhYeVcu/OCGNElZote7RFz1t9Pjsx8sVbbp7TlVbJA== /@fluentui/react-positioning/9.15.2_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@floating-ui/devtools': 0.2.1_@floating-ui+dom@1.6.5 @@ -2332,6 +2573,18 @@ packages: react: '>=16.14.0 <19.0.0' resolution: integrity: sha512-KWHRVuKSvQpFdGGxj802AwoHlq7VyFKaj89cgX2pBu2ZqZrdpxkbkfFQIvxLoaZ/Bzm7fWXVQrDYpj+8JHAfCA== + /@fluentui/react-shared-contexts/9.20.2_4865287ef69aa4f3fc3f709d3560286e: + dependencies: + '@fluentui/react-theme': 9.1.21 + '@swc/helpers': 0.5.11 + '@types/react': 17.0.45 + react: 17.0.1 + dev: false + peerDependencies: + '@types/react': '>=16.14.0 <19.0.0' + react: '>=16.14.0 <19.0.0' + resolution: + integrity: sha512-vNsPDpjhZjkBBTjWOB7ddG/US89lsqAYvOi1ITb7YT5CLMVLzexewcAdSFmF8yrnc1bEOEW1BEH8aJoT0NAHnA== /@fluentui/react-skeleton/9.1.5_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/react-field': 9.1.66_9c907a7ccd27a2e2d0ee9de5366482d1 @@ -2549,6 +2802,27 @@ packages: react-dom: '>=16.14.0 <19.0.0' resolution: integrity: sha512-POHXSr82v3/TWeYLYgX87B98/vJacEbcKBmUxRAjCDwhaSVsD+aqa7a3ayDcl2sXmj+YRmHTYsQC1KJ/9NpUfA== + /@fluentui/react-tabster/9.22.9_9c907a7ccd27a2e2d0ee9de5366482d1: + dependencies: + '@fluentui/react-shared-contexts': 9.20.2_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-theme': 9.1.21 + '@fluentui/react-utilities': 9.18.16_4865287ef69aa4f3fc3f709d3560286e + '@griffel/react': 1.5.23_react@17.0.1 + '@swc/helpers': 0.5.11 + '@types/react': 17.0.45 + '@types/react-dom': 17.0.17 + keyborg: 2.6.0 + react: 17.0.1 + react-dom: 17.0.1_react@17.0.1 + tabster: 8.2.0 + dev: false + peerDependencies: + '@types/react': '>=16.14.0 <19.0.0' + '@types/react-dom': '>=16.9.0 <19.0.0' + react: '>=16.14.0 <19.0.0' + react-dom: '>=16.14.0 <19.0.0' + resolution: + integrity: sha512-Mnndzbo3SKbdSkn6LmtZpQvM0sFNW3ryo5ZXcCBjkQPPk2P1kAxGDxSYbFuWDCI2oWa/daJmDJr0IWxr0sQZuA== /@fluentui/react-tag-picker/9.0.4_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/keyboard-keys': 9.0.7 @@ -2701,6 +2975,13 @@ packages: dev: false resolution: integrity: sha512-mrVhKbr4o9UKERPxgghIRDU59S7gRizrgz3/wwyMt7elkr8Sw+OpwKIeEw9x6P0RTcFDC00nggaMJhBGs7Xo4A== + /@fluentui/react-theme/9.1.21: + dependencies: + '@fluentui/tokens': 1.0.0-alpha.18 + '@swc/helpers': 0.5.11 + dev: false + resolution: + integrity: sha512-xiENKBT1ttcGiOKW0Dv2YEKYg92r1hYd6O/VGCvlX/j5ecclZ7RJO/O94LWQ8YJ22EzEYHeSp4y//uJQV9iU1g== /@fluentui/react-toast/9.3.44_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/keyboard-keys': 9.0.7 @@ -2776,6 +3057,30 @@ packages: react-dom: '>=16.14.0 <19.0.0' resolution: integrity: sha512-EsDWnPbD6P99g7+5VrEu+TYtfoIwTPVXRSHNrRUzTwubLpPHXEi/VKeCIx7RJVGQkrO8IDwRndpGMJmFkAeQ1Q== + /@fluentui/react-tooltip/9.4.41_9c907a7ccd27a2e2d0ee9de5366482d1: + dependencies: + '@fluentui/keyboard-keys': 9.0.7 + '@fluentui/react-jsx-runtime': 9.0.45_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-portal': 9.4.37_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-positioning': 9.15.10_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-shared-contexts': 9.20.2_4865287ef69aa4f3fc3f709d3560286e + '@fluentui/react-tabster': 9.22.9_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-theme': 9.1.21 + '@fluentui/react-utilities': 9.18.16_4865287ef69aa4f3fc3f709d3560286e + '@griffel/react': 1.5.23_react@17.0.1 + '@swc/helpers': 0.5.11 + '@types/react': 17.0.45 + '@types/react-dom': 17.0.17 + react: 17.0.1 + react-dom: 17.0.1_react@17.0.1 + dev: false + peerDependencies: + '@types/react': '>=16.14.0 <19.0.0' + '@types/react-dom': '>=16.9.0 <19.0.0' + react: '>=16.14.0 <19.0.0' + react-dom: '>=16.14.0 <19.0.0' + resolution: + integrity: sha512-KHfCp8uNFSqMVw/7eZ1rqLpTm9eeypmHODh8v0ght7o1Aom/KvMQwiYa21lWnga383bMMSit5+WJtIyaHjpgdg== /@fluentui/react-tree/9.5.1_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/keyboard-keys': 9.0.7 @@ -2805,6 +3110,19 @@ packages: react-dom: '>=16.14.0 <19.0.0' resolution: integrity: sha512-/PyTSWydHAimEDZBgkhXaVqPsPVCuL1FNccWLZoTKAv8MvdEQGgfcUYJ/dEhKtnKKEh89GtPpnkBLK+RR91TQw== + /@fluentui/react-utilities/9.18.16_4865287ef69aa4f3fc3f709d3560286e: + dependencies: + '@fluentui/keyboard-keys': 9.0.7 + '@fluentui/react-shared-contexts': 9.20.2_4865287ef69aa4f3fc3f709d3560286e + '@swc/helpers': 0.5.11 + '@types/react': 17.0.45 + react: 17.0.1 + dev: false + peerDependencies: + '@types/react': '>=16.14.0 <19.0.0' + react: '>=16.14.0 <19.0.0' + resolution: + integrity: sha512-IXPD/TMsCcA5PVM4AvLT3Wgck/te7OaMj4OqDcVGciob+73MliScmXYz8ZwSIP1fxlo6CBPZo8mPZK8C6nkKQQ== /@fluentui/react-utilities/9.18.9_4865287ef69aa4f3fc3f709d3560286e: dependencies: '@fluentui/keyboard-keys': 9.0.7 @@ -3185,6 +3503,12 @@ packages: dev: false resolution: integrity: sha512-Gr9G8LIlUhZYX5j6CfDQrofQqsWAz/q54KabWn1tWV/1083WwyoTZXiG1k6b37NnK7Feye7D7Nz+4MNqoKpXGw== + /@fluentui/tokens/1.0.0-alpha.18: + dependencies: + '@swc/helpers': 0.5.11 + dev: false + resolution: + integrity: sha512-d7CpB7RJhPlv8r6OjKRsL4mu8dvSiwrGdQuZyRhDjhCa/5u0xSdCxLmwGu4HOTlr9sg9Gf7LbQe2shAlq2J21w== /@fluentui/utilities/8.15.8_3df41e700a554c2356b362be396a0af4: dependencies: '@fluentui/dom-utilities': 2.3.5 @@ -22990,6 +23314,13 @@ packages: dev: false resolution: integrity: sha512-0GJcYokZX2QIqtJBTFwGksyZzMcZr3DAGncP4egcOIspxYJFDdWqMh9zZWnxIZhAsBwVbovFK2ko7VPMp1edYw== + /tabster/8.2.0: + dependencies: + keyborg: 2.6.0 + tslib: 2.6.3 + dev: false + resolution: + integrity: sha512-Gvplk/Yl/12aVFA6FPOqGcq31Qv8hbPfYO0N+6IxrRgRT6eSLsipT6gkZBYjyOwGsp6BD5XlZAuJgupfG/GHoA== /tapable/1.1.3: dev: false engines: @@ -25135,6 +25466,7 @@ packages: '@fluentui/react-hooks': 8.6.27_4865287ef69aa4f3fc3f709d3560286e '@fluentui/react-icons': 2.0.242_react@17.0.1 '@fluentui/react-motion-preview': 0.5.21_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-nav-preview': 0.9.1_9c907a7ccd27a2e2d0ee9de5366482d1 '@microsoft/decorators': 1.17.4 '@microsoft/eslint-config-spfx': 1.17.4_eslint@8.25.0+typescript@4.5.5 '@microsoft/eslint-plugin-spfx': 1.17.4_eslint@8.25.0+typescript@4.5.5 From 8182325f748e76ded19ea75f3692a420039755a6 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Thu, 17 Oct 2024 13:37:58 +0200 Subject: [PATCH 03/31] Fix some issues [packages-only] --- .../IdeaList/Commands/useCommands.tsx | 14 ++++---- .../IdeaModule/IdeaList/List/List.tsx | 9 +++-- .../IdeaModule/IdeaList/List/context.ts | 5 --- .../IdeaModule/IdeaList/List/useColumns.tsx | 35 +++++-------------- .../IdeaList/useIdeaListRenderer.tsx | 2 +- .../src/components/IdeaModule/types.ts | 7 ++-- 6 files changed, 24 insertions(+), 48 deletions(-) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/useCommands.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/useCommands.tsx index 58235ff2e..7573a0580 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/useCommands.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/useCommands.tsx @@ -2,17 +2,17 @@ import { format } from '@fluentui/react' import strings from 'PortfolioWebPartsStrings' import _ from 'lodash' import { ListMenuItem, getFluentIcon } from 'pp365-shared-library' -import { useContext, useMemo } from 'react' -import { ProjectListContext } from '../context' -import { ProjectListRenderMode } from '../types' +import { useMemo } from 'react' import { renderModes } from './renderModes' +import { useIdeaModuleContext } from 'components/IdeaModule/context' +import { IdeaListRenderMode } from 'components/IdeaModule/types' /** * Component logic hook for `Commands`. This hook is responsible for * rendering the toolbar and handling its actions. */ export function useCommands() { - const context = useContext(ProjectListContext) + const context = useIdeaModuleContext() const selectedRenderMode = useMemo( () => renderModes.find((renderMode) => renderMode.value === context.state.renderMode), [context.state.renderMode] @@ -38,7 +38,7 @@ export function useCommands() { }) .setOnClick(() => { context.setState({ - renderMode: value as ProjectListRenderMode + renderMode: value as IdeaListRenderMode }) }) ), @@ -62,9 +62,9 @@ export function useCommands() { ] const searchBoxPlaceholder = - !context.state.isDataLoaded || _.isEmpty(context.state.projects) + !context.state.loading || _.isEmpty(context.state.ideas) ? '' - : format(context.state.selectedVertical.searchBoxPlaceholder, context.projects.length) + : format(strings.SearchBoxPlaceholderText, context.state.ideas.length) return { toolbarItems, searchBoxPlaceholder } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/List.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/List.tsx index 0f49e5a6b..e7217735c 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/List.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/List.tsx @@ -8,26 +8,25 @@ import { } from '@fluentui/react-components' import { ProjectListModel } from 'pp365-shared-library/lib/models' import * as React from 'react' -import { useContext } from 'react' import styles from './List.module.scss' -import { ListContext } from './context' import { useList } from './useList' +import { useIdeaModuleContext } from 'components/IdeaModule/context' export const List = () => { - const context = useContext(ListContext) + const context = useIdeaModuleContext() const { columnSizingOptions, columns, defaultSortState } = useList() return (
diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/context.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/context.ts index 90a04e619..f6aa36402 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/context.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/context.ts @@ -10,11 +10,6 @@ export interface IListContext extends IIdeaModuleProps { * Ideas to be rendered in the list. */ ideas?: Record - - /** - * Size that determines the list appearance. - */ - size?: 'extra-small' | 'small' | 'medium' } export const ListContext = createContext(null) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/useColumns.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/useColumns.tsx index f922380cf..57db231ef 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/useColumns.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/useColumns.tsx @@ -1,13 +1,13 @@ import { Avatar, Link, TableCellLayout } from '@fluentui/react-components' import * as strings from 'PortfolioWebPartsStrings' import { ProjectLogo } from 'pp365-shared-library' -import React, { useContext } from 'react' -import { ProjectMenu } from '../ProjectMenu' -import { ListContext } from './context' +import React from 'react' import { IListColumn } from './types' +import { useIdeaModuleContext } from 'components/IdeaModule/context' export const useColumns = (): IListColumn[] => { - const context = useContext(ListContext) + const context = useIdeaModuleContext() + return [ { columnId: 'logo', @@ -21,7 +21,7 @@ export const useColumns = (): IListColumn[] => { title={item.title} url={item.url} renderMode='list' - size={context.size !== 'medium' ? '32px' : '48px'} + size={context.props.listSize !== 'medium' ? '32px' : '48px'} /> ) } @@ -81,7 +81,8 @@ export const useColumns = (): IListColumn[] => { truncate title={`${strings.ProjectOwner}: ${item.owner?.name ?? strings.NotSet}`} > - {item.owner?.name} + {' '} + {item.owner?.name} ) } @@ -101,31 +102,11 @@ export const useColumns = (): IListColumn[] => { truncate title={`${strings.ProjectManager}: ${item.manager?.name ?? strings.NotSet}`} > - {' '} + {' '} {item.manager?.name} ) } - }, - { - columnId: 'actions', - defaultWidth: 40, - minWidth: 40, - compare: () => { - return -1 - }, - renderHeaderCell: () => { - return null - }, - renderCell: (item) => { - return ( - - ) - } } ] } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/useIdeaListRenderer.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/useIdeaListRenderer.tsx index dff1b1788..3baa7d205 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/useIdeaListRenderer.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/useIdeaListRenderer.tsx @@ -79,7 +79,7 @@ export function useIdeaListRenderer({ props, state }: IIdeaModuleContext) { value={{ ...props, ideas: state.ideas, - size + listSize: size }} > diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts index bdcbe68c0..9b66851bb 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts @@ -6,7 +6,8 @@ export interface IIdeaModuleProps extends IBaseComponentProps { showSearchBox?: boolean showRenderModeSelector?: boolean showSortBy?: boolean - defaultRenderMode?: ProjectListRenderMode + defaultRenderMode?: IdeaListRenderMode + listSize?: 'extra-small' | 'small' | 'medium' } export interface IIdeaModuleState { @@ -16,12 +17,12 @@ export interface IIdeaModuleState { configuration?: ConfigurationItem[] ideas?: Record searchTerm: string - renderMode?: ProjectListRenderMode + renderMode?: IdeaListRenderMode isUserInIdeaManagerGroup?: boolean sort?: { fieldName: string; isSortedDescending: boolean } } -export type ProjectListRenderMode = 'tiles' | 'list' | 'compactList' +export type IdeaListRenderMode = 'tiles' | 'list' | 'compactList' export interface IIdeaModuleHashState { ideaId?: string From c4066c2aa6429d6a02d4b44907051fa46aa92256 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Thu, 17 Oct 2024 16:46:53 +0200 Subject: [PATCH 04/31] Update readme [skip-ci] --- SharePointFramework/PortfolioExtensions/package.json | 2 +- .../PortfolioWebParts/src/webparts/ideaModule/manifest.json | 3 ++- SharePointFramework/ProgramWebParts/package.json | 2 +- SharePointFramework/ProjectExtensions/package.json | 2 +- SharePointFramework/ProjectWebParts/package.json | 2 +- SharePointFramework/README.md | 1 + Templates/Portfolio/Objects/ClientSidePages/@.xml | 2 +- Templates/Portfolio/Objects/ClientSidePages/Idemodul.xml | 2 +- 8 files changed, 9 insertions(+), 7 deletions(-) diff --git a/SharePointFramework/PortfolioExtensions/package.json b/SharePointFramework/PortfolioExtensions/package.json index 9e586983b..19371bb0a 100644 --- a/SharePointFramework/PortfolioExtensions/package.json +++ b/SharePointFramework/PortfolioExtensions/package.json @@ -12,7 +12,7 @@ "watch": "concurrently \"npm run serve\" \"livereload './dist/*.js' -e 'js' -w 250\"", "prewatch": "node node_modules/pzl-spfx-tasks --pre-watch --loglevel silent", "postwatch": "node node_modules/pzl-spfx-tasks --post-watch --loglevel silent", - "serve": "concurrently \"gulp serve-deprecated --locale=nb-no --nobrowser\"", + "serve": "concurrently \"gulp serve-deprecated --locale=nb-no --nobrowser NODE --max-old-space-size=8192\"", "build": "gulp bundle --ship && gulp package-solution --ship", "postversion": "tsc && npm publish", "lint": "eslint --ext .ts,.tsx ./src --color --fix --config ../.eslintrc.yaml && npm run prettier", diff --git a/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/manifest.json b/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/manifest.json index 30fe9372b..f63dcd2e5 100644 --- a/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/manifest.json +++ b/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/manifest.json @@ -24,7 +24,8 @@ }, "officeFabricIconFontName": "Lightbulb", "properties": { - "title": "Idémodul" + "title": "Idémodul", + "configurationList": "Idékonfigurasjon" } } ] diff --git a/SharePointFramework/ProgramWebParts/package.json b/SharePointFramework/ProgramWebParts/package.json index f0b9c6b09..acff14125 100644 --- a/SharePointFramework/ProgramWebParts/package.json +++ b/SharePointFramework/ProgramWebParts/package.json @@ -14,7 +14,7 @@ "watch": "concurrently \"npm run serve\" \"livereload './dist/*.js' -e 'js' -w 250\"", "prewatch": "node node_modules/pzl-spfx-tasks --pre-watch --loglevel silent", "postwatch": "node node_modules/pzl-spfx-tasks --post-watch --loglevel silent", - "serve": "concurrently \"gulp serve-deprecated --locale=nb-no --nobrowser\"", + "serve": "concurrently \"gulp serve-deprecated --locale=nb-no --nobrowser NODE --max-old-space-size=8192\"", "build": "gulp bundle --ship && gulp package-solution --ship", "postversion": "tsc && npm publish", "lint": "eslint --ext .ts,.tsx ./src --color --fix --config ../.eslintrc.yaml && npm run prettier", diff --git a/SharePointFramework/ProjectExtensions/package.json b/SharePointFramework/ProjectExtensions/package.json index 60a19c85a..e5a60d334 100644 --- a/SharePointFramework/ProjectExtensions/package.json +++ b/SharePointFramework/ProjectExtensions/package.json @@ -11,7 +11,7 @@ "watch": "concurrently \"npm run serve\" \"livereload './dist/*.js' -e 'js' -w 250\"", "prewatch": "node node_modules/pzl-spfx-tasks --pre-watch --loglevel silent", "postwatch": "node node_modules/pzl-spfx-tasks --post-watch --loglevel silent", - "serve": "concurrently \"gulp serve-deprecated --locale=nb-no --nobrowser\"", + "serve": "concurrently \"gulp serve-deprecated --locale=nb-no --nobrowser NODE --max-old-space-size=8192\"", "build": "gulp bundle --ship && gulp package-solution --ship", "postversion": "tsc && npm publish", "lint": "eslint --ext .ts,.tsx ./src --color --fix --config ../.eslintrc.yaml && npm run prettier", diff --git a/SharePointFramework/ProjectWebParts/package.json b/SharePointFramework/ProjectWebParts/package.json index e95f58b37..3b7365f75 100644 --- a/SharePointFramework/ProjectWebParts/package.json +++ b/SharePointFramework/ProjectWebParts/package.json @@ -11,7 +11,7 @@ "watch": "concurrently \"npm run serve\" \"livereload './dist/*.js' -e 'js' -w 250\"", "prewatch": "node node_modules/pzl-spfx-tasks --pre-watch --loglevel silent", "postwatch": "node node_modules/pzl-spfx-tasks --post-watch --loglevel silent", - "serve": "concurrently \"gulp serve-deprecated --locale=nb-no --nobrowser NODE --max-old-space-size=8192\"", + "serve": "concurrently \"gulp serve-deprecated --locale=nb-no --nobrowser NODE --max-old-space-size=8192\"", "build": "gulp bundle --ship && gulp package-solution --ship", "postversion": "tsc && npm publish", "lint": "eslint --ext .ts,.tsx ./src --color --fix --config ../.eslintrc.yaml && npm run prettier", diff --git a/SharePointFramework/README.md b/SharePointFramework/README.md index 5a3fba4fa..d98ebc9db 100644 --- a/SharePointFramework/README.md +++ b/SharePointFramework/README.md @@ -13,6 +13,7 @@ | ProjectListWebPart | PortfolioWebParts | Prosjektutlisting (porteføljeforside) | 54fbeb7d-e463-4dcc-8873-50a3ab2f0f68 | | PortfolioTimelineWebPart | PortfolioWebParts | Prosjekttidslinje (Porteføljenivå) | 7284c568-f66c-4218-bb2c-3734a3cfa581 | | ResourceAllocationWebPart | PortfolioWebParts | Ressursallokering (tidslinje) | 2ef269b2-6370-4841-8b35-2185b7ccb22a | +| Idémodul | PortfolioWebParts | Idémodul side for håndtering av Idéer | 20f151a9-6891-4408-a6d6-77e749b9e3e7 | | ProgramAdministrationWebpart | ProgramWebParts | Program administrasjon | 9570e369-21a6-4bf5-8198-13506499de52 | | ProgramAggregationWebPart | ProgramWebParts | Program aggregeringsoversikt | 37c7e990-483d-4f70-b9b9-def1790817e7 | | ProgramProjectOverviewWebPart | ProgramWebParts | Programoversikt | 01417142-67c8-498b-a6da-6e78003023dd | diff --git a/Templates/Portfolio/Objects/ClientSidePages/@.xml b/Templates/Portfolio/Objects/ClientSidePages/@.xml index a2f573b34..e427896a5 100644 --- a/Templates/Portfolio/Objects/ClientSidePages/@.xml +++ b/Templates/Portfolio/Objects/ClientSidePages/@.xml @@ -2,7 +2,6 @@ - @@ -10,4 +9,5 @@ + \ No newline at end of file diff --git a/Templates/Portfolio/Objects/ClientSidePages/Idemodul.xml b/Templates/Portfolio/Objects/ClientSidePages/Idemodul.xml index e24a56768..4da37531d 100644 --- a/Templates/Portfolio/Objects/ClientSidePages/Idemodul.xml +++ b/Templates/Portfolio/Objects/ClientSidePages/Idemodul.xml @@ -2,7 +2,7 @@ - + From 404487b7d20665046d9dff79c2abee7e1b7847f5 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Mon, 21 Oct 2024 14:47:37 +0200 Subject: [PATCH 05/31] Add open/close hamburger --- .../src/components/IdeaModule/IdeaModule.tsx | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index 55c096521..8ad294fc2 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -68,26 +68,35 @@ const Reports = bundleIcon(DocumentBulletListMultiple20Filled, DocumentBulletLis export const IdeaModule: FC = (props) => { const { state, setState, fluentProviderId } = useIdeaModule(props) + const [isOpen, setIsOpen] = React.useState(true) const [size, setNavSize] = useState('small') const [enabledLinks, setEnabledLinks] = useState(true) const linkDestination = enabledLinks ? '#' : '' + const renderHamburgerWithToolTip = () => { + return ( + + setIsOpen(!isOpen)} /> + + ); + }; + return ( -
- - - + + + +
- - + + {renderHamburgerWithToolTip()} @@ -164,6 +173,7 @@ export const IdeaModule: FC = (props) => {
+ {!isOpen && renderHamburgerWithToolTip()}

Bring your ideas to life. Here you can create, share and collaborate on ideas.

@@ -172,10 +182,10 @@ export const IdeaModule: FC = (props) => { also collaborate on ideas with your team members.

- - - -
+
+ + + ) } From 9f7cea60b0d5bd786a81a2fe8ec750f3dd74b56e Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Tue, 22 Oct 2024 09:30:45 +0200 Subject: [PATCH 06/31] Hamboorger --- .../src/components/IdeaModule/IdeaModule.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index 8ad294fc2..b97e10141 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -76,11 +76,11 @@ export const IdeaModule: FC = (props) => { const renderHamburgerWithToolTip = () => { return ( - + setIsOpen(!isOpen)} /> - ); - }; + ) + } return ( @@ -95,7 +95,7 @@ export const IdeaModule: FC = (props) => { size={size} > - + {renderHamburgerWithToolTip()} From c638a8f04228fef9b7b487e0146dd086d2a2b44e Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Fri, 25 Oct 2024 13:39:17 +0200 Subject: [PATCH 07/31] Get ideas dynamically including values, field mappings ++ --- .github/workflows/ci-channel-test.yml | 1 - .../IdeaModule/IdeaField/IdeaField.tsx | 19 ++ .../components/IdeaModule/IdeaField/index.ts | 2 + .../components/IdeaModule/IdeaField/types.ts | 8 + .../IdeaModule/IdeaField/useIdeaField.tsx | 158 +++++++++++++ .../IdeaList/Commands/useCommands.tsx | 2 +- .../IdeaModule/IdeaList/IdeaList.tsx | 2 +- .../IdeaModule/IdeaList/List/List.tsx | 2 +- .../IdeaList/useIdeaListRenderer.tsx | 2 +- .../IdeaModule/IdeaModule.module.scss | 17 ++ .../src/components/IdeaModule/IdeaModule.tsx | 207 +++++++----------- .../src/components/IdeaModule/types.ts | 23 +- .../components/IdeaModule/useIdeaModule.ts | 33 +++ .../IdeaModule/useIdeaModuleDataFetch.ts | 12 +- .../IdeaModule/useIdeaModuleState.ts | 7 +- .../PortfolioWebParts/src/data/index.ts | 88 +++++++- .../PortfolioWebParts/src/data/types.ts | 11 +- .../src/webparts/ideaModule/index.ts | 4 + 18 files changed, 444 insertions(+), 154 deletions(-) create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/IdeaField.tsx create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/index.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/types.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx diff --git a/.github/workflows/ci-channel-test.yml b/.github/workflows/ci-channel-test.yml index 11f54f68e..c2372faec 100644 --- a/.github/workflows/ci-channel-test.yml +++ b/.github/workflows/ci-channel-test.yml @@ -4,7 +4,6 @@ on: push: branches: - releases/1.10 - - feat/ideamodule paths: - 'SharePointFramework/**' - 'Install/**' diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/IdeaField.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/IdeaField.tsx new file mode 100644 index 000000000..ac199c86f --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/IdeaField.tsx @@ -0,0 +1,19 @@ +import { Text } from '@fluentui/react-components' +import React, { FC } from 'react' +import { IIdeaFieldProps } from './types' +import { useIdeaField } from './useIdeaField' + +export const IdeaField: FC = (props) => { + const { renderValueForField } = useIdeaField(props) + + return ( +
+ + {props.model.displayName} + + {renderValueForField()} +
+ ) +} + +export * from './types' diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/index.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/index.ts new file mode 100644 index 000000000..186b93c0b --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/index.ts @@ -0,0 +1,2 @@ +export * from './IdeaField' +export * from './types' diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/types.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/types.ts new file mode 100644 index 000000000..62ea344e7 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/types.ts @@ -0,0 +1,8 @@ +import { EditableSPField } from 'pp365-shared-library' + +export interface IIdeaFieldProps extends React.HTMLAttributes { + /** + * Project property model + */ + model: EditableSPField +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx new file mode 100644 index 000000000..696dc1980 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx @@ -0,0 +1,158 @@ +import { IPersonaProps, ITag, Link } from '@fluentui/react' +import React from 'react' +import { IIdeaFieldProps } from './types' +import { Persona } from '@fluentui/react-components' +import { OverflowTagMenu } from 'pp365-shared-library' +import { + ChevronCircleRightFilled, + EarthFilled, + GlobeLocationFilled, + TagFilled, + TagMultipleFilled +} from '@fluentui/react-icons' + +/** + * Component logic hook for the `IdeaField` component. + * + * @param props Props for the `IdeaField` component + * + * @returns a render function `renderValueForField` for the `IdeaField` component, + * as well as the `displayMode` for the `IdeaField` component. + */ +export function useIdeaField(props: IIdeaFieldProps) { + /** + * Renders the value for the field based on the field type. + * + * @returns JSX.Element + */ + const renderValueForField = () => { + let icon = TagMultipleFilled + + if (props.model.internalName === 'GtIdeaRecommendation') { + props.model.type = 'TaxonomyFieldType' + } + + switch (props.model.internalName) { + case 'GtProjectServiceArea': + icon = GlobeLocationFilled + break + case 'GtProjectType': + icon = TagMultipleFilled + break + case 'GtUNSustDevGoals': + icon = EarthFilled + break + case 'GtProjectPhase': + icon = ChevronCircleRightFilled + break + default: + icon = TagFilled + break + } + + const renderMap = new Map JSX.Element>([ + [ + 'User', + ([user]: IPersonaProps[]) => { + return ( + + ) + } + ], + [ + 'UserMulti', + (users: IPersonaProps[]) => { + return ( +
+ {users.map((user, key) => ( + + ))} +
+ ) + } + ], + [ + 'TaxonomyFieldTypeMulti', + (tags: ITag[]) => ( +
+ tag && tag.name)} + icon={icon} + /> +
+ ) + ], + [ + 'TaxonomyFieldType', + ([tag]: ITag[]) => ( +
+ +
+ ) + ], + [ + 'URL', + ({ url, description }) => { + return ( +
+ + {description ?? url} + +
+ ) + } + ], + [ + 'Note', + (textValue: string) => ( +
') + }} + >
+ ) + ], + [ + 'DateTime', + (date: Date) => { + return
{date.toLocaleDateString()}
+ } + ] + ]) + + const value = props.model.getParsedValue() + + if (renderMap.has(props.model.type)) { + return renderMap.get(props.model.type)(value) + } else { + return
{value}
+ } + } + + return { renderValueForField } +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/useCommands.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/useCommands.tsx index 7573a0580..eeaf0ac89 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/useCommands.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/Commands/useCommands.tsx @@ -64,7 +64,7 @@ export function useCommands() { const searchBoxPlaceholder = !context.state.loading || _.isEmpty(context.state.ideas) ? '' - : format(strings.SearchBoxPlaceholderText, context.state.ideas.length) + : format(strings.SearchBoxPlaceholderText, context.state.ideas.data.items.length) return { toolbarItems, searchBoxPlaceholder } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/IdeaList.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/IdeaList.tsx index 8b3d73dd1..427fbaa1c 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/IdeaList.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/IdeaList.tsx @@ -13,7 +13,7 @@ export const IdeaList: FC = (props) => { const { context, fluentProviderId } = useIdeaList(props) const renderIdeas = useIdeaListRenderer(context) - if (context.state.ideas.length === 0) { + if (context.state.ideas.data.items.length === 0) { return (
diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/List.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/List.tsx index e7217735c..53e616695 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/List.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaList/List/List.tsx @@ -19,7 +19,7 @@ export const List = () => { return (
diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss index 4318b194a..15a2ccd66 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss @@ -10,5 +10,22 @@ display: grid; justify-content: flex-start; align-items: flex-start; + + .idea { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 16px; + margin-bottom: 16px; + + .ideaTitle { + font-size: 1.5rem; + font-weight: bold; + margin: 0; + } + + .ideaField { + margin: 0; + } + } } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index b97e10141..096f0f228 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -3,7 +3,7 @@ import styles from './IdeaModule.module.scss' import { IdeaModuleContext } from './context' import { IIdeaModuleProps } from './types' import { useIdeaModule } from './useIdeaModule' -import { FluentProvider, IdPrefixProvider, Tooltip } from '@fluentui/react-components' +import { FluentProvider, IdPrefixProvider, Spinner, Tooltip } from '@fluentui/react-components' import { customLightTheme } from 'pp365-shared-library' import { Hamburger, @@ -23,56 +23,28 @@ import { import { Board20Filled, Board20Regular, - BoxMultiple20Filled, - BoxMultiple20Regular, - DataArea20Filled, - DataArea20Regular, - DocumentBulletListMultiple20Filled, - DocumentBulletListMultiple20Regular, - HeartPulse20Filled, - HeartPulse20Regular, - MegaphoneLoud20Filled, - MegaphoneLoud20Regular, NotePin20Filled, NotePin20Regular, - People20Filled, - People20Regular, - PeopleStar20Filled, - PeopleStar20Regular, - Person20Filled, - PersonLightbulb20Filled, - PersonLightbulb20Regular, - Person20Regular, - PersonSearch20Filled, - PersonSearch20Regular, - PreviewLink20Filled, - PreviewLink20Regular, + Lightbulb20Filled, + Lightbulb20Regular, bundleIcon, PersonCircle24Regular } from '@fluentui/react-icons' +import { IdeaField } from './IdeaField' -const Person = bundleIcon(Person20Filled, Person20Regular) const Dashboard = bundleIcon(Board20Filled, Board20Regular) -const Announcements = bundleIcon(MegaphoneLoud20Filled, MegaphoneLoud20Regular) -const EmployeeSpotlight = bundleIcon(PersonLightbulb20Filled, PersonLightbulb20Regular) -const Search = bundleIcon(PersonSearch20Filled, PersonSearch20Regular) -const PerformanceReviews = bundleIcon(PreviewLink20Filled, PreviewLink20Regular) +const Lightbulb = bundleIcon(Lightbulb20Filled, Lightbulb20Regular) const JobPostings = bundleIcon(NotePin20Filled, NotePin20Regular) -const Interviews = bundleIcon(People20Filled, People20Regular) -const HealthPlans = bundleIcon(HeartPulse20Filled, HeartPulse20Regular) -const TrainingPrograms = bundleIcon(BoxMultiple20Filled, BoxMultiple20Regular) -const CareerDevelopment = bundleIcon(PeopleStar20Filled, PeopleStar20Regular) -const Analytics = bundleIcon(DataArea20Filled, DataArea20Regular) -const Reports = bundleIcon(DocumentBulletListMultiple20Filled, DocumentBulletListMultiple20Regular) export const IdeaModule: FC = (props) => { - const { state, setState, fluentProviderId } = useIdeaModule(props) + const { state, setState, fluentProviderId, ideas } = useIdeaModule(props) const [isOpen, setIsOpen] = React.useState(true) const [size, setNavSize] = useState('small') const [enabledLinks, setEnabledLinks] = useState(true) + const [selectedIdea, setSelectedIdea] = useState('') - const linkDestination = enabledLinks ? '#' : '' + const linkDestination = enabledLinks ? `?idea=${selectedIdea}` : '' const renderHamburgerWithToolTip = () => { return ( @@ -86,103 +58,75 @@ export const IdeaModule: FC = (props) => { -
- - - - {renderHamburgerWithToolTip()} - - - - }>Idémodul - } value='1'> - Dashboard - - } value='2'> - Announcements - - } value='3'> - Employee Spotlight - - } href={linkDestination} value='4'> - Profile Search - - } href={linkDestination} value='5'> - Performance Reviews - + {state.loading ? ( + + ) : ( +
+ + + + {renderHamburgerWithToolTip()} + + + + }>Idémodul + } value='1'> + Totaloversikt + + Registrerte idéer + } value='2'> + Oversikt + + + }>Idéer - Employee Management - - }>Job Postings - - - Openings - - - Submissions - - - - } value='9'> - Interviews - - - Benefits - } value='10'> - Health Plans - - - }>Retirement - - - Plan Information - - - Fund Performance - - - - - - } value='15'> - Training Programs - - - }>Career Development - - - Career Paths - - - Planning - - - - } value='19'> - Workforce Data - - } value='20'> - Reports - - - -
- {!isOpen && renderHamburgerWithToolTip()} -

- Bring your ideas to life. Here you can create, share and collaborate on ideas. -

-

- With the new Idea module, you can create and share ideas with your team. You can - also collaborate on ideas with your team members. -

+ + {ideas.map((idea, idx) => ( + + {idea.Title} + + ))} + + + + Idéer under behandling + } value='4'> + Oversikt + + + }>Idéer + + + Idé 3 + + + Idé 4 + + + + + +
+

+ Bring your ideas to life. Here you can create, share and collaborate on ideas. +

+
+
{ideas[0].Title}
+ {ideas.map((model, idx) => ( + + ))} +
+ {!isOpen && renderHamburgerWithToolTip()} +
-
+ )} @@ -191,6 +135,7 @@ export const IdeaModule: FC = (props) => { IdeaModule.defaultProps = { configurationList: 'Idékonfigurasjon', + configuration: 'Standard', sortBy: 'Title', showSearchBox: true, showRenderModeSelector: true, diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts index 9b66851bb..1b4548066 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts @@ -1,7 +1,9 @@ +import { ItemFieldValues, SPField } from 'pp365-shared-library' import { IBaseComponentProps } from '../types' export interface IIdeaModuleProps extends IBaseComponentProps { configurationList: string + configuration: string sortBy?: string showSearchBox?: boolean showRenderModeSelector?: boolean @@ -12,10 +14,11 @@ export interface IIdeaModuleProps extends IBaseComponentProps { export interface IIdeaModuleState { loading?: boolean + refetch?: number isRefetching?: boolean error?: any - configuration?: ConfigurationItem[] - ideas?: Record + configuration?: ConfigurationItem + ideas?: Ideas searchTerm: string renderMode?: IdeaListRenderMode isUserInIdeaManagerGroup?: boolean @@ -36,3 +39,19 @@ export class ConfigurationItem { ideaProcessingChoices: string ideaRegistrationChoices: string } + +export class Ideas { + data: IIdeaData +} + +export interface IIdeaData { + /** + * The items from the lists containing the field values + */ + items: ItemFieldValues[] + + /** + * Fields for the list + */ + fields?: SPField[] +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts index e3c01ed35..81634c7a3 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts @@ -1,6 +1,8 @@ import { useId } from '@fluentui/react-components' import { IIdeaModuleProps } from './types' import { useIdeaModuleState } from './useIdeaModuleState' +import { useIdeaModuleDataFetch } from './useIdeaModuleDataFetch' +import { EditableSPField, ItemFieldValues } from 'pp365-shared-library' /** * Component logic hook for `IdeaModule` component. @@ -10,9 +12,40 @@ export function useIdeaModule(props: IIdeaModuleProps) { const { state, setState } = useIdeaModuleState(props) const fluentProviderId = useId('fp-idea-module') + useIdeaModuleDataFetch(props, state.refetch, setState) + + // const createProperties(state: IProjectInformationState, spfxContext: SPFxContext) { + + // return state.data.fields + // .map((field) => + // new EditableSPField(field) + // .init(state.data.columns, currentLocale, state.data.template?.fieldConfiguration) + // .setValue(state.data.fieldValues) + // ) + // .sort((a, b) => { + // if (!a.column) return 1 + // if (!b.column) return -1 + // return a.column.sortOrder - b.column.sortOrder + // }) + // } + + const ideas = + !state.loading && + state.ideas.data.fields + .map((field) => { + const fieldValues: ItemFieldValues = state.ideas.data.items[0] + return new EditableSPField(field).setValue(fieldValues) + }) + .sort((a, b) => { + if (!a.column) return 1 + if (!b.column) return -1 + return a.column.sortOrder - b.column.sortOrder + }) + return { state, setState, + ideas, fluentProviderId } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleDataFetch.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleDataFetch.ts index 07b8f5f3b..058bc2a33 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleDataFetch.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleDataFetch.ts @@ -15,15 +15,17 @@ export function useIdeaModuleDataFetch( setState: (newState: Partial) => void ) { useEffect(() => { - Promise.all([props.dataAdapter.getConfiguration(props.configurationList)]).then( - ([configuration]) => { + Promise.all([ + props.dataAdapter.getIdeaConfiguration(props.configurationList, props.configuration) + ]).then(async ([configuration]) => { + await props.dataAdapter.getIdeasData(configuration).then((ideas) => setState({ configuration, - ideas: configuration, + ideas, loading: false, isRefetching: false }) - } - ) + ) + }) }, [refetch]) } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts index 677d59387..9d8d32ca2 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts @@ -12,12 +12,13 @@ export function useIdeaModuleState(props: IIdeaModuleProps) { const [state, $setState] = useState({ loading: true, - configuration: [], - ideas: {}, + configuration: null, + ideas: null, error: null, searchTerm: '', renderMode: props.defaultRenderMode ?? 'tiles', - sort: defaultSort + sort: defaultSort, + isRefetching: false }) /** diff --git a/SharePointFramework/PortfolioWebParts/src/data/index.ts b/SharePointFramework/PortfolioWebParts/src/data/index.ts index 19ee19ab0..81a411d88 100644 --- a/SharePointFramework/PortfolioWebParts/src/data/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/data/index.ts @@ -16,13 +16,16 @@ import { DataSource, DataSourceService, DefaultCaching, + getClassProperties, getUserPhoto, IGraphGroup, + ItemFieldValues, PortalDataService, PortfolioOverviewView, ProjectContentColumn, ProjectListModel, SPContentType, + SPField, SPFxContext, SPProjectItem, SPTimelineConfigurationItem, @@ -52,7 +55,8 @@ import { } from './types' import { IPersonaProps, IPersonaSharedProps } from '@fluentui/react' import { IProvisionRequestItem } from 'interfaces/IProvisionRequestItem' -import { ConfigurationItem } from 'components/IdeaModule' +import { ConfigurationItem, Ideas } from 'components/IdeaModule' +import { IItem } from '@pnp/sp/items/types' /** * Data adapter for Portfolio Web Parts. @@ -1029,7 +1033,10 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { } } - public async getConfiguration(listName: string): Promise { + public async getIdeaConfiguration( + listName: string, + configurationName: string + ): Promise { try { const configurationList = this._sp.web.lists.getByTitle(listName) const spItems = await configurationList.items @@ -1044,18 +1051,87 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { ) .using(DefaultCaching)() - return spItems.map((item) => { - return { + const configuration: ConfigurationItem = spItems + .filter((item) => item.Title === configurationName) + .map((item) => ({ title: item.Title, description: item.GtDescription, ideaProcessingList: item.GtIdeaProcessingList, ideaRegistrationList: item.GtIdeaRegistrationList, ideaProcessingChoices: item.GtIdeaProcessingChoices, ideaRegistrationChoices: item.GtIdeaRegistrationChoices + }))[0] + + return configuration + } catch (error) { + return null + } + } + + public async getItemFieldValues(item: IItem, userFields: string[] = []) { + const [fieldValuesAsText, fieldValues] = await Promise.all([ + item.fieldValuesAsText>(), + item + .select( + '*', + ...userFields.map((fieldName) => `${fieldName}/Id`), + ...userFields.map((fieldName) => `${fieldName}/Title`), + ...userFields.map((fieldName) => `${fieldName}/EMail`) + ) + .expand(...userFields)>() + ]) + return ItemFieldValues.create({ fieldValues, fieldValuesAsText }) + } + + public async getIdeasData(configuration: ConfigurationItem): Promise { + try { + const ideaRegistrationList = this._sp.web.lists.getByTitle(configuration.ideaRegistrationList) + // const ideaProcessingList = this._sp.web.lists.getByTitle(configuration.ideaProcessingList) + // const registered = await ideaRegistrationList.items.select('*', 'FieldValuesAsText') + // .expand('FieldValuesAsText').using(DefaultCaching)() + // const processing = await ideaProcessingList.items.select('*', 'FieldValuesAsText') + // .expand('FieldValuesAsText').using(DefaultCaching)() + + const fields = await ideaRegistrationList.fields + .select(...getClassProperties(SPField)) + .filter( + "substringof('Gt', InternalName) or InternalName eq 'Title' or InternalName eq 'Id' or InternalName eq 'ID'" + )() + const userFields = fields + .filter((fld) => fld.TypeAsString.indexOf('User') === 0) + .map((fld) => fld.InternalName) + + const [ideaList] = await this._sp.web.lists + .filter(`Title eq '${configuration.ideaRegistrationList}'`) + .select('Id')() + + const items = await this._sp.web.lists.getById(ideaList.Id).items.select('Id')< + { Id: number }[] + >() + + const list = this._sp.web.lists.getById(ideaList.Id) + const listItems = await Promise.all( + items.map(async (item) => { + const listItem = await list.items.getById(item.Id) + return listItem + }) + ) + + const allFieldValues = await Promise.all( + listItems.map(async (item) => { + const fieldValues = await this.getItemFieldValues(item, userFields) + return fieldValues + }) + ) + + return { + data: { + items: allFieldValues, + fields } - }) + } } catch (error) { - return [] + return null } } } diff --git a/SharePointFramework/PortfolioWebParts/src/data/types.ts b/SharePointFramework/PortfolioWebParts/src/data/types.ts index ef12d86c3..7d9f8d49d 100644 --- a/SharePointFramework/PortfolioWebParts/src/data/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/data/types.ts @@ -16,7 +16,7 @@ import { import { IPortfolioAggregationConfiguration, IPortfolioOverviewConfiguration } from '../components' import { IPersonaSharedProps } from '@fluentui/react' import { IProvisionRequestItem } from 'interfaces/IProvisionRequestItem' -import { ConfigurationItem } from 'components/IdeaModule' +import { ConfigurationItem, Ideas } from 'components/IdeaModule' export interface IFetchDataForViewItemResult extends ISearchResult { SiteId: string @@ -367,5 +367,12 @@ export interface IPortfolioWebPartsDataAdapter { * * @returns A Promise that resolves to an array containing the configuration. */ - getConfiguration?(listName: string): Promise + getIdeaConfiguration?(listName: string, configurationName: string): Promise + + /** + * Retrieves the data for the ideas from the "Idéregistrering" list + * + * @returns A Promise that resolves to an object containing the data for the ideas. + */ + getIdeasData?(configuration: ConfigurationItem): Promise } diff --git a/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts b/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts index 9b922386e..33d9602b6 100644 --- a/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts @@ -27,6 +27,10 @@ export default class IdeaModuleWebPart extends BasePortfolioWebPart Date: Thu, 31 Oct 2024 13:42:19 +0100 Subject: [PATCH 08/31] getSelectedIdea dynamically --- .../src/components/IdeaModule/IdeaModule.tsx | 13 ++-- .../src/components/IdeaModule/types.ts | 23 +++++- .../components/IdeaModule/useIdeaModule.ts | 72 +++++++++++-------- .../IdeaModule/useIdeaModuleState.ts | 2 +- .../PortfolioWebParts/src/data/index.ts | 7 +- 5 files changed, 75 insertions(+), 42 deletions(-) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index 096f0f228..dde6d0627 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -37,14 +37,13 @@ const Lightbulb = bundleIcon(Lightbulb20Filled, Lightbulb20Regular) const JobPostings = bundleIcon(NotePin20Filled, NotePin20Regular) export const IdeaModule: FC = (props) => { - const { state, setState, fluentProviderId, ideas } = useIdeaModule(props) + const { state, setState, fluentProviderId, selectedIdea } = useIdeaModule(props) const [isOpen, setIsOpen] = React.useState(true) const [size, setNavSize] = useState('small') const [enabledLinks, setEnabledLinks] = useState(true) - const [selectedIdea, setSelectedIdea] = useState('') - const linkDestination = enabledLinks ? `?idea=${selectedIdea}` : '' + const linkDestination = enabledLinks ? `#` : '' const renderHamburgerWithToolTip = () => { return ( @@ -88,9 +87,9 @@ export const IdeaModule: FC = (props) => { }>Idéer - {ideas.map((idea, idx) => ( + {state.ideas.data.items.map((idea, idx) => ( - {idea.Title} + {idea?.Title} ))} @@ -118,8 +117,8 @@ export const IdeaModule: FC = (props) => { Bring your ideas to life. Here you can create, share and collaborate on ideas.

-
{ideas[0].Title}
- {ideas.map((model, idx) => ( +

{selectedIdea.item.Title}

+ {state.selectedIdea.fieldValues.map((model, idx) => ( ))}
diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts index 1b4548066..61c01eb19 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts @@ -1,4 +1,4 @@ -import { ItemFieldValues, SPField } from 'pp365-shared-library' +import { EditableSPField, ItemFieldValues, SPField } from 'pp365-shared-library' import { IBaseComponentProps } from '../types' export interface IIdeaModuleProps extends IBaseComponentProps { @@ -19,6 +19,7 @@ export interface IIdeaModuleState { error?: any configuration?: ConfigurationItem ideas?: Ideas + selectedIdea?: IIdea searchTerm: string renderMode?: IdeaListRenderMode isUserInIdeaManagerGroup?: boolean @@ -40,6 +41,19 @@ export class ConfigurationItem { ideaRegistrationChoices: string } +export interface IIdea { + /** + * The item + */ + item: any[] + + /** + * The fieldValues for the item + */ + fieldValues?: EditableSPField[] +} + + export class Ideas { data: IIdeaData } @@ -48,7 +62,12 @@ export interface IIdeaData { /** * The items from the lists containing the field values */ - items: ItemFieldValues[] + items: any[] + + /** + * The items from the lists containing the field values + */ + fieldValues?: ItemFieldValues[] /** * Fields for the list diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts index 81634c7a3..a4f9faf4c 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts @@ -2,7 +2,9 @@ import { useId } from '@fluentui/react-components' import { IIdeaModuleProps } from './types' import { useIdeaModuleState } from './useIdeaModuleState' import { useIdeaModuleDataFetch } from './useIdeaModuleDataFetch' -import { EditableSPField, ItemFieldValues } from 'pp365-shared-library' +import { EditableSPField, ItemFieldValues, parseUrlHash } from 'pp365-shared-library' +import _ from 'lodash' +import { useEffect } from 'react' /** * Component logic hook for `IdeaModule` component. @@ -14,38 +16,52 @@ export function useIdeaModule(props: IIdeaModuleProps) { useIdeaModuleDataFetch(props, state.refetch, setState) - // const createProperties(state: IProjectInformationState, spfxContext: SPFxContext) { + const getSelectedIdea = (hashState: Map) => { + if (state.selectedIdea) return state.selectedIdea + const ideaIdUrlParam = new URLSearchParams(document.location.search).get('ideaId') + const ideas = state.ideas.data.items + let selectedIdea = null - // return state.data.fields - // .map((field) => - // new EditableSPField(field) - // .init(state.data.columns, currentLocale, state.data.template?.fieldConfiguration) - // .setValue(state.data.fieldValues) - // ) - // .sort((a, b) => { - // if (!a.column) return 1 - // if (!b.column) return -1 - // return a.column.sortOrder - b.column.sortOrder - // }) - // } - - const ideas = - !state.loading && - state.ideas.data.fields - .map((field) => { - const fieldValues: ItemFieldValues = state.ideas.data.items[0] - return new EditableSPField(field).setValue(fieldValues) - }) - .sort((a, b) => { - if (!a.column) return 1 - if (!b.column) return -1 - return a.column.sortOrder - b.column.sortOrder - }) + if (ideaIdUrlParam) { + selectedIdea = _.find(ideas, (idea) => idea.id.toString() === ideaIdUrlParam) + if (!selectedIdea) { + console.log('Idea not found', ideaIdUrlParam) + } + } else if (hashState.has('ideaId')) { + selectedIdea = _.find(ideas, (idea) => idea.id === hashState.get('ideaId')) + if (!selectedIdea) { + console.log('Idea not found', ideaIdUrlParam) + } + } else { + selectedIdea = ideas[0].Id + if (!selectedIdea) { + console.log('Idea not found', ideaIdUrlParam) + } + } + return { + item: selectedIdea, + fieldValues: state.ideas.data.fields + .map((field) => { + const fieldValues: ItemFieldValues = state.ideas.data.fieldValues.find( + (fv) => fv.id === selectedIdea + ) + if (!fieldValues) return null + return new EditableSPField(field).setValue(fieldValues) + }) + .sort((a, b) => { + if (!a.column) return 1 + if (!b.column) return -1 + return a.column.sortOrder - b.column.sortOrder + }) + } + } + const hashState = parseUrlHash() + const selectedIdea = !state.loading && getSelectedIdea(hashState) return { state, setState, - ideas, + selectedIdea, fluentProviderId } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts index 9d8d32ca2..7d9fb4ed8 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts @@ -18,7 +18,7 @@ export function useIdeaModuleState(props: IIdeaModuleProps) { searchTerm: '', renderMode: props.defaultRenderMode ?? 'tiles', sort: defaultSort, - isRefetching: false + isRefetching: false, }) /** diff --git a/SharePointFramework/PortfolioWebParts/src/data/index.ts b/SharePointFramework/PortfolioWebParts/src/data/index.ts index 81a411d88..6dbe8566d 100644 --- a/SharePointFramework/PortfolioWebParts/src/data/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/data/index.ts @@ -1105,9 +1105,7 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { .filter(`Title eq '${configuration.ideaRegistrationList}'`) .select('Id')() - const items = await this._sp.web.lists.getById(ideaList.Id).items.select('Id')< - { Id: number }[] - >() + const items = await this._sp.web.lists.getById(ideaList.Id).items() const list = this._sp.web.lists.getById(ideaList.Id) const listItems = await Promise.all( @@ -1126,7 +1124,8 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { return { data: { - items: allFieldValues, + items: items, + fieldValues: allFieldValues, fields } } From 9b93a6ad186921dbac3f432529697534b40863fe Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Thu, 7 Nov 2024 16:17:56 +0100 Subject: [PATCH 09/31] Improvements to getting ideas + change idea state --- .../IdeaModule/IdeaField/useIdeaField.tsx | 10 +- .../src/components/IdeaModule/IdeaModule.tsx | 74 ++++++---- .../src/components/IdeaModule/types.ts | 18 ++- .../components/IdeaModule/useIdeaModule.ts | 86 ++++++----- .../IdeaModule/useIdeaModuleState.ts | 2 +- .../PortfolioWebParts/src/data/index.ts | 137 +++++++++++++----- .../PortfolioWebParts/src/data/types.ts | 4 +- .../src/webparts/ideaModule/index.ts | 4 +- 8 files changed, 224 insertions(+), 111 deletions(-) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx index 696dc1980..3e6ab01e4 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx @@ -147,10 +147,14 @@ export function useIdeaField(props: IIdeaFieldProps) { const value = props.model.getParsedValue() - if (renderMap.has(props.model.type)) { - return renderMap.get(props.model.type)(value) + if (value) { + if (renderMap.has(props.model.type)) { + return renderMap.get(props.model.type)(value) + } else { + return
{value}
+ } } else { - return
{value}
+ return
} } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index dde6d0627..7a0d9c6e6 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -1,10 +1,10 @@ -import React, { FC, useState } from 'react' +import React, { FC } from 'react' import styles from './IdeaModule.module.scss' import { IdeaModuleContext } from './context' import { IIdeaModuleProps } from './types' import { useIdeaModule } from './useIdeaModule' import { FluentProvider, IdPrefixProvider, Spinner, Tooltip } from '@fluentui/react-components' -import { customLightTheme } from 'pp365-shared-library' +import { customLightTheme, setUrlHash, UserMessage } from 'pp365-shared-library' import { Hamburger, NavCategory, @@ -16,7 +16,6 @@ import { NavSectionHeader, NavSubItem, NavSubItemGroup, - NavSize, NavDivider, AppItemStatic } from '@fluentui/react-nav-preview' @@ -37,13 +36,8 @@ const Lightbulb = bundleIcon(Lightbulb20Filled, Lightbulb20Regular) const JobPostings = bundleIcon(NotePin20Filled, NotePin20Regular) export const IdeaModule: FC = (props) => { - const { state, setState, fluentProviderId, selectedIdea } = useIdeaModule(props) - + const { state, setState, getSelectedIdea, fluentProviderId } = useIdeaModule(props) const [isOpen, setIsOpen] = React.useState(true) - const [size, setNavSize] = useState('small') - const [enabledLinks, setEnabledLinks] = useState(true) - - const linkDestination = enabledLinks ? `#` : '' const renderHamburgerWithToolTip = () => { return ( @@ -53,6 +47,8 @@ export const IdeaModule: FC = (props) => { ) } + console.log({ state }) + return ( @@ -67,7 +63,7 @@ export const IdeaModule: FC = (props) => { openCategories={['3', '5']} open={isOpen} type={'inline'} - size={size} + size='small' > @@ -76,19 +72,25 @@ export const IdeaModule: FC = (props) => { }>Idémodul - } value='1'> + } value='1'> Totaloversikt Registrerte idéer - } value='2'> + } value='2'> Oversikt }>Idéer - {state.ideas.data.items.map((idea, idx) => ( - + { + setUrlHash({ ideaId: idea.Id.toString() }) + getSelectedIdea() + }} + > {idea?.Title} ))} @@ -96,31 +98,47 @@ export const IdeaModule: FC = (props) => { Idéer under behandling - } value='4'> + } value='4'> Oversikt }>Idéer - - Idé 3 - - - Idé 4 - + {state.ideas.data.items.map((idea, idx) => ( + { + setUrlHash({ ideaId: idea.Id.toString() }) + getSelectedIdea() + }} + > + {idea?.Title} + + ))}
-

- Bring your ideas to life. Here you can create, share and collaborate on ideas. -

+ {state.error && ( + + )}
-

{selectedIdea.item.Title}

- {state.selectedIdea.fieldValues.map((model, idx) => ( - - ))} + {state.selectedIdea ? ( + <> +

{state.selectedIdea.item.Title}

+ {state.selectedIdea.fieldValues.map((model, idx) => ( + + ))} + + ) : ( + + )}
{!isOpen && renderHamburgerWithToolTip()}
diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts index 61c01eb19..317296932 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts @@ -1,5 +1,12 @@ import { EditableSPField, ItemFieldValues, SPField } from 'pp365-shared-library' import { IBaseComponentProps } from '../types' +import { MessageBarType } from '@fluentui/react' + +export class IdeaModuleErrorMessage extends Error { + constructor(public message: string, public type: MessageBarType) { + super(message) + } +} export interface IIdeaModuleProps extends IBaseComponentProps { configurationList: string @@ -18,7 +25,7 @@ export interface IIdeaModuleState { isRefetching?: boolean error?: any configuration?: ConfigurationItem - ideas?: Ideas + ideas?: Idea selectedIdea?: IIdea searchTerm: string renderMode?: IdeaListRenderMode @@ -45,16 +52,15 @@ export interface IIdea { /** * The item */ - item: any[] + item: any /** * The fieldValues for the item - */ + */ fieldValues?: EditableSPField[] } - -export class Ideas { +export class Idea { data: IIdeaData } @@ -66,7 +72,7 @@ export interface IIdeaData { /** * The items from the lists containing the field values - */ + */ fieldValues?: ItemFieldValues[] /** diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts index a4f9faf4c..029bb9144 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts @@ -1,8 +1,10 @@ +/* eslint-disable no-console */ + import { useId } from '@fluentui/react-components' -import { IIdeaModuleProps } from './types' +import { IIdeaModuleHashState, IIdeaModuleProps } from './types' import { useIdeaModuleState } from './useIdeaModuleState' import { useIdeaModuleDataFetch } from './useIdeaModuleDataFetch' -import { EditableSPField, ItemFieldValues, parseUrlHash } from 'pp365-shared-library' +import { EditableSPField, ItemFieldValues, parseUrlHash, setUrlHash } from 'pp365-shared-library' import _ from 'lodash' import { useEffect } from 'react' @@ -16,52 +18,68 @@ export function useIdeaModule(props: IIdeaModuleProps) { useIdeaModuleDataFetch(props, state.refetch, setState) - const getSelectedIdea = (hashState: Map) => { - if (state.selectedIdea) return state.selectedIdea + const getSelectedIdea = () => { + const hashState = parseUrlHash() + + if (state.selectedIdea && state.selectedIdea.item.Id === hashState.get('ideaId')) { + return + } + const ideaIdUrlParam = new URLSearchParams(document.location.search).get('ideaId') const ideas = state.ideas.data.items let selectedIdea = null if (ideaIdUrlParam) { - selectedIdea = _.find(ideas, (idea) => idea.id.toString() === ideaIdUrlParam) - if (!selectedIdea) { - console.log('Idea not found', ideaIdUrlParam) - } + selectedIdea = _.find(ideas, (idea) => idea.Id.toString() === ideaIdUrlParam) } else if (hashState.has('ideaId')) { - selectedIdea = _.find(ideas, (idea) => idea.id === hashState.get('ideaId')) - if (!selectedIdea) { - console.log('Idea not found', ideaIdUrlParam) - } + selectedIdea = _.find(ideas, (idea) => idea.Id === hashState.get('ideaId')) } else { - selectedIdea = ideas[0].Id - if (!selectedIdea) { - console.log('Idea not found', ideaIdUrlParam) - } + selectedIdea = _.first(ideas) } - return { - item: selectedIdea, - fieldValues: state.ideas.data.fields - .map((field) => { - const fieldValues: ItemFieldValues = state.ideas.data.fieldValues.find( - (fv) => fv.id === selectedIdea - ) - if (!fieldValues) return null - return new EditableSPField(field).setValue(fieldValues) - }) - .sort((a, b) => { - if (!a.column) return 1 - if (!b.column) return -1 - return a.column.sortOrder - b.column.sortOrder - }) + + if (!selectedIdea) { + state.error = 'Det ble ikke funnet noen ideer. Opprett en ny ide for å se din idé her.' + return } + + const obj: IIdeaModuleHashState = {} + if (selectedIdea) obj.ideaId = selectedIdea.Id.toString() + setUrlHash(obj) + + const fieldValues = state.ideas.data.fields + .map((field) => { + const fieldValues: ItemFieldValues = state.ideas.data.fieldValues.find( + (fv) => fv.id === selectedIdea.Id + ) + if (!fieldValues) return null + return new EditableSPField(field).setValue(fieldValues) + }) + .sort((a, b) => { + if (!a.column) return 1 + if (!b.column) return -1 + return a.column.sortOrder - b.column.sortOrder + }) + + setState({ + ...state, + selectedIdea: { + item: selectedIdea, + fieldValues: fieldValues + } + }) } - const hashState = parseUrlHash() - const selectedIdea = !state.loading && getSelectedIdea(hashState) + + + useEffect(() => { + if (!state.loading) { + getSelectedIdea() + } + }, [state.loading, state.selectedIdea]) return { state, setState, - selectedIdea, + getSelectedIdea, fluentProviderId } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts index 7d9fb4ed8..9d8d32ca2 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModuleState.ts @@ -18,7 +18,7 @@ export function useIdeaModuleState(props: IIdeaModuleProps) { searchTerm: '', renderMode: props.defaultRenderMode ?? 'tiles', sort: defaultSort, - isRefetching: false, + isRefetching: false }) /** diff --git a/SharePointFramework/PortfolioWebParts/src/data/index.ts b/SharePointFramework/PortfolioWebParts/src/data/index.ts index 6dbe8566d..bee816320 100644 --- a/SharePointFramework/PortfolioWebParts/src/data/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/data/index.ts @@ -1,3 +1,5 @@ +/* eslint-disable no-console */ + import { format } from '@fluentui/react/lib/Utilities' import { dateAdd, PnPClientStorage } from '@pnp/core' import { @@ -55,7 +57,7 @@ import { } from './types' import { IPersonaProps, IPersonaSharedProps } from '@fluentui/react' import { IProvisionRequestItem } from 'interfaces/IProvisionRequestItem' -import { ConfigurationItem, Ideas } from 'components/IdeaModule' +import { ConfigurationItem, Idea } from 'components/IdeaModule' import { IItem } from '@pnp/sp/items/types' /** @@ -1051,16 +1053,20 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { ) .using(DefaultCaching)() - const configuration: ConfigurationItem = spItems - .filter((item) => item.Title === configurationName) - .map((item) => ({ - title: item.Title, - description: item.GtDescription, - ideaProcessingList: item.GtIdeaProcessingList, - ideaRegistrationList: item.GtIdeaRegistrationList, - ideaProcessingChoices: item.GtIdeaProcessingChoices, - ideaRegistrationChoices: item.GtIdeaRegistrationChoices - }))[0] + const configurationItem = spItems.find((item) => item.Title === configurationName) + + if (!configurationItem) { + throw new Error(`Configuration with name ${configurationName} not found`) + } + + const configuration: ConfigurationItem = { + title: configurationItem.Title, + description: configurationItem.GtDescription, + ideaProcessingList: configurationItem.GtIdeaProcessingList, + ideaRegistrationList: configurationItem.GtIdeaRegistrationList, + ideaProcessingChoices: configurationItem.GtIdeaProcessingChoices, + ideaRegistrationChoices: configurationItem.GtIdeaRegistrationChoices + } return configuration } catch (error) { @@ -1083,31 +1089,32 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { return ItemFieldValues.create({ fieldValues, fieldValuesAsText }) } - public async getIdeasData(configuration: ConfigurationItem): Promise { + public getIdeaData(ideaId: number): Promise { + console.log(ideaId) try { - const ideaRegistrationList = this._sp.web.lists.getByTitle(configuration.ideaRegistrationList) - // const ideaProcessingList = this._sp.web.lists.getByTitle(configuration.ideaProcessingList) - // const registered = await ideaRegistrationList.items.select('*', 'FieldValuesAsText') - // .expand('FieldValuesAsText').using(DefaultCaching)() - // const processing = await ideaProcessingList.items.select('*', 'FieldValuesAsText') - // .expand('FieldValuesAsText').using(DefaultCaching)() - - const fields = await ideaRegistrationList.fields + } catch (error) { + return error + } + } + + public async getIdeasData(configuration: ConfigurationItem): Promise { + const getListData = async ( + listName: string + ): Promise<{ items: any[]; fieldValues: ItemFieldValues[]; fields: SPField[] }> => { + const [listInfo] = await this._sp.web.lists.filter(`Title eq '${listName}'`).select('Id')() + const list = this._sp.web.lists.getById(listInfo.Id) + const items = await list.items() + + const fields = await list.fields .select(...getClassProperties(SPField)) .filter( - "substringof('Gt', InternalName) or InternalName eq 'Title' or InternalName eq 'Id' or InternalName eq 'ID'" + 'substringof(\'Gt\', InternalName) or InternalName eq \'Title\' or InternalName eq \'Id\'' )() + const userFields = fields .filter((fld) => fld.TypeAsString.indexOf('User') === 0) .map((fld) => fld.InternalName) - const [ideaList] = await this._sp.web.lists - .filter(`Title eq '${configuration.ideaRegistrationList}'`) - .select('Id')() - - const items = await this._sp.web.lists.getById(ideaList.Id).items() - - const list = this._sp.web.lists.getById(ideaList.Id) const listItems = await Promise.all( items.map(async (item) => { const listItem = await list.items.getById(item.Id) @@ -1123,16 +1130,76 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { ) return { - data: { - items: items, - fieldValues: allFieldValues, - fields - } + items: items, + fieldValues: allFieldValues, + fields + } + } + + const registrationData = await getListData(configuration.ideaRegistrationList) + const processingData = await getListData(configuration.ideaProcessingList) + + console.log({ registrationData, processingData }) + + return { + data: { + items: processingData.items, + fieldValues: processingData.fieldValues, + fields: processingData.fields } - } catch (error) { - return null } } + + // public async getIdeasDataX(configuration: ConfigurationItem): Promise { + // try { + // const ideaRegistrationList = this._sp.web.lists.getByTitle(configuration.ideaRegistrationList) + // // const ideaProcessingList = this._sp.web.lists.getByTitle(configuration.ideaProcessingList) + // // const registered = await ideaRegistrationList.items.select('*', 'FieldValuesAsText') + // // .expand('FieldValuesAsText').using(DefaultCaching)() + // // const processing = await ideaProcessingList.items.select('*', 'FieldValuesAsText') + // // .expand('FieldValuesAsText').using(DefaultCaching)() + + // // const fields = await ideaRegistrationList.fields + // // .select(...getClassProperties(SPField)) + // // .filter( + // // "substringof('Gt', InternalName) or InternalName eq 'Title' or InternalName eq 'Id' or InternalName eq 'ID'" + // // )() + // // const userFields = fields + // // .filter((fld) => fld.TypeAsString.indexOf('User') === 0) + // // .map((fld) => fld.InternalName) + + // const [ideaList] = await this._sp.web.lists + // .filter(`Title eq '${configuration.ideaRegistrationList}'`) + // .select('Id')() + + // const items = await this._sp.web.lists.getById(ideaList.Id).items() + + // const list = this._sp.web.lists.getById(ideaList.Id) + // const listItems = await Promise.all( + // items.map(async (item) => { + // const listItem = await list.items.getById(item.Id) + // return listItem + // }) + // ) + + // const allFieldValues = await Promise.all( + // listItems.map(async (item) => { + // const fieldValues = await this.getItemFieldValues(item, userFields) + // return fieldValues + // }) + // ) + + // return { + // data: { + // items: items, + // fieldValues: allFieldValues, + // fields + // } + // } + // } catch (error) { + // return null + // } + // } } export * from './types' diff --git a/SharePointFramework/PortfolioWebParts/src/data/types.ts b/SharePointFramework/PortfolioWebParts/src/data/types.ts index 7d9f8d49d..26ef1bd03 100644 --- a/SharePointFramework/PortfolioWebParts/src/data/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/data/types.ts @@ -16,7 +16,7 @@ import { import { IPortfolioAggregationConfiguration, IPortfolioOverviewConfiguration } from '../components' import { IPersonaSharedProps } from '@fluentui/react' import { IProvisionRequestItem } from 'interfaces/IProvisionRequestItem' -import { ConfigurationItem, Ideas } from 'components/IdeaModule' +import { ConfigurationItem, Idea } from 'components/IdeaModule' export interface IFetchDataForViewItemResult extends ISearchResult { SiteId: string @@ -374,5 +374,5 @@ export interface IPortfolioWebPartsDataAdapter { * * @returns A Promise that resolves to an object containing the data for the ideas. */ - getIdeasData?(configuration: ConfigurationItem): Promise + getIdeasData?(configuration: ConfigurationItem): Promise } diff --git a/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts b/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts index 33d9602b6..a596cd7b0 100644 --- a/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts @@ -25,12 +25,12 @@ export default class IdeaModuleWebPart extends BasePortfolioWebPart Date: Fri, 8 Nov 2024 16:53:05 +0100 Subject: [PATCH 10/31] Improvements to ideaConfiguration + ideaPhase bar + merging of registered and processed ideas --- .../IdeaModule/IdeaModule.module.scss | 23 ++- .../src/components/IdeaModule/IdeaModule.tsx | 146 +++++++++++++----- .../src/components/IdeaModule/types.ts | 35 +++-- .../components/IdeaModule/useIdeaModule.ts | 24 ++- .../PortfolioWebParts/src/data/index.ts | 94 +++++------ .../PortfolioWebParts/src/data/types.ts | 10 +- .../src/models/IdeaConfigurationModel.ts | 63 ++++++++ .../PortfolioWebParts/src/models/index.ts | 1 + .../src/webparts/ideaModule/index.ts | 2 +- .../shared-library/src/icons/iconCatalog.ts | 20 ++- 10 files changed, 310 insertions(+), 108 deletions(-) create mode 100644 SharePointFramework/PortfolioWebParts/src/models/IdeaConfigurationModel.ts diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss index 15a2ccd66..56f72be66 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss @@ -7,21 +7,34 @@ .content { flex: 1; padding: 16px; + gap: 16px; display: grid; justify-content: flex-start; align-items: flex-start; - .idea { - display: grid; - grid-template-columns: 1fr 1fr; - grid-gap: 16px; - margin-bottom: 16px; + .ideaHeader { + display: flex; + flex-direction: column; + gap: 16px; + + .ideaPhases { + display: flex; + flex-direction: row; + justify-content: center; + } .ideaTitle { font-size: 1.5rem; font-weight: bold; margin: 0; } + } + + .idea { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 16px; + margin-bottom: 16px; .ideaField { margin: 0; diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index 7a0d9c6e6..1b190dfde 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -3,8 +3,16 @@ import styles from './IdeaModule.module.scss' import { IdeaModuleContext } from './context' import { IIdeaModuleProps } from './types' import { useIdeaModule } from './useIdeaModule' -import { FluentProvider, IdPrefixProvider, Spinner, Tooltip } from '@fluentui/react-components' -import { customLightTheme, setUrlHash, UserMessage } from 'pp365-shared-library' +import { + Divider, + FluentProvider, + IdPrefixProvider, + Spinner, + Tab, + TabList, + Tooltip +} from '@fluentui/react-components' +import { customLightTheme, getFluentIcon, setUrlHash, UserMessage } from 'pp365-shared-library' import { Hamburger, NavCategory, @@ -26,8 +34,7 @@ import { NotePin20Regular, Lightbulb20Filled, Lightbulb20Regular, - bundleIcon, - PersonCircle24Regular + bundleIcon } from '@fluentui/react-icons' import { IdeaField } from './IdeaField' @@ -47,8 +54,6 @@ export const IdeaModule: FC = (props) => { ) } - console.log({ state }) - return ( @@ -58,11 +63,11 @@ export const IdeaModule: FC = (props) => { ) : (
@@ -71,21 +76,20 @@ export const IdeaModule: FC = (props) => { - }>Idémodul - } value='1'> + Idémodul + } value='total'> Totaloversikt - Registrerte idéer - } value='2'> + Registrering + } value='registrering'> Oversikt - - }>Idéer + + }>Mine idéer - {state.ideas.data.items.map((idea, idx) => ( + {state.ideas.data.items.filter((idea) => !idea.processing).map((idea) => ( { setUrlHash({ ideaId: idea.Id.toString() }) getSelectedIdea() @@ -97,17 +101,16 @@ export const IdeaModule: FC = (props) => { - Idéer under behandling - } value='4'> + Behandling + } value='behandling'> Oversikt - - }>Idéer + + }>Mine idéer - {state.ideas.data.items.map((idea, idx) => ( + {state.ideas.data.items.filter((idea) => idea.processing).map((idea) => ( { setUrlHash({ ideaId: idea.Id.toString() }) getSelectedIdea() @@ -128,18 +131,91 @@ export const IdeaModule: FC = (props) => { intent='error' /> )} -
- {state.selectedIdea ? ( - <> + {state.selectedIdea ? ( + <> +
+ + + Registrering av idé + + + + Godkjent for behandling + + + + Behandling av idé + + + + Idé godkjent + + + + Bestill prosjekt + +

{state.selectedIdea.item.Title}

- {state.selectedIdea.fieldValues.map((model, idx) => ( +
+
+ {state.selectedIdea.registeredFieldValues.map((model, idx) => ( ))} - - ) : ( - - )} -
+
+ +
+ {state.selectedIdea.processingFieldValues?.map((model, idx) => ( + + ))} +
+ + ) : ( + + )} {!isOpen && renderHamburgerWithToolTip()}
diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts index 317296932..7a709d59e 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts @@ -1,6 +1,7 @@ import { EditableSPField, ItemFieldValues, SPField } from 'pp365-shared-library' import { IBaseComponentProps } from '../types' import { MessageBarType } from '@fluentui/react' +import { IdeaConfigurationModel } from 'models' export class IdeaModuleErrorMessage extends Error { constructor(public message: string, public type: MessageBarType) { @@ -24,7 +25,7 @@ export interface IIdeaModuleState { refetch?: number isRefetching?: boolean error?: any - configuration?: ConfigurationItem + configuration?: IdeaConfigurationModel ideas?: Idea selectedIdea?: IIdea searchTerm: string @@ -39,15 +40,6 @@ export interface IIdeaModuleHashState { ideaId?: string } -export class ConfigurationItem { - title: string - description: string - ideaProcessingList: string - ideaRegistrationList: string - ideaProcessingChoices: string - ideaRegistrationChoices: string -} - export interface IIdea { /** * The item @@ -55,16 +47,21 @@ export interface IIdea { item: any /** - * The fieldValues for the item + * The fieldValues for the registered fields */ - fieldValues?: EditableSPField[] + registeredFieldValues?: EditableSPField[] + + /** + * The fieldValues for the processing fields + */ + processingFieldValues?: EditableSPField[] } export class Idea { - data: IIdeaData + data: IIdeasData } -export interface IIdeaData { +export interface IIdeasData { /** * The items from the lists containing the field values */ @@ -73,10 +70,16 @@ export interface IIdeaData { /** * The items from the lists containing the field values */ - fieldValues?: ItemFieldValues[] + fieldValues?: { + registered: ItemFieldValues[] + processing: ItemFieldValues[] + } /** * Fields for the list */ - fields?: SPField[] + fields?: { + registered: SPField[] + processing: SPField[] + } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts index 029bb9144..0c66f1bcd 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts @@ -42,13 +42,15 @@ export function useIdeaModule(props: IIdeaModuleProps) { return } + + const obj: IIdeaModuleHashState = {} if (selectedIdea) obj.ideaId = selectedIdea.Id.toString() setUrlHash(obj) - const fieldValues = state.ideas.data.fields + const registeredFieldValues = state.ideas.data.fields.registered .map((field) => { - const fieldValues: ItemFieldValues = state.ideas.data.fieldValues.find( + const fieldValues: ItemFieldValues = state.ideas.data.fieldValues.registered.find( (fv) => fv.id === selectedIdea.Id ) if (!fieldValues) return null @@ -60,16 +62,30 @@ export function useIdeaModule(props: IIdeaModuleProps) { return a.column.sortOrder - b.column.sortOrder }) + const processingFieldValues = selectedIdea.processing && state.ideas.data.fields.processing + .map((field) => { + const fieldValues: ItemFieldValues = state.ideas.data.fieldValues.processing.find( + (fv) => fv.get('GtRegistratedIdeaId')?.value === selectedIdea.Id + ) + if (!fieldValues) return null + return new EditableSPField(field).setValue(fieldValues) + }) + .sort((a, b) => { + if (!a.column) return 1 + if (!b.column) return -1 + return a.column.sortOrder - b.column.sortOrder + }) + setState({ ...state, selectedIdea: { item: selectedIdea, - fieldValues: fieldValues + registeredFieldValues, + processingFieldValues } }) } - useEffect(() => { if (!state.loading) { getSelectedIdea() diff --git a/SharePointFramework/PortfolioWebParts/src/data/index.ts b/SharePointFramework/PortfolioWebParts/src/data/index.ts index bee816320..5f254fa7f 100644 --- a/SharePointFramework/PortfolioWebParts/src/data/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/data/index.ts @@ -45,8 +45,10 @@ import { ChartData, ChartDataItem, DataField, + IdeaConfigurationModel, ProgramItem, - SPChartConfigurationItem + SPChartConfigurationItem, + SPIdeaConfigurationItem } from '../models' import * as config from './config' import { @@ -57,7 +59,7 @@ import { } from './types' import { IPersonaProps, IPersonaSharedProps } from '@fluentui/react' import { IProvisionRequestItem } from 'interfaces/IProvisionRequestItem' -import { ConfigurationItem, Idea } from 'components/IdeaModule' +import { Idea } from 'components/IdeaModule' import { IItem } from '@pnp/sp/items/types' /** @@ -410,7 +412,7 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { const configElement = _.find(timelineConfig, { title: strings.ProjectLabel }) return { data, reports, configElement, columns: configuration.refiners } - } catch (error) {} + } catch (error) { } } /** @@ -1036,41 +1038,22 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { } public async getIdeaConfiguration( - listName: string, - configurationName: string - ): Promise { + listName: string = 'Idékonfigurasjon', + configurationName: string = 'Standard' + ): Promise { try { - const configurationList = this._sp.web.lists.getByTitle(listName) - const spItems = await configurationList.items - .select( - 'Id', - 'Title', - 'GtDescription', - 'GtIdeaProcessingList', - 'GtIdeaRegistrationList', - 'GtIdeaProcessingChoices', - 'GtIdeaRegistrationChoices' - ) - .using(DefaultCaching)() - - const configurationItem = spItems.find((item) => item.Title === configurationName) - - if (!configurationItem) { - throw new Error(`Configuration with name ${configurationName} not found`) - } - - const configuration: ConfigurationItem = { - title: configurationItem.Title, - description: configurationItem.GtDescription, - ideaProcessingList: configurationItem.GtIdeaProcessingList, - ideaRegistrationList: configurationItem.GtIdeaRegistrationList, - ideaProcessingChoices: configurationItem.GtIdeaProcessingChoices, - ideaRegistrationChoices: configurationItem.GtIdeaRegistrationChoices - } - - return configuration + const config = await this._sp.web.lists + .getByTitle(listName) + .select(...new SPIdeaConfigurationItem().fields) + .items() + + return ( + config + .map((item) => new IdeaConfigurationModel(item)) + .find((item) => item.title === configurationName) || new IdeaConfigurationModel(config[0]) + ) } catch (error) { - return null + return error } } @@ -1097,7 +1080,7 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { } } - public async getIdeasData(configuration: ConfigurationItem): Promise { + public async getIdeasData(configuration: IdeaConfigurationModel): Promise { const getListData = async ( listName: string ): Promise<{ items: any[]; fieldValues: ItemFieldValues[]; fields: SPField[] }> => { @@ -1136,16 +1119,41 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { } } - const registrationData = await getListData(configuration.ideaRegistrationList) - const processingData = await getListData(configuration.ideaProcessingList) + const registrationData = await getListData(configuration.registrationList) + const processingData = await getListData(configuration.processingList) + + // For each registrationData.item and registrationData.fieldValues, find the corresponding processingData.item and processingData.fieldValues where the registrationData.item.Id is contained within the processingData.item.GtRegistratedIdeaId + const itemsData = registrationData.items.map((registered) => { + const processing = processingData.items.find( + (processingItem) => processingItem.GtRegistratedIdeaId === registered.Id + ) - console.log({ registrationData, processingData }) + // merge the registered and processing item + return { ...registered, processing } + }) + + const fieldValues = registrationData.fieldValues.map((values) => { + const processingFieldValues = processingData.fieldValues.find( + (processingFieldValues) => processingFieldValues.id === values.id + ) + + // merge the values and processing values + return { ...values, ...processingFieldValues } + }) + + console.log({ itemsData, fieldValues, registrationData, processingData }) return { data: { - items: processingData.items, - fieldValues: processingData.fieldValues, - fields: processingData.fields + items: itemsData, + fieldValues: { + registered: registrationData.fieldValues, + processing: processingData.fieldValues + }, + fields: { + registered: registrationData.fields, + processing: processingData.fields + } } } } diff --git a/SharePointFramework/PortfolioWebParts/src/data/types.ts b/SharePointFramework/PortfolioWebParts/src/data/types.ts index 26ef1bd03..5cc015fc8 100644 --- a/SharePointFramework/PortfolioWebParts/src/data/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/data/types.ts @@ -16,7 +16,8 @@ import { import { IPortfolioAggregationConfiguration, IPortfolioOverviewConfiguration } from '../components' import { IPersonaSharedProps } from '@fluentui/react' import { IProvisionRequestItem } from 'interfaces/IProvisionRequestItem' -import { ConfigurationItem, Idea } from 'components/IdeaModule' +import { Idea } from 'components/IdeaModule' +import { IdeaConfigurationModel } from 'models' export interface IFetchDataForViewItemResult extends ISearchResult { SiteId: string @@ -367,12 +368,15 @@ export interface IPortfolioWebPartsDataAdapter { * * @returns A Promise that resolves to an array containing the configuration. */ - getIdeaConfiguration?(listName: string, configurationName: string): Promise + getIdeaConfiguration?( + listName: string, + configurationName: string + ): Promise /** * Retrieves the data for the ideas from the "Idéregistrering" list * * @returns A Promise that resolves to an object containing the data for the ideas. */ - getIdeasData?(configuration: ConfigurationItem): Promise + getIdeasData?(configuration: IdeaConfigurationModel): Promise } diff --git a/SharePointFramework/PortfolioWebParts/src/models/IdeaConfigurationModel.ts b/SharePointFramework/PortfolioWebParts/src/models/IdeaConfigurationModel.ts new file mode 100644 index 000000000..debdbf10b --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/models/IdeaConfigurationModel.ts @@ -0,0 +1,63 @@ +/* eslint-disable max-classes-per-file */ + +export class SPIdeaConfigurationItem { + public Title: string = '' + public GtDescription: string = null + public GtIdeaProcessingList: string = 'Idébehandling' + public GtIdeaRegistrationList: string = 'Idéregistrering' + public GtIdeaProcessingChoices: string = null + public GtIdeaRegistrationChoices: string = null + public get fields(): string[] { + return Object.keys(this) + } +} + +export class IdeaConfigurationModel { + public title: string + public description: { registration: string; processing: string; projectData: string } + public processingList: string + public registrationList: string + public processing: { key: string; choice: string; recommendation?: string }[] = [] + public registration: { key: string; choice: string; recommendation?: string }[] = [] + + /** + * Creates a new instance of TimelineConfigurationModel + * + * @param item SP item + */ + constructor(item: SPIdeaConfigurationItem) { + this.title = item.Title + this.description = JSON.parse(item.GtDescription) || { + registration: '', + processing: '', + projectData: '' + } + this.processingList = item.GtIdeaProcessingList + this.registrationList = item.GtIdeaRegistrationList + + const processingChoices = JSON.parse(item.GtIdeaProcessingChoices) || {} + const registrationChoices = JSON.parse(item.GtIdeaRegistrationChoices) || {} + + if (processingChoices && typeof processingChoices === 'object') { + this.processing = Object.keys(processingChoices).map((key) => ({ + key, + choice: processingChoices[key]?.choice, + recommendation: processingChoices[key]?.recommendation + })) + } + + if (registrationChoices && typeof registrationChoices === 'object') { + this.registration = Object.keys(registrationChoices).map((key) => ({ + key, + choice: registrationChoices[key]?.choice, + recommendation: registrationChoices[key]?.recommendation + })) + } + } +} + +export enum Choice { + Approve = 'approve', + Consideration = 'consideration', + Reject = 'reject' +} diff --git a/SharePointFramework/PortfolioWebParts/src/models/index.ts b/SharePointFramework/PortfolioWebParts/src/models/index.ts index 1ab2c6cff..c59d49d21 100644 --- a/SharePointFramework/PortfolioWebParts/src/models/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/models/index.ts @@ -8,3 +8,4 @@ export * from './ChartConfiguration' export * from './DataField' export * from './ProgramItem' export * from './IdeaListModel' +export * from './IdeaConfigurationModel' diff --git a/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts b/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts index a596cd7b0..75f33a4c6 100644 --- a/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts @@ -28,7 +28,7 @@ export default class IdeaModuleWebPart extends BasePortfolioWebPart Date: Fri, 8 Nov 2024 16:57:48 +0100 Subject: [PATCH 11/31] Ideamodule - ClientSidePage + Navigation --- .github/workflows/ci-channel-test.yml | 1 + .github/workflows/ci-releases.yml | 1 + Templates/Portfolio/Objects/ClientSidePages/Idemodul.xml | 2 +- Templates/Portfolio/Objects/Navigation.xml | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-channel-test.yml b/.github/workflows/ci-channel-test.yml index c2372faec..11f54f68e 100644 --- a/.github/workflows/ci-channel-test.yml +++ b/.github/workflows/ci-channel-test.yml @@ -4,6 +4,7 @@ on: push: branches: - releases/1.10 + - feat/ideamodule paths: - 'SharePointFramework/**' - 'Install/**' diff --git a/.github/workflows/ci-releases.yml b/.github/workflows/ci-releases.yml index 1a9886caa..eda7221f4 100644 --- a/.github/workflows/ci-releases.yml +++ b/.github/workflows/ci-releases.yml @@ -4,6 +4,7 @@ on: push: branches: - releases/1.10 + - feat/ideamodule paths: - 'SharePointFramework/**' - 'Install/**' diff --git a/Templates/Portfolio/Objects/ClientSidePages/Idemodul.xml b/Templates/Portfolio/Objects/ClientSidePages/Idemodul.xml index 4da37531d..a429d1cf2 100644 --- a/Templates/Portfolio/Objects/ClientSidePages/Idemodul.xml +++ b/Templates/Portfolio/Objects/ClientSidePages/Idemodul.xml @@ -2,7 +2,7 @@ - + diff --git a/Templates/Portfolio/Objects/Navigation.xml b/Templates/Portfolio/Objects/Navigation.xml index 7d589a7d4..b97e76a00 100644 --- a/Templates/Portfolio/Objects/Navigation.xml +++ b/Templates/Portfolio/Objects/Navigation.xml @@ -9,6 +9,7 @@ + \ No newline at end of file From f171f5c45aae5d57c7e70fae30f3321442d5dac6 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Mon, 11 Nov 2024 16:48:47 +0100 Subject: [PATCH 12/31] =?UTF-8?q?Add=20"Id=C3=A9modul"=20as=20category=20a?= =?UTF-8?q?nd=20add=20elements=20to=20"Prosjektinnholdskolonner"=20for=20i?= =?UTF-8?q?deas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Portfolio/Objects/ContentTypes/@.xml | 1 + .../Objects/ContentTypes/Idea/Idekolonne.xml | 18 ++ .../Lists/Prosjektinnholdskolonner.xml | 251 ++++++++++++++++++ Templates/Portfolio/Objects/SiteFields/@.xml | 1 + .../DataSource/GtDataSourceCategory.xml | 1 + .../SiteFields/Idea/GtIdeaCopyToProcess.xml | 1 + Templates/Portfolio/Resources.en-US.resx | 15 ++ Templates/Portfolio/Resources.no-NB.resx | 15 ++ 8 files changed, 303 insertions(+) create mode 100644 Templates/Portfolio/Objects/ContentTypes/Idea/Idekolonne.xml create mode 100644 Templates/Portfolio/Objects/SiteFields/Idea/GtIdeaCopyToProcess.xml diff --git a/Templates/Portfolio/Objects/ContentTypes/@.xml b/Templates/Portfolio/Objects/ContentTypes/@.xml index fb81fbf0a..3ca3ed2e3 100644 --- a/Templates/Portfolio/Objects/ContentTypes/@.xml +++ b/Templates/Portfolio/Objects/ContentTypes/@.xml @@ -1,5 +1,6 @@ + diff --git a/Templates/Portfolio/Objects/ContentTypes/Idea/Idekolonne.xml b/Templates/Portfolio/Objects/ContentTypes/Idea/Idekolonne.xml new file mode 100644 index 000000000..20cc34132 --- /dev/null +++ b/Templates/Portfolio/Objects/ContentTypes/Idea/Idekolonne.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + diff --git a/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml b/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml index 3d4068038..9d927ec3e 100644 --- a/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml +++ b/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml @@ -2,6 +2,7 @@ xmlns:pnp="http://schemas.dev.office.com/PnP/2021/03/ProvisioningSchema"> + @@ -49,6 +50,29 @@ clienttemplates.js + + + + + + + + + + + + + + + + + + + + 100 + + clienttemplates.js + @@ -739,6 +763,233 @@ 200 300 + + 1000 + Bakgrunn + GtIdeaBackground + GtIdeaBackgroundOWSMTXT + Note + Idémodul + 200 + 300 + True + + + 1010 + Problemstilling + GtIdeaIssue + GtIdeaIssueOWSMTXT + Note + Idémodul + 200 + 300 + True + + + 1020 + Mulige gevinster + GtIdeaPossibleGains + GtIdeaPossibleGainsOWSMTXT + Note + Idémodul + 200 + 300 + True + + + 1030 + Berørte parter + GtIdeaAffectedParties + GtIdeaAffectedPartiesOWSTEXT + Text + Idémodul + 120 + 200 + True + True + + + 1040 + Løsningsforslag + GtIdeaSolutionProposals + GtIdeaSolutionProposalsOWSMTXT + Note + Idémodul + 200 + 300 + True + + + 1050 + Løsningsforslag + GtIdeaExecutionPlan + GtIdeaExecutionPlanOWSMTXT + Note + Idémodul + 200 + 300 + True + + + 1060 + Kritiske suksessfaktorer + GtIdeaCriticalSuccessFactors + GtIdeaCriticalSuccessFactorsOWSMTXT + Note + Idémodul + 200 + 300 + True + + + 1070 + Ressursbehov + GtIdeaResourceRequirements + GtIdeaResourceRequirementsOWSMTXT + Note + Idémodul + 200 + 300 + True + + + 1080 + Andre kommentarer + GtIdeaOtherComments + GtIdeaOtherCommentsOWSMTXT + Note + Idémodul + 200 + 300 + True + + + 1090 + Innmelder + GtIdeaReporter + GtIdeaReporter + User + Idémodul + 100 + 200 + True + True + + + 1100 + Kommentar til anbefaling + GtIdeaRecommendationComment + GtIdeaRecommendationCommentOWSMTXT + Note + Idémodul + 200 + 300 + True + + + 1110 + Anbefaling + GtIdeaRecommendation + GtIdeaRecommendationOWSCHCS + Tags + Idémodul + 100 + 200 + True + True + + + 1120 + Idéeier + GtIdeaOwner + GtIdeaOwner + User + Idémodul + 100 + 200 + True + + + 1130 + Overordnet løsningsbeskrivelse + GtIdeaSolutionDescription + GtIdeaSolutionDescriptionOWSMTXT + Note + Idémodul + 200 + 300 + + + 1140 + Forventede gevinster + GtIdeaExpectedGain + GtIdeaExpectedGainOWSMTXT + Note + Idémodul + 200 + 300 + + + 1150 + Gjennomføringsplan: Tilnærming + GtIdeaExecutionPlanApproach + GtIdeaExecutionPlanApproachOWSMTXT + Note + Idémodul + 200 + 300 + + + 1160 + Gjennomføringsplan: Ressursbehov + GtIdeaExecutionResourceNeeds + GtIdeaExecutionResourceNeedsOWSMTXT + Note + Idémodul + 200 + 300 + + + 1170 + Gjennomføringsplan: Suksessfaktorer + GtIdeaExecutionSuccessFactors + GtIdeaExecutionSuccessFactorsOWSMTXT + Note + Idémodul + 200 + 300 + + + 1180 + Referanse + GtIdeaReference + GtIdeaReferenceOWSMTXT + Note + Idémodul + 200 + 300 + + + 1190 + Beslutning + GtIdeaDecision + GtIdeaDecisionOWSCHCS + Tags + Idémodul + 100 + 200 + True + + + 1210 + Kommentar til beslutning + GtIdeaStrategicValue + GtIdeaDecisionCommentOWSMTXT + Note + Idémodul + 200 + 300 + \ No newline at end of file diff --git a/Templates/Portfolio/Objects/SiteFields/@.xml b/Templates/Portfolio/Objects/SiteFields/@.xml index f00c6f55f..c1f85b1a9 100644 --- a/Templates/Portfolio/Objects/SiteFields/@.xml +++ b/Templates/Portfolio/Objects/SiteFields/@.xml @@ -1,6 +1,7 @@ + diff --git a/Templates/Portfolio/Objects/SiteFields/DataSource/GtDataSourceCategory.xml b/Templates/Portfolio/Objects/SiteFields/DataSource/GtDataSourceCategory.xml index a6968fb8b..fc20ff3b4 100644 --- a/Templates/Portfolio/Objects/SiteFields/DataSource/GtDataSourceCategory.xml +++ b/Templates/Portfolio/Objects/SiteFields/DataSource/GtDataSourceCategory.xml @@ -9,5 +9,6 @@ Ressursallokering Prosjekter Erfaringslogg + Idémodul \ No newline at end of file diff --git a/Templates/Portfolio/Objects/SiteFields/Idea/GtIdeaCopyToProcess.xml b/Templates/Portfolio/Objects/SiteFields/Idea/GtIdeaCopyToProcess.xml new file mode 100644 index 000000000..dc014a2eb --- /dev/null +++ b/Templates/Portfolio/Objects/SiteFields/Idea/GtIdeaCopyToProcess.xml @@ -0,0 +1 @@ +1 diff --git a/Templates/Portfolio/Resources.en-US.resx b/Templates/Portfolio/Resources.en-US.resx index 1d9e67f4e..03e5737ca 100644 --- a/Templates/Portfolio/Resources.en-US.resx +++ b/Templates/Portfolio/Resources.en-US.resx @@ -334,6 +334,9 @@ Card view + + Idémodul + Project Portal content types @@ -543,6 +546,12 @@ Content type for idea configuration (Idea Processing) + + + Idekolonne + + + Innholdstype for idekolonne (kolonnekonfigurasjon) Hjelpeinnhold @@ -2138,6 +2147,12 @@ Recommendations are configured as JSON, multiple choices can be added, note that the key must remain the same for the default choices + + Kopier til behandling + + + Verdien skal kopieres til behandling (kolonne i behandling må matche på visningsnavn eller internt navn, og type.) + Original handover date diff --git a/Templates/Portfolio/Resources.no-NB.resx b/Templates/Portfolio/Resources.no-NB.resx index 9895c1b80..3760b83b6 100644 --- a/Templates/Portfolio/Resources.no-NB.resx +++ b/Templates/Portfolio/Resources.no-NB.resx @@ -346,6 +346,9 @@ Kortvisning + + Idémodul + Prosjektportalen innholdstyper @@ -595,6 +598,12 @@ Innholdstype for idékonfigurasjon (Idébehandling) + + Idekolonne + + + Innholdstype for idekolonne (kolonnekonfigurasjon) + Hjelpeinnhold @@ -2281,6 +2290,12 @@ Anbefalingsvalg settes opp med JSON, her kan flere valg legges til, merk at nøkkel (key) må forbli lik for standardvalgene + + Kopier til behandling + + + Verdien skal kopieres til behandling (kolonne i behandling må matche på visningsnavn eller internt navn, og type.) + Opprinnelig overtakelsesdato From 524885be9e2c061d54b3f23bc0da8275889fadf6 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Tue, 12 Nov 2024 08:43:08 +0100 Subject: [PATCH 13/31] Fix IdeaView for "Prosjektinnholdskolonner" --- .../Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml b/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml index 9d927ec3e..355b2855f 100644 --- a/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml +++ b/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml @@ -60,12 +60,10 @@ - - + - From 5336512c6b8557198e2c3fe5a551fdb1e36a2428 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Tue, 12 Nov 2024 10:06:38 +0100 Subject: [PATCH 14/31] Use "Prosjektinnholdskolonner" for fieldconfig + hideFields functionality [packages-only] --- .../src/components/IdeaModule/IdeaModule.tsx | 76 ++++++++++++------- .../src/components/IdeaModule/types.ts | 14 +++- .../components/IdeaModule/useIdeaModule.ts | 12 +-- .../PortfolioWebParts/src/data/index.ts | 16 +++- .../src/webparts/ideaModule/index.ts | 14 ++++ .../src/models/EditableSPField.ts | 5 +- 6 files changed, 99 insertions(+), 38 deletions(-) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index 1b190dfde..992cc5872 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -87,17 +87,20 @@ export const IdeaModule: FC = (props) => { }>Mine idéer - {state.ideas.data.items.filter((idea) => !idea.processing).map((idea) => ( - { - setUrlHash({ ideaId: idea.Id.toString() }) - getSelectedIdea() - }} - > - {idea?.Title} - - ))} + {state.ideas.data.items + .filter((idea) => !idea.processing) + .map((idea) => ( + { + setUrlHash({ ideaId: idea.Id.toString() }) + getSelectedIdea() + }} + > + {idea?.Title} + + ))} @@ -108,17 +111,20 @@ export const IdeaModule: FC = (props) => { }>Mine idéer - {state.ideas.data.items.filter((idea) => idea.processing).map((idea) => ( - { - setUrlHash({ ideaId: idea.Id.toString() }) - getSelectedIdea() - }} - > - {idea?.Title} - - ))} + {state.ideas.data.items + .filter((idea) => idea.processing) + .map((idea) => ( + { + setUrlHash({ ideaId: idea.Id.toString() }) + getSelectedIdea() + }} + > + {idea?.Title} + + ))} @@ -229,9 +235,25 @@ export const IdeaModule: FC = (props) => { IdeaModule.defaultProps = { configurationList: 'Idékonfigurasjon', configuration: 'Standard', - sortBy: 'Title', - showSearchBox: true, - showRenderModeSelector: true, - showSortBy: true, - defaultRenderMode: 'tiles' + hiddenRegFields: ['Title'], + hiddenProcFields: [ + 'Title', + 'GtIdeaUrl', + 'GtRegistratedIdea', + 'GtIdeaProjectData', + 'GtIdeaStrategicValue', + 'GtIdeaStrategicNumber', + 'GtIdeaQualityBenefit', + 'GtIdeaQualityNumber', + 'GtIdeaEconomicBenefit', + 'GtIdeaEconomicNumber', + 'GtIdeaOperationalNeed', + 'GtIdeaOperationalNumber', + 'GtIdeaRisk', + 'GtIdeaRiskNumber', + 'GtIdeaManualScore', + 'GtIdeaManualComment', + 'GtIdeaScore', + 'GtIdeaPriority' + ] } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts index 7a709d59e..ea6339121 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts @@ -1,4 +1,9 @@ -import { EditableSPField, ItemFieldValues, SPField } from 'pp365-shared-library' +import { + EditableSPField, + ItemFieldValues, + ProjectContentColumn, + SPField +} from 'pp365-shared-library' import { IBaseComponentProps } from '../types' import { MessageBarType } from '@fluentui/react' import { IdeaConfigurationModel } from 'models' @@ -18,6 +23,8 @@ export interface IIdeaModuleProps extends IBaseComponentProps { showSortBy?: boolean defaultRenderMode?: IdeaListRenderMode listSize?: 'extra-small' | 'small' | 'medium' + hiddenRegFields?: string[] + hiddenProcFields?: string[] } export interface IIdeaModuleState { @@ -82,4 +89,9 @@ export interface IIdeasData { registered: SPField[] processing: SPField[] } + + /** + * Columns from "Prosjektinnholdskolonner" list + */ + columns: ProjectContentColumn[] } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts index 0c66f1bcd..a8dd51763 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts @@ -42,19 +42,18 @@ export function useIdeaModule(props: IIdeaModuleProps) { return } - - const obj: IIdeaModuleHashState = {} if (selectedIdea) obj.ideaId = selectedIdea.Id.toString() setUrlHash(obj) const registeredFieldValues = state.ideas.data.fields.registered + .filter((field) => !props.hiddenRegFields?.includes(field.InternalName)) .map((field) => { const fieldValues: ItemFieldValues = state.ideas.data.fieldValues.registered.find( (fv) => fv.id === selectedIdea.Id ) if (!fieldValues) return null - return new EditableSPField(field).setValue(fieldValues) + return new EditableSPField(field).init(state.ideas.data.columns).setValue(fieldValues) }) .sort((a, b) => { if (!a.column) return 1 @@ -62,14 +61,17 @@ export function useIdeaModule(props: IIdeaModuleProps) { return a.column.sortOrder - b.column.sortOrder }) - const processingFieldValues = selectedIdea.processing && state.ideas.data.fields.processing + const processingFieldValues = + selectedIdea.processing && + state.ideas.data.fields.processing .map((field) => { const fieldValues: ItemFieldValues = state.ideas.data.fieldValues.processing.find( (fv) => fv.get('GtRegistratedIdeaId')?.value === selectedIdea.Id ) if (!fieldValues) return null - return new EditableSPField(field).setValue(fieldValues) + return new EditableSPField(field).init(state.ideas.data.columns).setValue(fieldValues) }) + .filter((field) => !props.hiddenProcFields?.includes(field.InternalName)) .sort((a, b) => { if (!a.column) return 1 if (!b.column) return -1 diff --git a/SharePointFramework/PortfolioWebParts/src/data/index.ts b/SharePointFramework/PortfolioWebParts/src/data/index.ts index 5f254fa7f..c024ca8ba 100644 --- a/SharePointFramework/PortfolioWebParts/src/data/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/data/index.ts @@ -412,7 +412,7 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { const configElement = _.find(timelineConfig, { title: strings.ProjectLabel }) return { data, reports, configElement, columns: configuration.refiners } - } catch (error) { } + } catch (error) {} } /** @@ -1091,7 +1091,7 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { const fields = await list.fields .select(...getClassProperties(SPField)) .filter( - 'substringof(\'Gt\', InternalName) or InternalName eq \'Title\' or InternalName eq \'Id\'' + "substringof('Gt', InternalName) or InternalName eq 'Title' or InternalName eq 'Id'" )() const userFields = fields @@ -1119,6 +1119,15 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { } } + const level = strings.DataSourceLevelPortfolio + + const columns: ProjectContentColumn[] = await new Promise((resolve, reject) => { + this.portalDataService + .fetchProjectContentColumns('PROJECT_CONTENT_COLUMNS', 'Idémodul', level) + .then(resolve) + .catch(reject) + }) + const registrationData = await getListData(configuration.registrationList) const processingData = await getListData(configuration.processingList) @@ -1153,7 +1162,8 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { fields: { registered: registrationData.fields, processing: processingData.fields - } + }, + columns } } } diff --git a/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts b/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts index 75f33a4c6..f45b3a648 100644 --- a/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts @@ -31,6 +31,20 @@ export default class IdeaModuleWebPart extends BasePortfolioWebPart c.internalName === this.internalName) + public init(columns: (ProjectColumn | ProjectContentColumn)[], currentLocale?: string, configurationName?: string) { + this.column = columns.find((c) => c.internalName === this.internalName) as ProjectColumn this._isExternal = _.isEmpty(columns) this.displayName = this.column?.name ?? this.displayName this._initConfiguration(currentLocale, configurationName) From 8b8b3362013ac065e380b4d8f6fe22d8bfbc5faf Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Tue, 12 Nov 2024 15:39:35 +0100 Subject: [PATCH 15/31] Add dynamic ideaPhaseBar + restructuring of components --- .../IdeaModule/IdeaField/useIdeaField.tsx | 5 +- .../IdeaModule/IdeaModule.module.scss | 23 ++- .../src/components/IdeaModule/IdeaModule.tsx | 190 +----------------- .../components/IdeaModule/IdeaNav/IdeaNav.tsx | 106 ++++++++++ .../components/IdeaModule/IdeaNav/index.ts | 1 + .../IdeaPhaseBar/IdeaPhaseBar.module.scss | 6 + .../IdeaModule/IdeaPhaseBar/IdeaPhaseBar.tsx | 77 +++++++ .../IdeaModule/IdeaPhaseBar/index.ts | 1 + .../src/components/IdeaModule/types.ts | 15 ++ .../{useIdeaModule.ts => useIdeaModule.tsx} | 47 ++++- .../ProjectProvision/ProjectProvision.tsx | 14 +- .../src/components/ProjectProvision/types.ts | 5 + .../src/util/createFieldValueMap.ts | 4 +- .../Lists/Prosjektinnholdskolonner.xml | 40 ++-- 14 files changed, 318 insertions(+), 216 deletions(-) create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaNav/IdeaNav.tsx create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaNav/index.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/IdeaPhaseBar.module.scss create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/IdeaPhaseBar.tsx create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/index.ts rename SharePointFramework/PortfolioWebParts/src/components/IdeaModule/{useIdeaModule.ts => useIdeaModule.tsx} (71%) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx index 3e6ab01e4..9a8a30d48 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx @@ -28,7 +28,10 @@ export function useIdeaField(props: IIdeaFieldProps) { const renderValueForField = () => { let icon = TagMultipleFilled - if (props.model.internalName === 'GtIdeaRecommendation') { + if ( + props.model.internalName === 'GtIdeaRecommendation' || + props.model.internalName === 'GtIdeaDecision' + ) { props.model.type = 'TaxonomyFieldType' } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss index 56f72be66..32a4265b3 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss @@ -2,25 +2,33 @@ margin: 0; overflow: hidden; display: flex; - height: 100%; + height: 100vh; .content { flex: 1; - padding: 16px; + padding: 32px; gap: 16px; display: grid; justify-content: flex-start; align-items: flex-start; + height: fit-content; .ideaHeader { display: flex; flex-direction: column; gap: 16px; - .ideaPhases { + .toolbar { display: flex; - flex-direction: row; + flex-direction: column; justify-content: center; + align-items: center; + gap: 16px; + + .hamburger { + position: absolute; + left: 32px; + } } .ideaTitle { @@ -39,6 +47,13 @@ .ideaField { margin: 0; } + + .status { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 8px; + } } } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index 992cc5872..9d710cced 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -3,56 +3,14 @@ import styles from './IdeaModule.module.scss' import { IdeaModuleContext } from './context' import { IIdeaModuleProps } from './types' import { useIdeaModule } from './useIdeaModule' -import { - Divider, - FluentProvider, - IdPrefixProvider, - Spinner, - Tab, - TabList, - Tooltip -} from '@fluentui/react-components' -import { customLightTheme, getFluentIcon, setUrlHash, UserMessage } from 'pp365-shared-library' -import { - Hamburger, - NavCategory, - NavCategoryItem, - NavDrawer, - NavDrawerBody, - NavDrawerHeader, - NavItem, - NavSectionHeader, - NavSubItem, - NavSubItemGroup, - NavDivider, - AppItemStatic -} from '@fluentui/react-nav-preview' -import { - Board20Filled, - Board20Regular, - NotePin20Filled, - NotePin20Regular, - Lightbulb20Filled, - Lightbulb20Regular, - bundleIcon -} from '@fluentui/react-icons' +import { Divider, FluentProvider, IdPrefixProvider, Spinner } from '@fluentui/react-components' +import { customLightTheme, UserMessage } from 'pp365-shared-library' import { IdeaField } from './IdeaField' - -const Dashboard = bundleIcon(Board20Filled, Board20Regular) -const Lightbulb = bundleIcon(Lightbulb20Filled, Lightbulb20Regular) -const JobPostings = bundleIcon(NotePin20Filled, NotePin20Regular) +import { IdeaPhaseBar } from './IdeaPhaseBar' +import { IdeaNav } from './IdeaNav' export const IdeaModule: FC = (props) => { - const { state, setState, getSelectedIdea, fluentProviderId } = useIdeaModule(props) - const [isOpen, setIsOpen] = React.useState(true) - - const renderHamburgerWithToolTip = () => { - return ( - - setIsOpen(!isOpen)} /> - - ) - } + const { state, setState, isOpen, renderHamburger, fluentProviderId } = useIdeaModule(props) return ( @@ -62,73 +20,7 @@ export const IdeaModule: FC = (props) => { ) : (
- - - - {renderHamburgerWithToolTip()} - - - - Idémodul - } value='total'> - Totaloversikt - - Registrering - } value='registrering'> - Oversikt - - - }>Mine idéer - - {state.ideas.data.items - .filter((idea) => !idea.processing) - .map((idea) => ( - { - setUrlHash({ ideaId: idea.Id.toString() }) - getSelectedIdea() - }} - > - {idea?.Title} - - ))} - - - - Behandling - } value='behandling'> - Oversikt - - - }>Mine idéer - - {state.ideas.data.items - .filter((idea) => idea.processing) - .map((idea) => ( - { - setUrlHash({ ideaId: idea.Id.toString() }) - getSelectedIdea() - }} - > - {idea?.Title} - - ))} - - - - +
{state.error && ( = (props) => { {state.selectedIdea ? ( <>
- - - Registrering av idé - - - - Godkjent for behandling - - - - Behandling av idé - - - - Idé godkjent - - - - Bestill prosjekt - - +
+
{!isOpen && renderHamburger()}
+ +

{state.selectedIdea.item.Title}

@@ -222,7 +53,6 @@ export const IdeaModule: FC = (props) => { ) : ( )} - {!isOpen && renderHamburgerWithToolTip()}
)} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaNav/IdeaNav.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaNav/IdeaNav.tsx new file mode 100644 index 000000000..e293108d6 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaNav/IdeaNav.tsx @@ -0,0 +1,106 @@ +import React, { FC } from 'react' +import { + NavCategory, + NavCategoryItem, + NavDrawer, + NavDrawerBody, + NavDrawerHeader, + NavItem, + NavSectionHeader, + NavSubItem, + NavSubItemGroup, + NavDivider, + AppItemStatic +} from '@fluentui/react-nav-preview' +import { + Board20Filled, + Board20Regular, + NotePin20Filled, + NotePin20Regular, + Lightbulb20Filled, + Lightbulb20Regular, + bundleIcon +} from '@fluentui/react-icons' +import { useIdeaModuleContext } from '../context' +import { Tooltip } from '@fluentui/react-components' +import { getFluentIcon, setUrlHash } from 'pp365-shared-library' +import { useIdeaModule } from '../useIdeaModule' + +const Dashboard = bundleIcon(Board20Filled, Board20Regular) +const Lightbulb = bundleIcon(Lightbulb20Filled, Lightbulb20Regular) +const JobPostings = bundleIcon(NotePin20Filled, NotePin20Regular) + +export const IdeaNav: FC = () => { + const context = useIdeaModuleContext() + const { getSelectedIdea, isOpen, renderHamburger } = useIdeaModule() + + return ( + + + + {renderHamburger()} + + + + Idémodul + } value='total'> + Totaloversikt + + Registrering + } value='registrering'> + Oversikt + + + }>Mine idéer + + {context.state.ideas.data.items + .filter((idea) => !idea.processing) + .map((idea) => ( + { + setUrlHash({ ideaId: idea.Id.toString() }) + getSelectedIdea() + }} + > + {idea?.Title} + + ))} + + + + Behandling + } value='behandling'> + Oversikt + + + }>Mine idéer + + {context.state.ideas.data.items + .filter((idea) => idea.processing) + .map((idea) => ( + { + setUrlHash({ ideaId: idea.Id.toString() }) + getSelectedIdea() + }} + > + {idea?.Title} + + ))} + + + + + ) +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaNav/index.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaNav/index.ts new file mode 100644 index 000000000..767364809 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaNav/index.ts @@ -0,0 +1 @@ +export * from './IdeaNav' diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/IdeaPhaseBar.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/IdeaPhaseBar.module.scss new file mode 100644 index 000000000..98f8292ca --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/IdeaPhaseBar.module.scss @@ -0,0 +1,6 @@ +.ideaPhases { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/IdeaPhaseBar.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/IdeaPhaseBar.tsx new file mode 100644 index 000000000..31ac5eac1 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/IdeaPhaseBar.tsx @@ -0,0 +1,77 @@ +import React, { FC } from 'react' +import styles from './IdeaPhaseBar.module.scss' +import { Tab, TabList } from '@fluentui/react-components' +import { getFluentIcon } from 'pp365-shared-library' +import { useIdeaModuleContext } from '../context' +import { IdeaPhase } from '../types' +import { ProjectProvision } from 'components/ProjectProvision' + +export const IdeaPhaseBar: FC = () => { + const context = useIdeaModuleContext() + + const phases = [ + { + phase: IdeaPhase.Registration, + name: 'Registrering av idé', + icon: getFluentIcon('Lightbulb') + }, + { + phase: IdeaPhase.Processing, + name: 'Behandling av idé', + icon: getFluentIcon('Edit') + }, + { + phase: IdeaPhase.ApprovedForConcept, + name: 'Idé godkjent', + icon: getFluentIcon('CheckmarkCircle') + }, + { + phase: IdeaPhase.Provisioned, + name: 'Bestill område', + icon: getFluentIcon('BoxToolbox') + } + ] + return ( + <> + + {phases.map((phase) => { + if (phase.phase === IdeaPhase.Provisioned) { + return ( + + ) + } + + return ( + <> + + {phase.name} + + + + ) + })} + + + ) +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/index.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/index.ts new file mode 100644 index 000000000..b7140167b --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/index.ts @@ -0,0 +1 @@ +export * from './IdeaPhaseBar' diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts index ea6339121..1ac0007e3 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts @@ -7,6 +7,7 @@ import { import { IBaseComponentProps } from '../types' import { MessageBarType } from '@fluentui/react' import { IdeaConfigurationModel } from 'models' +import { Slot } from '@fluentui/react-components' export class IdeaModuleErrorMessage extends Error { constructor(public message: string, public type: MessageBarType) { @@ -35,6 +36,7 @@ export interface IIdeaModuleState { configuration?: IdeaConfigurationModel ideas?: Idea selectedIdea?: IIdea + phase?: IdeaPhase searchTerm: string renderMode?: IdeaListRenderMode isUserInIdeaManagerGroup?: boolean @@ -95,3 +97,16 @@ export interface IIdeasData { */ columns: ProjectContentColumn[] } + +export interface IIdeaPhase { + phase: IdeaPhase + name: string + icon?: Slot<'span'> +} + +export enum IdeaPhase { + Registration, + Processing, + ApprovedForConcept, + Provisioned +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.tsx similarity index 71% rename from SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts rename to SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.tsx index a8dd51763..da319dfa1 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.tsx @@ -1,23 +1,34 @@ /* eslint-disable no-console */ - -import { useId } from '@fluentui/react-components' -import { IIdeaModuleHashState, IIdeaModuleProps } from './types' +import React from 'react' +import { Tooltip, useId } from '@fluentui/react-components' +import { IdeaPhase, IIdeaModuleHashState, IIdeaModuleProps } from './types' import { useIdeaModuleState } from './useIdeaModuleState' import { useIdeaModuleDataFetch } from './useIdeaModuleDataFetch' import { EditableSPField, ItemFieldValues, parseUrlHash, setUrlHash } from 'pp365-shared-library' import _ from 'lodash' -import { useEffect } from 'react' +import { useEffect, useState } from 'react' +import { Hamburger } from '@fluentui/react-nav-preview' /** * Component logic hook for `IdeaModule` component. * clear */ -export function useIdeaModule(props: IIdeaModuleProps) { +export function useIdeaModule(props?: IIdeaModuleProps) { const { state, setState } = useIdeaModuleState(props) const fluentProviderId = useId('fp-idea-module') useIdeaModuleDataFetch(props, state.refetch, setState) + const [isOpen, setIsOpen] = useState(true) + + const renderHamburger = () => { + return ( + + setIsOpen(!isOpen)} /> + + ) + } + const getSelectedIdea = () => { const hashState = parseUrlHash() @@ -78,13 +89,35 @@ export function useIdeaModule(props: IIdeaModuleProps) { return a.column.sortOrder - b.column.sortOrder }) + let ideaPhase: IdeaPhase + if (selectedIdea.processing) { + if ( + selectedIdea.processing.GtIdeaDecision === + state.configuration.processing.find((p) => p.key === 'approve')?.recommendation + ) { + ideaPhase = IdeaPhase.ApprovedForConcept + } else { + ideaPhase = IdeaPhase.Processing + } + } else { + if ( + selectedIdea.GtIdeaRecommendation === + state.configuration.registration.find((p) => p.key === 'approve')?.recommendation + ) { + ideaPhase = IdeaPhase.Processing + } else { + ideaPhase = IdeaPhase.Registration + } + } + setState({ ...state, selectedIdea: { item: selectedIdea, registeredFieldValues, processingFieldValues - } + }, + phase: ideaPhase }) } @@ -98,6 +131,8 @@ export function useIdeaModule(props: IIdeaModuleProps) { state, setState, getSelectedIdea, + renderHamburger, + isOpen, fluentProviderId } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectProvision/ProjectProvision.tsx b/SharePointFramework/PortfolioWebParts/src/components/ProjectProvision/ProjectProvision.tsx index 4365cc16a..462aaed96 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectProvision/ProjectProvision.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectProvision/ProjectProvision.tsx @@ -51,9 +51,10 @@ export const ProjectProvision: FC = (props) => { primaryActionButton={{ onClick: () => setState({ showProvisionDrawer: true }) }} - icon={getFluentIcon('Add')} - appearance='primary' - size='large' + icon={props.icon} + appearance={props.appearance} + size={props.size} + disabled={props.disabled} > {strings.Provision.ProvisionButtonLabel} @@ -88,3 +89,10 @@ export const ProjectProvision: FC = (props) => { ) } + +ProjectProvision.defaultProps = { + disabled: false, + icon: getFluentIcon('Add'), + appearance: 'primary', + size: 'large' +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectProvision/types.ts b/SharePointFramework/PortfolioWebParts/src/components/ProjectProvision/types.ts index 2443c10bf..ac9054484 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectProvision/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectProvision/types.ts @@ -1,7 +1,12 @@ +import { Slot } from '@fluentui/react-components' import { IBaseComponentProps } from 'components/types' export interface IProjectProvisionProps extends IBaseComponentProps { provisionUrl: string + disabled?: boolean + icon?: Slot<'span'> + appearance?: 'secondary' | 'primary' | 'outline' | 'subtle' | 'transparent' + size?: 'small' | 'medium' | 'large' } export interface IProjectProvisionState { diff --git a/SharePointFramework/shared-library/src/util/createFieldValueMap.ts b/SharePointFramework/shared-library/src/util/createFieldValueMap.ts index ea3aa2f6a..e415409f4 100644 --- a/SharePointFramework/shared-library/src/util/createFieldValueMap.ts +++ b/SharePointFramework/shared-library/src/util/createFieldValueMap.ts @@ -21,7 +21,7 @@ export const createFieldValueMap = (): Map value.split(';').map((v) => ({ key: v, name: v }))], + ['TaxonomyFieldType', ({ value }) => value?.split(';').map((v) => ({ key: v, name: v }))], [ 'TaxonomyFieldTypeMulti', ({ $ }) => @@ -32,7 +32,7 @@ export const createFieldValueMap = (): Map new Date($)], ['DateTime', ({ $ }) => new Date($)], - ['MultiChoice', ({ value }) => value.split(', ')], + ['MultiChoice', ({ value }) => value?.split(', ')], [ 'User', ({ $ }) => diff --git a/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml b/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml index 355b2855f..63cd5f7b4 100644 --- a/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml +++ b/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml @@ -852,17 +852,6 @@ 1080 - Andre kommentarer - GtIdeaOtherComments - GtIdeaOtherCommentsOWSMTXT - Note - Idémodul - 200 - 300 - True - - - 1090 Innmelder GtIdeaReporter GtIdeaReporter @@ -874,7 +863,7 @@ True - 1100 + 1090 Kommentar til anbefaling GtIdeaRecommendationComment GtIdeaRecommendationCommentOWSMTXT @@ -885,7 +874,7 @@ True - 1110 + 1100 Anbefaling GtIdeaRecommendation GtIdeaRecommendationOWSCHCS @@ -897,7 +886,7 @@ True - 1120 + 1110 Idéeier GtIdeaOwner GtIdeaOwner @@ -908,7 +897,7 @@ True - 1130 + 1120 Overordnet løsningsbeskrivelse GtIdeaSolutionDescription GtIdeaSolutionDescriptionOWSMTXT @@ -918,7 +907,7 @@ 300 - 1140 + 1130 Forventede gevinster GtIdeaExpectedGain GtIdeaExpectedGainOWSMTXT @@ -928,7 +917,7 @@ 300 - 1150 + 1140 Gjennomføringsplan: Tilnærming GtIdeaExecutionPlanApproach GtIdeaExecutionPlanApproachOWSMTXT @@ -938,7 +927,7 @@ 300 - 1160 + 1150 Gjennomføringsplan: Ressursbehov GtIdeaExecutionResourceNeeds GtIdeaExecutionResourceNeedsOWSMTXT @@ -948,7 +937,7 @@ 300 - 1170 + 1160 Gjennomføringsplan: Suksessfaktorer GtIdeaExecutionSuccessFactors GtIdeaExecutionSuccessFactorsOWSMTXT @@ -958,7 +947,7 @@ 300 - 1180 + 1170 Referanse GtIdeaReference GtIdeaReferenceOWSMTXT @@ -967,6 +956,17 @@ 200 300 + + 1180 + Andre kommentarer + GtIdeaOtherComments + GtIdeaOtherCommentsOWSMTXT + Note + Idémodul + 200 + 300 + True + 1190 Beslutning From ba55aeaa8ac170943128e4581fd30c8fbe480776 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Tue, 12 Nov 2024 16:09:08 +0100 Subject: [PATCH 16/31] Fix selectedValue for NavDrawer [packages-only] --- .github/workflows/ci-releases.yml | 1 - .../IdeaModule/IdeaModule.module.scss | 2 +- .../src/components/IdeaModule/IdeaModule.tsx | 108 +++++++++++++++++- .../components/IdeaModule/IdeaNav/IdeaNav.tsx | 106 ----------------- .../components/IdeaModule/IdeaNav/index.ts | 1 - .../components/IdeaModule/useIdeaModule.tsx | 2 +- 6 files changed, 105 insertions(+), 115 deletions(-) delete mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaNav/IdeaNav.tsx delete mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaNav/index.ts diff --git a/.github/workflows/ci-releases.yml b/.github/workflows/ci-releases.yml index eda7221f4..1a9886caa 100644 --- a/.github/workflows/ci-releases.yml +++ b/.github/workflows/ci-releases.yml @@ -4,7 +4,6 @@ on: push: branches: - releases/1.10 - - feat/ideamodule paths: - 'SharePointFramework/**' - 'Install/**' diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss index 32a4265b3..5c4e067b1 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss @@ -6,7 +6,7 @@ .content { flex: 1; - padding: 32px; + padding: 16px 32px; gap: 16px; display: grid; justify-content: flex-start; diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index 9d710cced..f91ac9d65 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -3,14 +3,46 @@ import styles from './IdeaModule.module.scss' import { IdeaModuleContext } from './context' import { IIdeaModuleProps } from './types' import { useIdeaModule } from './useIdeaModule' -import { Divider, FluentProvider, IdPrefixProvider, Spinner } from '@fluentui/react-components' -import { customLightTheme, UserMessage } from 'pp365-shared-library' +import { + Divider, + FluentProvider, + IdPrefixProvider, + Spinner, + Tooltip +} from '@fluentui/react-components' +import { customLightTheme, getFluentIcon, setUrlHash, UserMessage } from 'pp365-shared-library' +import { + NavCategory, + NavCategoryItem, + NavDrawer, + NavDrawerBody, + NavDrawerHeader, + NavItem, + NavSectionHeader, + NavSubItem, + NavSubItemGroup, + NavDivider, + AppItemStatic +} from '@fluentui/react-nav-preview' +import { + Board20Filled, + Board20Regular, + NotePin20Filled, + NotePin20Regular, + Lightbulb20Filled, + Lightbulb20Regular, + bundleIcon +} from '@fluentui/react-icons' import { IdeaField } from './IdeaField' import { IdeaPhaseBar } from './IdeaPhaseBar' -import { IdeaNav } from './IdeaNav' + +const Dashboard = bundleIcon(Board20Filled, Board20Regular) +const Lightbulb = bundleIcon(Lightbulb20Filled, Lightbulb20Regular) +const JobPostings = bundleIcon(NotePin20Filled, NotePin20Regular) export const IdeaModule: FC = (props) => { - const { state, setState, isOpen, renderHamburger, fluentProviderId } = useIdeaModule(props) + const { state, setState, getSelectedIdea, isOpen, renderHamburger, fluentProviderId } = + useIdeaModule(props) return ( @@ -20,7 +52,73 @@ export const IdeaModule: FC = (props) => { ) : (
- + + + + {renderHamburger()} + + + + Idémodul + } value='total'> + Totaloversikt + + Registrering + } value='registrering'> + Oversikt + + + }>Mine idéer + + {state.ideas.data.items + .filter((idea) => !idea.processing) + .map((idea) => ( + { + setUrlHash({ ideaId: idea.Id.toString() }) + getSelectedIdea() + }} + > + {idea?.Title} + + ))} + + + + Behandling + } value='behandling'> + Oversikt + + + }>Mine idéer + + {state.ideas.data.items + .filter((idea) => idea.processing) + .map((idea) => ( + { + setUrlHash({ ideaId: idea.Id.toString() }) + getSelectedIdea() + }} + > + {idea?.Title} + + ))} + + + +
{state.error && ( { - const context = useIdeaModuleContext() - const { getSelectedIdea, isOpen, renderHamburger } = useIdeaModule() - - return ( - - - - {renderHamburger()} - - - - Idémodul - } value='total'> - Totaloversikt - - Registrering - } value='registrering'> - Oversikt - - - }>Mine idéer - - {context.state.ideas.data.items - .filter((idea) => !idea.processing) - .map((idea) => ( - { - setUrlHash({ ideaId: idea.Id.toString() }) - getSelectedIdea() - }} - > - {idea?.Title} - - ))} - - - - Behandling - } value='behandling'> - Oversikt - - - }>Mine idéer - - {context.state.ideas.data.items - .filter((idea) => idea.processing) - .map((idea) => ( - { - setUrlHash({ ideaId: idea.Id.toString() }) - getSelectedIdea() - }} - > - {idea?.Title} - - ))} - - - - - ) -} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaNav/index.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaNav/index.ts deleted file mode 100644 index 767364809..000000000 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaNav/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './IdeaNav' diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.tsx index da319dfa1..9fa2b4592 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.tsx @@ -13,7 +13,7 @@ import { Hamburger } from '@fluentui/react-nav-preview' * Component logic hook for `IdeaModule` component. * clear */ -export function useIdeaModule(props?: IIdeaModuleProps) { +export function useIdeaModule(props: IIdeaModuleProps) { const { state, setState } = useIdeaModuleState(props) const fluentProviderId = useId('fp-idea-module') From ec69523352b799f3946111364b92062a82886ffe Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Tue, 12 Nov 2024 18:56:07 +0100 Subject: [PATCH 17/31] Add accordion if in process for registration item --- .../IdeaModule/IdeaField/IdeaField.tsx | 6 +- .../IdeaModule/IdeaField/useIdeaField.tsx | 70 +++++++-- .../IdeaModule/IdeaModule.module.scss | 28 +++- .../src/components/IdeaModule/IdeaModule.tsx | 136 ++++++++++++++++-- .../Lists/Prosjektinnholdskolonner.xml | 22 +-- 5 files changed, 221 insertions(+), 41 deletions(-) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/IdeaField.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/IdeaField.tsx index ac199c86f..f0a630fe0 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/IdeaField.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/IdeaField.tsx @@ -11,7 +11,11 @@ export const IdeaField: FC = (props) => { {props.model.displayName} - {renderValueForField()} + {renderValueForField() ? ( + {renderValueForField()} + ) : ( + Ingen verdi + )}
) } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx index 9a8a30d48..f21c644b8 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx @@ -1,8 +1,8 @@ import { IPersonaProps, ITag, Link } from '@fluentui/react' import React from 'react' import { IIdeaFieldProps } from './types' -import { Persona } from '@fluentui/react-components' -import { OverflowTagMenu } from 'pp365-shared-library' +import { Persona, Tag } from '@fluentui/react-components' +import { getFluentIcon, OverflowTagMenu } from 'pp365-shared-library' import { ChevronCircleRightFilled, EarthFilled, @@ -10,6 +10,7 @@ import { TagFilled, TagMultipleFilled } from '@fluentui/react-icons' +import { useIdeaModuleContext } from '../context' /** * Component logic hook for the `IdeaField` component. @@ -20,11 +21,10 @@ import { * as well as the `displayMode` for the `IdeaField` component. */ export function useIdeaField(props: IIdeaFieldProps) { - /** - * Renders the value for the field based on the field type. - * - * @returns JSX.Element - */ + const context = useIdeaModuleContext() + + const isInProcessing = context.state.selectedIdea.item.processing + const renderValueForField = () => { let icon = TagMultipleFilled @@ -32,7 +32,7 @@ export function useIdeaField(props: IIdeaFieldProps) { props.model.internalName === 'GtIdeaRecommendation' || props.model.internalName === 'GtIdeaDecision' ) { - props.model.type = 'TaxonomyFieldType' + props.model.type = 'IdeaStatus' } switch (props.model.internalName) { @@ -145,6 +145,56 @@ export function useIdeaField(props: IIdeaFieldProps) { (date: Date) => { return
{date.toLocaleDateString()}
} + ], + [ + 'IdeaStatus', + (textValue: string) => { + const processing = context.state.configuration.processing + const registration = context.state.configuration.registration + + const approveValue = isInProcessing + ? processing.find((p) => p.key === 'approve')?.recommendation + : registration.find((p) => p.key === 'approve')?.recommendation + const considerationValue = isInProcessing + ? processing.find((p) => p.key === 'consideration')?.recommendation + : registration.find((p) => p.key === 'consideration')?.recommendation + const rejectValue = isInProcessing + ? processing.find((p) => p.key === 'reject')?.recommendation + : registration.find((p) => p.key === 'reject')?.recommendation + + const statusStyles = { + [approveValue]: { + backgroundColor: 'var(--colorPaletteLightGreenBackground2)', + icon: getFluentIcon('CheckmarkCircle') + }, + [considerationValue]: { + backgroundColor: 'var(--colorPaletteYellowBackground2)', + icon: getFluentIcon('Edit') + }, + [rejectValue]: { + backgroundColor: 'var(--colorPaletteRedBackground2)', + icon: getFluentIcon('DismissCircle') + } + } + + const { backgroundColor, icon } = statusStyles[textValue] || { + backgroundColor: 'var(--colorNeutralBackground2)', + icon: null + } + + return ( +
+ + {textValue} + +
+ ) + } ] ]) @@ -157,9 +207,9 @@ export function useIdeaField(props: IIdeaFieldProps) { return
{value}
} } else { - return
+ return null } } - return { renderValueForField } + return { renderValueForField, isInProcessing } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss index 5c4e067b1..4364d746e 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss @@ -32,8 +32,9 @@ } .ideaTitle { - font-size: 1.5rem; + font-size: 2rem; font-weight: bold; + height: 32px; margin: 0; } } @@ -47,13 +48,32 @@ .ideaField { margin: 0; } + } + + .statusSection { + display: flex; + flex-direction: column; + padding: 16px 32px; + border-radius: var(--borderRadiusXLarge); + margin: 0 32px; + + .ideaTitle { + font-size: 1rem; + font-weight: bold; + margin: 0; + } .status { display: flex; - justify-content: flex-end; - align-items: center; - gap: 8px; + justify-content: flex-start; + flex-direction: row; + gap: 32px; + margin-bottom: 16px; } } } } + +.loading { + padding: 32px; +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index f91ac9d65..2e1d99b50 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -1,9 +1,14 @@ -import React, { FC } from 'react' +import React, { FC, useState } from 'react' import styles from './IdeaModule.module.scss' import { IdeaModuleContext } from './context' import { IIdeaModuleProps } from './types' import { useIdeaModule } from './useIdeaModule' import { + Accordion, + AccordionHeader, + AccordionItem, + AccordionPanel, + AccordionToggleEventHandler, Divider, FluentProvider, IdPrefixProvider, @@ -44,17 +49,92 @@ export const IdeaModule: FC = (props) => { const { state, setState, getSelectedIdea, isOpen, renderHamburger, fluentProviderId } = useIdeaModule(props) + const [openItems, setOpenItems] = useState(['registration']) + const handleToggle: AccordionToggleEventHandler = (event, data) => { + setOpenItems(data.openItems) + } + + const ignoreFields = [ + 'GtIdeaRecommendation', + 'GtIdeaRecommendationComment', + 'GtIdeaDecision', + 'GtIdeaDecisionComment' + ] + + const renderStatus = () => { + const isInProcessing = state.selectedIdea.item.processing + const processing = state.configuration.processing + const registration = state.configuration.registration + + const approveValue = isInProcessing + ? processing.find((p) => p.key === 'approve')?.recommendation + : registration.find((p) => p.key === 'approve')?.recommendation + const considerationValue = isInProcessing + ? processing.find((p) => p.key === 'consideration')?.recommendation + : registration.find((p) => p.key === 'consideration')?.recommendation + const rejectValue = isInProcessing + ? processing.find((p) => p.key === 'reject')?.recommendation + : registration.find((p) => p.key === 'reject')?.recommendation + + const statusStyles = { + [approveValue]: { + backgroundColor: 'var(--colorPaletteLightGreenBackground1)', + borderColor: 'var(--colorPaletteLightGreenBorder1)' + }, + [considerationValue]: { + backgroundColor: 'var(--colorPaletteYellowBackground1)', + borderColor: 'var(--colorPaletteYellowBorder1)' + }, + [rejectValue]: { + backgroundColor: 'var(--colorPaletteRedBackground1)', + borderColor: 'var(--colorPaletteRedBorder1)' + } + } + + const statusValue = isInProcessing + ? state.selectedIdea.item.processing.GtIdeaDecision + : state.selectedIdea.item.GtIdeaRecommendation + + const backgroundColor = + statusStyles[statusValue]?.backgroundColor || 'var(--colorNeutralBackground2)' + const borderColor = statusStyles[statusValue]?.borderColor || 'var(--colorNeutralBorder2)' + + const fieldValues = isInProcessing + ? state.selectedIdea.processingFieldValues + : state.selectedIdea.registeredFieldValues + + const filterKey = isInProcessing ? 'GtIdeaDecision' : 'GtIdeaRecommendation' + + return ( +
+

Status

+
+ {fieldValues + .filter((model) => model.internalName.includes(filterKey)) + .map((model, idx) => ( + + ))} +
+
+ ) + } + return ( {state.loading ? ( - + ) : (
= (props) => { Oversikt - }>Mine idéer + }>Registrerte idéer {state.ideas.data.items .filter((idea) => !idea.processing) @@ -99,7 +179,7 @@ export const IdeaModule: FC = (props) => { Oversikt - }>Mine idéer + }>Idéer i behandling {state.ideas.data.items .filter((idea) => idea.processing) @@ -136,20 +216,46 @@ export const IdeaModule: FC = (props) => {

{state.selectedIdea.item.Title}

-
- {state.selectedIdea.registeredFieldValues.map((model, idx) => ( - - ))} -
+ {!state.selectedIdea.item.processing && ( +
+ {state.selectedIdea.registeredFieldValues + .filter((model) => !ignoreFields.includes(model.internalName)) + .map((model, idx) => ( + + ))} +
+ )} + {!state.selectedIdea.item.processing && renderStatus()} + {state.selectedIdea.item.processing && ( + + + + Registrert idé + + +
+ {state.selectedIdea.registeredFieldValues + .filter((model) => !ignoreFields.includes(model.internalName)) + .map((model, idx) => ( + + ))} +
+
+
+
+ )}
- {state.selectedIdea.processingFieldValues?.map((model, idx) => ( - - ))} + {state.selectedIdea.processingFieldValues + ?.filter((model) => !ignoreFields.includes(model.internalName)) + .map((model, idx) => ( + + ))}
+ {state.selectedIdea.item.processing && renderStatus()} ) : ( - + )}
diff --git a/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml b/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml index 63cd5f7b4..99b626acc 100644 --- a/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml +++ b/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml @@ -864,17 +864,6 @@ 1090 - Kommentar til anbefaling - GtIdeaRecommendationComment - GtIdeaRecommendationCommentOWSMTXT - Note - Idémodul - 200 - 300 - True - - - 1100 Anbefaling GtIdeaRecommendation GtIdeaRecommendationOWSCHCS @@ -885,6 +874,17 @@ True True + + 1100 + Kommentar til anbefaling + GtIdeaRecommendationComment + GtIdeaRecommendationCommentOWSMTXT + Note + Idémodul + 200 + 300 + True + 1110 Idéeier From 8698c6409ad38ceef3288d996d34c9e356ef51e8 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Wed, 13 Nov 2024 11:09:11 +0100 Subject: [PATCH 18/31] Changes to styling and responsiveness for small screens [packages-only] --- .../PortfolioWebParts/package.json | 3 +- .../IdeaModule/IdeaModule.module.scss | 15 ++++--- .../src/components/IdeaModule/IdeaModule.tsx | 4 +- .../IdeaPhaseBar/IdeaPhaseBar.module.scss | 3 +- common/config/rush/pnpm-lock.yaml | 41 ++++++++++++++----- 5 files changed, 47 insertions(+), 19 deletions(-) diff --git a/SharePointFramework/PortfolioWebParts/package.json b/SharePointFramework/PortfolioWebParts/package.json index 7d941d2ca..6bbb96c1e 100644 --- a/SharePointFramework/PortfolioWebParts/package.json +++ b/SharePointFramework/PortfolioWebParts/package.json @@ -66,7 +66,8 @@ "@fluentui/react-datepicker-compat": "~0.4.38", "@fluentui/react-motion-preview": "~0.5.20", "@fluentui/react-nav-preview": "~0.9.1", - "react-gauge-component": "~1.2.61" + "react-gauge-component": "~1.2.61", + "@fluentui/react-motion-components-preview": "~0.3.0" }, "devDependencies": { "@microsoft/eslint-config-spfx": "1.17.4", diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss index 4364d746e..91adfb662 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss @@ -9,9 +9,9 @@ padding: 16px 32px; gap: 16px; display: grid; - justify-content: flex-start; align-items: flex-start; height: fit-content; + width: 100%; .ideaHeader { display: flex; @@ -21,12 +21,12 @@ .toolbar { display: flex; flex-direction: column; - justify-content: center; - align-items: center; gap: 16px; .hamburger { position: absolute; + z-index: 2; + top: 5px; left: 32px; } } @@ -41,7 +41,7 @@ .idea { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: repeat(auto-fit, minmax(450px, 1fr)); grid-gap: 16px; margin-bottom: 16px; @@ -55,7 +55,7 @@ flex-direction: column; padding: 16px 32px; border-radius: var(--borderRadiusXLarge); - margin: 0 32px; + width: fit-content; .ideaTitle { font-size: 1rem; @@ -66,9 +66,14 @@ .status { display: flex; justify-content: flex-start; + flex-wrap: wrap; flex-direction: row; gap: 32px; margin-bottom: 16px; + + .field { + max-width: 620px; + } } } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index 2e1d99b50..c2102c1e8 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -115,7 +115,9 @@ export const IdeaModule: FC = (props) => { {fieldValues .filter((model) => model.internalName.includes(filterKey)) .map((model, idx) => ( - +
+ +
))}
diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/IdeaPhaseBar.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/IdeaPhaseBar.module.scss index 98f8292ca..7384360e9 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/IdeaPhaseBar.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/IdeaPhaseBar.module.scss @@ -2,5 +2,6 @@ display: flex; flex-direction: row; justify-content: center; - align-items: center + align-items: center; + flex-wrap: wrap; } diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index f9bdc64ba..02712a924 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -6,6 +6,7 @@ dependencies: '@fluentui/react-hooks': 8.6.27_4865287ef69aa4f3fc3f709d3560286e '@fluentui/react-icons': 2.0.242_react@17.0.1 '@fluentui/react-motion-preview': 0.5.21_9c907a7ccd27a2e2d0ee9de5366482d1 + '@fluentui/react-nav-preview': 0.9.1_9c907a7ccd27a2e2d0ee9de5366482d1 '@microsoft/decorators': 1.17.4 '@microsoft/eslint-config-spfx': 1.17.4_eslint@8.25.0+typescript@4.5.5 '@microsoft/eslint-plugin-spfx': 1.17.4_eslint@8.25.0+typescript@4.5.5 @@ -2098,6 +2099,22 @@ packages: react-dom: '>=16.14.0 <19.0.0' resolution: integrity: sha512-1D5Ih3Q/V6dkjSrmwsH0W61PY6b3TjkiMV5bS6Z8xsY/rbJRo1Ssv3yXFs82xo8SUDy4rVORLkfrbuM1Lo7ZSg== + /@fluentui/react-motion-components-preview/0.3.0_9c907a7ccd27a2e2d0ee9de5366482d1: + dependencies: + '@fluentui/react-motion': 9.6.0_9c907a7ccd27a2e2d0ee9de5366482d1 + '@swc/helpers': 0.5.11 + '@types/react': 17.0.45 + '@types/react-dom': 17.0.17 + react: 17.0.1 + react-dom: 17.0.1_react@17.0.1 + dev: false + peerDependencies: + '@types/react': '>=16.14.0 <19.0.0' + '@types/react-dom': '>=16.9.0 <19.0.0' + react: '>=16.14.0 <19.0.0' + react-dom: '>=16.14.0 <19.0.0' + resolution: + integrity: sha512-N888xO727bSogyH0WUSW2pkjQ2vXEpyDa0Ygj+4XQaTfHz8DecDiKfM83zUpQ7pZOhx8eQPUP76flijm+iVm8w== /@fluentui/react-motion-preview/0.5.21_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/react-jsx-runtime': 9.0.38_4865287ef69aa4f3fc3f709d3560286e @@ -6722,7 +6739,7 @@ packages: '@fluentui/react': 7.204.0_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-file-type-icons': 8.11.13_4865287ef69aa4f3fc3f709d3560286e '@fluentui/react-hooks': 8.8.5_4865287ef69aa4f3fc3f709d3560286e - '@fluentui/react-icons': 2.0.242_react@17.0.1 + '@fluentui/react-icons': 2.0.261_react@17.0.1 '@fluentui/react-northstar': 0.66.0_346d49937f02d12b52cc20d3762bb2de '@fluentui/react-theme-provider': 0.19.16_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/scheme-utilities': 8.3.52_4865287ef69aa4f3fc3f709d3560286e @@ -6782,7 +6799,7 @@ packages: '@fluentui/react-components': 9.53.0_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-file-type-icons': 8.11.13_4865287ef69aa4f3fc3f709d3560286e '@fluentui/react-hooks': 8.8.5_4865287ef69aa4f3fc3f709d3560286e - '@fluentui/react-icons': 2.0.242_react@17.0.1 + '@fluentui/react-icons': 2.0.261_react@17.0.1 '@fluentui/react-icons-northstar': 0.66.5_react-dom@17.0.1+react@17.0.1 '@fluentui/react-migration-v8-v9': 9.6.15_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-northstar': 0.66.0_346d49937f02d12b52cc20d3762bb2de @@ -25404,7 +25421,7 @@ packages: dependencies: '@fluentui/react': 8.98.1_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-components': 9.52.0_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-icons': 2.0.242_react@17.0.1 + '@fluentui/react-icons': 2.0.261_react@17.0.1 '@microsoft/decorators': 1.17.4 '@microsoft/eslint-config-spfx': 1.17.4_eslint@8.25.0+typescript@4.5.5 '@microsoft/eslint-plugin-spfx': 1.17.4_eslint@8.25.0+typescript@4.5.5 @@ -25464,7 +25481,8 @@ packages: '@fluentui/react-datepicker-compat': 0.4.39_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-file-type-icons': 8.11.13_4865287ef69aa4f3fc3f709d3560286e '@fluentui/react-hooks': 8.6.27_4865287ef69aa4f3fc3f709d3560286e - '@fluentui/react-icons': 2.0.242_react@17.0.1 + '@fluentui/react-icons': 2.0.261_react@17.0.1 + '@fluentui/react-motion-components-preview': 0.3.0_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-motion-preview': 0.5.21_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-nav-preview': 0.9.1_9c907a7ccd27a2e2d0ee9de5366482d1 '@microsoft/decorators': 1.17.4 @@ -25545,14 +25563,14 @@ packages: peerDependencies: '@pnp/odata': '*' resolution: - integrity: sha512-QH+N4mdQ5roufqeZ/NhxynFDyqmx8+lVQ6e+5UTC24nq+FtmwJrIz/i5j2lwlyR4kOnJxQltqkBK9M1Xlu6rNw== + integrity: sha512-E5t7s2IiGyWkdPgvgdb2Iw152thTYFGtlxhuFgNKX8sBQ/vJXXfpYNjsSYoXx4BofaQo6DvjCyD0LvOTlvcuFw== tarball: file:projects/pp365-portfoliowebparts.tgz version: 0.0.0 file:projects/pp365-programwebparts.tgz_@pnp+odata@2.15.0: dependencies: '@fluentui/react': 8.98.1_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-components': 9.52.0_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-icons': 2.0.242_react@17.0.1 + '@fluentui/react-icons': 2.0.261_react@17.0.1 '@microsoft/eslint-config-spfx': 1.17.4_eslint@8.25.0+typescript@4.5.5 '@microsoft/eslint-plugin-spfx': 1.17.4_eslint@8.25.0+typescript@4.5.5 '@microsoft/rush-stack-compiler-4.5': 0.2.2 @@ -25633,7 +25651,7 @@ packages: dependencies: '@fluentui/react': 8.98.1_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-components': 9.52.0_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-icons': 2.0.242_react@17.0.1 + '@fluentui/react-icons': 2.0.261_react@17.0.1 '@microsoft/decorators': 1.17.4 '@microsoft/eslint-config-spfx': 1.17.4_eslint@8.25.0+typescript@4.5.5 '@microsoft/eslint-plugin-spfx': 1.17.4_eslint@8.25.0+typescript@4.5.5 @@ -25707,7 +25725,7 @@ packages: '@fluentui/react': 8.98.1_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-components': 9.52.0_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-datepicker-compat': 0.4.39_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-icons': 2.0.242_react@17.0.1 + '@fluentui/react-icons': 2.0.261_react@17.0.1 '@microsoft/decorators': 1.17.4 '@microsoft/eslint-config-spfx': 1.17.4_eslint@8.25.0+typescript@4.5.5 '@microsoft/eslint-plugin-spfx': 1.17.4_eslint@8.25.0+typescript@4.5.5 @@ -25793,7 +25811,7 @@ packages: '@fluentui/react': 8.98.1_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-components': 9.52.0_9c907a7ccd27a2e2d0ee9de5366482d1 '@fluentui/react-datepicker-compat': 0.4.39_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-icons': 2.0.242_react@17.0.1 + '@fluentui/react-icons': 2.0.261_react@17.0.1 '@microsoft/rush-stack-compiler-4.5': 0.2.2 '@microsoft/sp-application-base': 1.17.4_9c907a7ccd27a2e2d0ee9de5366482d1 '@microsoft/sp-build-web': 1.17.4 @@ -25860,7 +25878,7 @@ packages: file:projects/pp365-spfx-tasks.tgz_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/react-components': 9.52.0_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-icons': 2.0.242_react@17.0.1 + '@fluentui/react-icons': 2.0.261_react@17.0.1 '@types/colors': 1.2.1 '@types/lodash': 4.14.195 colors: 1.4.0 @@ -25884,7 +25902,7 @@ packages: file:projects/pp365-templates.tgz_9c907a7ccd27a2e2d0ee9de5366482d1: dependencies: '@fluentui/react-components': 9.52.0_9c907a7ccd27a2e2d0ee9de5366482d1 - '@fluentui/react-icons': 2.0.242_react@17.0.1 + '@fluentui/react-icons': 2.0.261_react@17.0.1 '@ptkdev/json-token-replace': 1.2.2 '@types/lodash': 4.14.202 chokidar-cli: 2.1.0 @@ -25913,6 +25931,7 @@ specifiers: '@fluentui/react-hooks': 8.6.27 '@fluentui/react-icons': ~2.0.240 '@fluentui/react-motion-preview': ~0.5.20 + '@fluentui/react-nav-preview': ~0.9.1 '@microsoft/decorators': 1.17.4 '@microsoft/eslint-config-spfx': 1.17.4 '@microsoft/eslint-plugin-spfx': 1.17.4 From 93b49161e15923f24cd584111e8ab47fe9e15224 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Wed, 13 Nov 2024 15:58:54 +0100 Subject: [PATCH 19/31] Add dynamic copying of data from registrationList to processingList --- .../src/extensions/ideaRegistration/index.tsx | 167 +++++++++--------- .../IdeaModule/IdeaField/IdeaField.tsx | 2 +- .../PortfolioWebParts/src/data/index.ts | 64 ------- .../src/models/ProjectContentColumn.ts | 3 + .../Lists/Prosjektinnholdskolonner.xml | 1 + Templates/Portfolio/Resources.en-US.resx | 4 +- Templates/Portfolio/Resources.no-NB.resx | 4 +- 7 files changed, 88 insertions(+), 157 deletions(-) diff --git a/SharePointFramework/PortfolioExtensions/src/extensions/ideaRegistration/index.tsx b/SharePointFramework/PortfolioExtensions/src/extensions/ideaRegistration/index.tsx index eb7b8779a..632cac4ac 100644 --- a/SharePointFramework/PortfolioExtensions/src/extensions/ideaRegistration/index.tsx +++ b/SharePointFramework/PortfolioExtensions/src/extensions/ideaRegistration/index.tsx @@ -14,11 +14,17 @@ import '@pnp/sp/lists' import '@pnp/sp/folders/list' import '@pnp/sp/site-groups/web' import '@pnp/sp/clientside-pages/web' -import { ClientsideText, IClientsidePage } from '@pnp/sp/clientside-pages' import { isUserAuthorized } from '../../helpers/isUserAuthorized' import strings from 'PortfolioExtensionsStrings' import { Choice, IdeaConfigurationModel, SPIdeaConfigurationItem } from 'models' import { find } from 'underscore' +import { + getClassProperties, + ProjectContentColumn, + SPField, + SPProjectContentColumnItem +} from 'pp365-shared-library' +import _ from 'underscore' const LOG_SOURCE: string = 'IdeaRegistrationCommand' @@ -36,6 +42,7 @@ export default class IdeaRegistrationCommand extends BaseListViewCommandSet this._openCmd = this.tryGetCommand('OPEN_IDEA_REGISTRATION_DIALOG') this._openCmd.visible = false this._openLinkCmd = this.tryGetCommand('IDEA_PROCESSING_LINK') + // this._openLinkCmd.title = `Gå til ${this._config.processingList}` this._openLinkCmd.visible = this.context.pageContext.list.title.includes('registrering') this._userAuthorized = await isUserAuthorized( this._sp, @@ -203,9 +210,73 @@ export default class IdeaRegistrationCommand extends BaseListViewCommandSet * @param comment Comment from the dialog */ private _onSubmit = async (row: RowAccessor, comment: string): Promise => { + const getProcessingColumns = async (): Promise<{ fields: SPField[] }> => { + const [listInfo] = await this._sp.web.lists + .filter(`Title eq '${this._config.processingList}'`) + .select('Id')() + const list = this._sp.web.lists.getById(listInfo.Id) + const fields = await list.fields + .select(...getClassProperties(SPField)) + .filter("substringof('Gt', InternalName) or InternalName eq 'Title'")() + + return { + fields + } + } + + const contentColumns = async () => { + try { + const list = this._sp.web.lists.getByTitle('Prosjektinnholdskolonner') + + const columnItems = await list.items.select( + ...Object.keys(new SPProjectContentColumnItem()) + )() + + const filteredColumnItems = columnItems.filter( + (col) => + col.GtDataSourceCategory === 'Idémodul' || + (!col.GtDataSourceCategory && !col.GtDataSourceLevel) || + (!col.GtDataSourceCategory && _.contains(col.GtDataSourceLevel, 'Portefølje')) + ) + + return filteredColumnItems + .filter((col) => col.GtIdeaCopyToProcess) + .map((item) => new ProjectContentColumn(item)) + } catch (error) { + throw new Error(error) + } + } + + const columns = await contentColumns() + const processingColumns = await getProcessingColumns() + const registrationList = row + + const userFields = registrationList.fields + .filter((fld) => fld.fieldType.indexOf('User') === 0) + .map((fld) => fld.internalName) + + const columnsToCopy = processingColumns.fields.filter((col) => { + const field = registrationList.fields.find( + (f) => f.internalName === col.InternalName || f.displayName === col.Title + ) + return field && columns.some((contentCol) => contentCol.internalName === col.InternalName) + }) + + const copyData = columnsToCopy + .map((col) => { + if (userFields.includes(col.InternalName)) { + return { + [`${col.InternalName}Id`]: registrationList.getValueByName(col.InternalName)[0]?.id + } + } + + return { + [col.InternalName]: registrationList.getValueByName(col.InternalName) + } + }) + .reduce((acc, val) => ({ ...acc, ...val }), {}) + const rowId = row.getValueByName('ID') - const rowTitle = row.getValueByName('Title') - const rowReporter = row.getValueByName('GtIdeaReporter')[0] || '' await this._sp.web.lists .getByTitle(this._config.registrationList) @@ -218,9 +289,9 @@ export default class IdeaRegistrationCommand extends BaseListViewCommandSet Log.info(LOG_SOURCE, `Updated ${this._config.registrationList}: Approved`) - const pageUrl = await this._createSitePage(row) - await this._updateProcessingList(rowId, rowTitle, rowReporter, pageUrl) + const ideaModuleUrl = `${this.context.pageContext.site.absoluteUrl}/SitePages/Idemodul.aspx#ideaId=${rowId}` + await this._updateProcessingList(rowId, copyData, ideaModuleUrl) window.location.reload() } @@ -255,19 +326,17 @@ export default class IdeaRegistrationCommand extends BaseListViewCommandSet * Update the work list with selected values of the registration list * * @param rowId Id of the row in the registration list - * @param rowTitle Title of the row in the registration list + * @param copyData Data to copy from the registration list to processing list */ private _updateProcessingList = async ( rowId: number, - rowTitle: string, - rowReporter: any, + copyData: any, pageUrl: string ): Promise => { await this._sp.web.lists.getByTitle(this._config.processingList).items.add({ - Title: rowTitle, GtRegistratedIdeaId: rowId, GtIdeaUrl: pageUrl, - GtIdeaReporterId: rowReporter?.id + ...copyData }) Log.info(LOG_SOURCE, 'Updated work list') @@ -284,82 +353,4 @@ export default class IdeaRegistrationCommand extends BaseListViewCommandSet find(this._config.registration, { key: Choice.Approve })?.recommendation ) } - - /** - * Create a site page with the selected values of the registration list - * - * @param row Selected row - */ - private _createSitePage = async (row: RowAccessor): Promise => { - const title: string = row.getValueByName('Title') - const urlFriendlyTitle = title.replace(/é/g, 'e').replace(/[^a-zA-Z0-9-_ÆØÅæøå ]/g, '') - const page: IClientsidePage = await this._sp.web.addClientsidePage( - `KUR-${urlFriendlyTitle}`, - `KUR-${urlFriendlyTitle}`, - 'Article' - ) - - const reporter = row.getValueByName('GtIdeaReporter')[0] || null - - page.layoutType = 'NoImage' - page.showTopicHeader = true - page.topicHeader = 'Idé' - page.description = `Konsept utredningsrapport for: ${title}` - - const section = page.addSection() - const column1 = section.addColumn(4) - const column2 = section.addColumn(4) - const column3 = section.addColumn(4) - - column1.addControl(new ClientsideText(`

Tittel

${row.getValueByName('Title')}`)) - column1.addControl( - new ClientsideText(`

Bakgrunn

${row.getValueByName('GtIdeaBackground')}`) - ) - column1.addControl( - new ClientsideText( - `

Forslag til løsning

${row.getValueByName('GtIdeaSolutionProposals')}` - ) - ) - column1.addControl( - new ClientsideText( - `

Overordnet gjennomføringsplan

${row.getValueByName('GtIdeaExecutionPlan')}` - ) - ) - column2.addControl( - new ClientsideText( - `

Innmelder

${ - reporter - ? `${reporter?.title}` - : 'Ikke angitt' - }` - ) - ) - column2.addControl( - new ClientsideText(`

Ressursbehov

${row.getValueByName('GtIdeaResourceRequirements')}`) - ) - column2.addControl( - new ClientsideText(`

Problemstilling

${row.getValueByName('GtIdeaIssue')}`) - ) - column2.addControl( - new ClientsideText(`

Mulige gevinster

${row.getValueByName('GtIdeaPossibleGains')}`) - ) - - column3.addControl( - new ClientsideText(`

Berørte parter

${row.getValueByName('GtIdeaAffectedParties')}`) - ) - column3.addControl( - new ClientsideText( - `

Kritiske suksessfaktorer

${row.getValueByName('GtIdeaCriticalSuccessFactors')}` - ) - ) - column3.addControl( - new ClientsideText(`

Andre kommentarer

${row.getValueByName('GtIdeaOtherComments')}`) - ) - - section.emphasis = 1 - page.save() - const savedPage = await page.load() - - return savedPage['json'].AbsoluteUrl - } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/IdeaField.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/IdeaField.tsx index f0a630fe0..fe25de4ac 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/IdeaField.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/IdeaField.tsx @@ -14,7 +14,7 @@ export const IdeaField: FC = (props) => { {renderValueForField() ? ( {renderValueForField()} ) : ( - Ingen verdi + Ikke angitt )}
) diff --git a/SharePointFramework/PortfolioWebParts/src/data/index.ts b/SharePointFramework/PortfolioWebParts/src/data/index.ts index c024ca8ba..fd770577e 100644 --- a/SharePointFramework/PortfolioWebParts/src/data/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/data/index.ts @@ -1072,14 +1072,6 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { return ItemFieldValues.create({ fieldValues, fieldValuesAsText }) } - public getIdeaData(ideaId: number): Promise { - console.log(ideaId) - try { - } catch (error) { - return error - } - } - public async getIdeasData(configuration: IdeaConfigurationModel): Promise { const getListData = async ( listName: string @@ -1131,13 +1123,11 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { const registrationData = await getListData(configuration.registrationList) const processingData = await getListData(configuration.processingList) - // For each registrationData.item and registrationData.fieldValues, find the corresponding processingData.item and processingData.fieldValues where the registrationData.item.Id is contained within the processingData.item.GtRegistratedIdeaId const itemsData = registrationData.items.map((registered) => { const processing = processingData.items.find( (processingItem) => processingItem.GtRegistratedIdeaId === registered.Id ) - // merge the registered and processing item return { ...registered, processing } }) @@ -1146,12 +1136,9 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { (processingFieldValues) => processingFieldValues.id === values.id ) - // merge the values and processing values return { ...values, ...processingFieldValues } }) - console.log({ itemsData, fieldValues, registrationData, processingData }) - return { data: { items: itemsData, @@ -1167,57 +1154,6 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { } } } - - // public async getIdeasDataX(configuration: ConfigurationItem): Promise { - // try { - // const ideaRegistrationList = this._sp.web.lists.getByTitle(configuration.ideaRegistrationList) - // // const ideaProcessingList = this._sp.web.lists.getByTitle(configuration.ideaProcessingList) - // // const registered = await ideaRegistrationList.items.select('*', 'FieldValuesAsText') - // // .expand('FieldValuesAsText').using(DefaultCaching)() - // // const processing = await ideaProcessingList.items.select('*', 'FieldValuesAsText') - // // .expand('FieldValuesAsText').using(DefaultCaching)() - - // // const fields = await ideaRegistrationList.fields - // // .select(...getClassProperties(SPField)) - // // .filter( - // // "substringof('Gt', InternalName) or InternalName eq 'Title' or InternalName eq 'Id' or InternalName eq 'ID'" - // // )() - // // const userFields = fields - // // .filter((fld) => fld.TypeAsString.indexOf('User') === 0) - // // .map((fld) => fld.InternalName) - - // const [ideaList] = await this._sp.web.lists - // .filter(`Title eq '${configuration.ideaRegistrationList}'`) - // .select('Id')() - - // const items = await this._sp.web.lists.getById(ideaList.Id).items() - - // const list = this._sp.web.lists.getById(ideaList.Id) - // const listItems = await Promise.all( - // items.map(async (item) => { - // const listItem = await list.items.getById(item.Id) - // return listItem - // }) - // ) - - // const allFieldValues = await Promise.all( - // listItems.map(async (item) => { - // const fieldValues = await this.getItemFieldValues(item, userFields) - // return fieldValues - // }) - // ) - - // return { - // data: { - // items: items, - // fieldValues: allFieldValues, - // fields - // } - // } - // } catch (error) { - // return null - // } - // } } export * from './types' diff --git a/SharePointFramework/shared-library/src/models/ProjectContentColumn.ts b/SharePointFramework/shared-library/src/models/ProjectContentColumn.ts index 6abb45325..2871bcb51 100644 --- a/SharePointFramework/shared-library/src/models/ProjectContentColumn.ts +++ b/SharePointFramework/shared-library/src/models/ProjectContentColumn.ts @@ -17,6 +17,7 @@ export class SPProjectContentColumnItem { public GtFieldDataTypeProperties?: string = '' public GtFieldLocked?: boolean = false public GtDataSourceLevel?: string[] = [] + public GtIdeaCopyToProcess?: boolean = false } export type ProjectContentColumnData = { @@ -43,6 +44,7 @@ export class ProjectContentColumn implements IProjectContentColumn { public isSorted?: boolean public isSortedDescending?: boolean public isMultiline?: boolean + public ideaCopyToProcess?: boolean public data?: ProjectContentColumnData constructor(item?: SPProjectContentColumnItem) { @@ -60,6 +62,7 @@ export class ProjectContentColumn implements IProjectContentColumn { this.isMultiline = this.dataType === 'note' || this.dataType === 'tags' this.minWidth = item?.GtColMinWidth ?? 100 this.maxWidth = item?.GtColMaxWidth + this.ideaCopyToProcess = item?.GtIdeaCopyToProcess this.data = { isGroupable: this.isGroupable, dataTypeProperties: tryParseJson(item?.GtFieldDataTypeProperties, {}), diff --git a/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml b/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml index 99b626acc..9813c59e3 100644 --- a/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml +++ b/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml @@ -83,6 +83,7 @@ 120 200 True + True 100 diff --git a/Templates/Portfolio/Resources.en-US.resx b/Templates/Portfolio/Resources.en-US.resx index 03e5737ca..94260eeed 100644 --- a/Templates/Portfolio/Resources.en-US.resx +++ b/Templates/Portfolio/Resources.en-US.resx @@ -2016,10 +2016,10 @@ Solution proposals for the idea - KUR + IdeaUrl - Concept investigation report (KUR) for the idea + IdeaUrl for the idea Project data association diff --git a/Templates/Portfolio/Resources.no-NB.resx b/Templates/Portfolio/Resources.no-NB.resx index 3760b83b6..69eeb1413 100644 --- a/Templates/Portfolio/Resources.no-NB.resx +++ b/Templates/Portfolio/Resources.no-NB.resx @@ -2159,10 +2159,10 @@ Løsningsforslag for idéen - KUR + IdéUrl - Konsept utredningsrapport (KUR) for idéen + IdéUrl for idéen Prosjektdata-tilknytning From 1f83b2cf97bf45e8f62061400eb4e7a6cc429e49 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Wed, 13 Nov 2024 16:18:33 +0100 Subject: [PATCH 20/31] Bugfix for processing idea without values [skip-ci] --- .../IdeaModule/IdeaField/useIdeaField.tsx | 32 +++++----- .../src/components/IdeaModule/IdeaModule.tsx | 1 - .../Lists/Prosjektinnholdskolonner.xml | 62 +++++++++---------- 3 files changed, 49 insertions(+), 46 deletions(-) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx index f21c644b8..943c06e4c 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx @@ -57,20 +57,24 @@ export function useIdeaField(props: IIdeaFieldProps) { [ 'User', ([user]: IPersonaProps[]) => { - return ( - - ) + if (user) { + return ( + + ) + } else { + return null + } } ], [ diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index c2102c1e8..394f3aa8d 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -237,7 +237,6 @@ export const IdeaModule: FC = (props) => {
{state.selectedIdea.registeredFieldValues - .filter((model) => !ignoreFields.includes(model.internalName)) .map((model, idx) => ( ))} diff --git a/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml b/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml index 9813c59e3..1241d2a2e 100644 --- a/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml +++ b/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml @@ -865,29 +865,6 @@ 1090 - Anbefaling - GtIdeaRecommendation - GtIdeaRecommendationOWSCHCS - Tags - Idémodul - 100 - 200 - True - True - - - 1100 - Kommentar til anbefaling - GtIdeaRecommendationComment - GtIdeaRecommendationCommentOWSMTXT - Note - Idémodul - 200 - 300 - True - - - 1110 Idéeier GtIdeaOwner GtIdeaOwner @@ -898,7 +875,7 @@ True - 1120 + 1100 Overordnet løsningsbeskrivelse GtIdeaSolutionDescription GtIdeaSolutionDescriptionOWSMTXT @@ -908,7 +885,7 @@ 300 - 1130 + 1110 Forventede gevinster GtIdeaExpectedGain GtIdeaExpectedGainOWSMTXT @@ -918,7 +895,7 @@ 300 - 1140 + 1120 Gjennomføringsplan: Tilnærming GtIdeaExecutionPlanApproach GtIdeaExecutionPlanApproachOWSMTXT @@ -928,7 +905,7 @@ 300 - 1150 + 1130 Gjennomføringsplan: Ressursbehov GtIdeaExecutionResourceNeeds GtIdeaExecutionResourceNeedsOWSMTXT @@ -938,7 +915,7 @@ 300 - 1160 + 1140 Gjennomføringsplan: Suksessfaktorer GtIdeaExecutionSuccessFactors GtIdeaExecutionSuccessFactorsOWSMTXT @@ -948,7 +925,7 @@ 300 - 1170 + 1150 Referanse GtIdeaReference GtIdeaReferenceOWSMTXT @@ -958,7 +935,7 @@ 300 - 1180 + 1160 Andre kommentarer GtIdeaOtherComments GtIdeaOtherCommentsOWSMTXT @@ -968,6 +945,29 @@ 300 True + + 1170 + Anbefaling + GtIdeaRecommendation + GtIdeaRecommendationOWSCHCS + Tags + Idémodul + 100 + 200 + True + True + + + 1180 + Kommentar til anbefaling + GtIdeaRecommendationComment + GtIdeaRecommendationCommentOWSMTXT + Note + Idémodul + 200 + 300 + True + 1190 Beslutning @@ -980,7 +980,7 @@ True - 1210 + 1200 Kommentar til beslutning GtIdeaStrategicValue GtIdeaDecisionCommentOWSMTXT From fd040b9ac79deb5ae67eee9725c64cb6fcf71aa2 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Wed, 13 Nov 2024 16:19:22 +0100 Subject: [PATCH 21/31] Change title of IDEA_PROCESSING_LINK [skip-ci] --- .../src/extensions/ideaRegistration/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SharePointFramework/PortfolioExtensions/src/extensions/ideaRegistration/manifest.json b/SharePointFramework/PortfolioExtensions/src/extensions/ideaRegistration/manifest.json index 75360b146..cf89cc48a 100644 --- a/SharePointFramework/PortfolioExtensions/src/extensions/ideaRegistration/manifest.json +++ b/SharePointFramework/PortfolioExtensions/src/extensions/ideaRegistration/manifest.json @@ -17,10 +17,10 @@ }, "IDEA_PROCESSING_LINK": { "title": { - "default": "Gå til idébehandling" + "default": "Gå til behandling" }, "iconImageUrl": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAA/lJREFUWEe1l02ME2UYx//P27orIiaiQQ9GiSCShU5ljQfU2OnWQKKi7E4HDReJXvSiRCT4Fa0hfhDUqBe9aPBCdNvpqqAJxG7fGpWDcbFTdoOrGCQckCAmrl+7tu9jZtrisEw70y3Oceb5+L3P5zuEEM/q1ea8P3uVoYBHCLjJT4WBrwXw5kXTwjpwIPtXCLOuCAUJasl0Cop3g7AoSNb9zjgJQRvtYq4QRr4dAGnJ9JNgfuGMIYbNwIcQNBbhatl5X6NoHIr7CVgPgnZGluhpu5h7yUVq87QE0HTjUQCv/+ecn1pIp3dKKat+9nRdj57mhVtB9KLn+2ZbWm90DLByIH2zUPxlQ/G4iuDOQwXLDhPSlSlDEzV8AuAqR14JuuXQaO6rVrp+ESAtYRwGYVkYA36GzzoAY9IuWctbpeIcAG1gcABKuAVE4B1lmX8izMlny8T1oZcZtM19L1TKHh0Z9bNzDkA8aQwzw3Sqef6MWNxJS3kdOK37R4866nQPEbLlorUhFICWMH52lJgxXClZ987l9E2dWML4gAgbnMPYJeuKQIDF+qYLL8FUY4jQVlvmXukGQNPTjwO807HxGxbMOyp3/T3b3lkpWKGbSyNQ37v5F7S2PJrb3w1AfCC9hhXvc+cFxHXjMvtDe4DbzasjVfVTXYhNW+Zz3QDEEmmTiIddgKi4Zvyz7LG2AN4UdNMBTSfeTgiVAkdRSxjfOTOAAVmRVrKrCOhGkQAd9VlwfWAROgJxPb2dwc+4daDQX/7cOjgXiPhtxioWGHOTSby9Usw/Gwoglhq6lmp0pF4GmJw/I27odBY0ZsC3zWnKEV5SKeR/DAXgRiFpPMCMd1wGxmuVkrWlkyjEEsarRHjMjSLhwXLRereVfqttSJpujAC4p9OW9LYegI9saa1vB996Ha8ZXIQZ4fTtAgBTVYhlEzJ7op2xPt28Mgo12dRBj1pq7x85OSeARkHexeA9DQP7bD12BzIZ5WswkxGarHwKYK0bNdC6ssztDUpd8JVMN94C8JBjqN1un3WHeNuW1sNBzuugAU+fbl4cZXWkfifkjC3zz/upaPrQcwBlnMVTJbFkQmZ/D7IdCsAR8my1Mbtk3egLkDC+AaG/0y0aGAHHmXergeh+YjXthWASvWB+r/6usy0aDsBzSwoMa5vbT+hBNFtwVcrsq9XUeKBzAJGIWHGwkJ0IIxu6BrwAfg6Cvs95DjQVgxwEfT+vAH4b0rv5/pcULE8NXtZTE6eaExGgXQTl/nIxBAG8qTkBZyLq8sOFkV/Oaw3UW9H4GMC6AMN7bGndHdZ56CJ0h9GtGy+lC6Z3gHFfY9l4/UyB8D7/07ut8sXuXzsB+BfElbcwrolkGwAAAABJRU5ErkJggg==", "type": "command" } } -} \ No newline at end of file +} From fd14e41d530633ee22f075011a01bc356e3a4add Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Wed, 13 Nov 2024 16:28:55 +0100 Subject: [PATCH 22/31] Code improvements and adjustments [skip-ci] --- .../IdeaModule/IdeaField/useIdeaField.tsx | 40 +++++++++++-------- .../src/components/IdeaModule/IdeaModule.tsx | 9 ++--- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx index 943c06e4c..f81244fdb 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx @@ -23,8 +23,6 @@ import { useIdeaModuleContext } from '../context' export function useIdeaField(props: IIdeaFieldProps) { const context = useIdeaModuleContext() - const isInProcessing = context.state.selectedIdea.item.processing - const renderValueForField = () => { let icon = TagMultipleFilled @@ -156,32 +154,42 @@ export function useIdeaField(props: IIdeaFieldProps) { const processing = context.state.configuration.processing const registration = context.state.configuration.registration - const approveValue = isInProcessing - ? processing.find((p) => p.key === 'approve')?.recommendation - : registration.find((p) => p.key === 'approve')?.recommendation - const considerationValue = isInProcessing - ? processing.find((p) => p.key === 'consideration')?.recommendation - : registration.find((p) => p.key === 'consideration')?.recommendation - const rejectValue = isInProcessing - ? processing.find((p) => p.key === 'reject')?.recommendation - : registration.find((p) => p.key === 'reject')?.recommendation + const approveValue = [ + processing.find((p) => p.key === 'approve')?.recommendation, + registration.find((p) => p.key === 'approve')?.recommendation + ] + const considerationValue = [ + processing.find((p) => p.key === 'consideration')?.recommendation, + registration.find((p) => p.key === 'consideration')?.recommendation + ] + const rejectValue = [ + processing.find((p) => p.key === 'reject')?.recommendation, + registration.find((p) => p.key === 'reject')?.recommendation + ] const statusStyles = { - [approveValue]: { + approve: { backgroundColor: 'var(--colorPaletteLightGreenBackground2)', icon: getFluentIcon('CheckmarkCircle') }, - [considerationValue]: { + consideration: { backgroundColor: 'var(--colorPaletteYellowBackground2)', icon: getFluentIcon('Edit') }, - [rejectValue]: { + reject: { backgroundColor: 'var(--colorPaletteRedBackground2)', icon: getFluentIcon('DismissCircle') } } - const { backgroundColor, icon } = statusStyles[textValue] || { + const statusKey = ['approve', 'consideration', 'reject'].find( + (key) => + approveValue.includes(textValue) || + considerationValue.includes(textValue) || + rejectValue.includes(textValue) + ) + + const { backgroundColor, icon } = statusStyles[statusKey] || { backgroundColor: 'var(--colorNeutralBackground2)', icon: null } @@ -215,5 +223,5 @@ export function useIdeaField(props: IIdeaFieldProps) { } } - return { renderValueForField, isInProcessing } + return { renderValueForField } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index 394f3aa8d..a549018fb 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -97,7 +97,7 @@ export const IdeaModule: FC = (props) => { const backgroundColor = statusStyles[statusValue]?.backgroundColor || 'var(--colorNeutralBackground2)' - const borderColor = statusStyles[statusValue]?.borderColor || 'var(--colorNeutralBorder2)' + const borderColor = statusStyles[statusValue]?.borderColor || 'var(--colorNeutralBackground4)' const fieldValues = isInProcessing ? state.selectedIdea.processingFieldValues @@ -236,10 +236,9 @@ export const IdeaModule: FC = (props) => {
- {state.selectedIdea.registeredFieldValues - .map((model, idx) => ( - - ))} + {state.selectedIdea.registeredFieldValues.map((model, idx) => ( + + ))}
From 93f2369b4a14d7885609ac2db9143793a3e7ad82 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Thu, 14 Nov 2024 10:23:49 +0100 Subject: [PATCH 23/31] Add provisionUrl property + some styling fixes [packages-only] --- .../IdeaModule/IdeaField/useIdeaField.tsx | 14 ++++++++------ .../components/IdeaModule/IdeaModule.module.scss | 11 +++++++++++ .../src/components/IdeaModule/IdeaModule.tsx | 2 +- .../IdeaModule/IdeaPhaseBar/IdeaPhaseBar.tsx | 4 ++-- .../src/components/IdeaModule/types.ts | 1 + .../src/webparts/ideaModule/index.ts | 4 ++++ 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx index f81244fdb..0f4e1caa0 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaField/useIdeaField.tsx @@ -182,12 +182,14 @@ export function useIdeaField(props: IIdeaFieldProps) { } } - const statusKey = ['approve', 'consideration', 'reject'].find( - (key) => - approveValue.includes(textValue) || - considerationValue.includes(textValue) || - rejectValue.includes(textValue) - ) + let statusKey = null + if (approveValue.includes(textValue)) { + statusKey = 'approve' + } else if (considerationValue.includes(textValue)) { + statusKey = 'consideration' + } else if (rejectValue.includes(textValue)) { + statusKey = 'reject' + } const { backgroundColor, icon } = statusStyles[statusKey] || { backgroundColor: 'var(--colorNeutralBackground2)', diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss index 91adfb662..b742125fb 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss @@ -39,6 +39,17 @@ } } + .accordion { + width: fit-content; + padding: 0 8px; + margin-bottom: 16px; + + &:hover { + background-color: var(--colorNeutralBackground1Hover); + border-radius: var(--borderRadiusLarge); + } + } + .idea { display: grid; grid-template-columns: repeat(auto-fit, minmax(450px, 1fr)); diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index a549018fb..87403c5cb 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -231,7 +231,7 @@ export const IdeaModule: FC = (props) => { {state.selectedIdea.item.processing && ( - + Registrert idé diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/IdeaPhaseBar.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/IdeaPhaseBar.tsx index 31ac5eac1..817dd1464 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/IdeaPhaseBar.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaPhaseBar/IdeaPhaseBar.tsx @@ -35,7 +35,7 @@ export const IdeaPhaseBar: FC = () => { <> {phases.map((phase) => { - if (phase.phase === IdeaPhase.Provisioned) { + if (phase.phase === IdeaPhase.Provisioned && context.props.provisionUrl) { return ( { } size='medium' disabled={context.state.phase !== IdeaPhase.ApprovedForConcept} - provisionUrl='https://puzzlepart.sharepoint.com/sites/bestillingsportalen' + provisionUrl={context.props.provisionUrl} /> ) } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts index 1ac0007e3..9baa784a0 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/types.ts @@ -26,6 +26,7 @@ export interface IIdeaModuleProps extends IBaseComponentProps { listSize?: 'extra-small' | 'small' | 'medium' hiddenRegFields?: string[] hiddenProcFields?: string[] + provisionUrl?: string } export interface IIdeaModuleState { diff --git a/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts b/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts index f45b3a648..0bfe11f59 100644 --- a/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/webparts/ideaModule/index.ts @@ -45,6 +45,10 @@ export default class IdeaModuleWebPart extends BasePortfolioWebPart Date: Thu, 14 Nov 2024 11:04:18 +0100 Subject: [PATCH 24/31] Add CT to IdeaColumns and fix view --- .../Lists/Prosjektinnholdskolonner.xml | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml b/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml index 1241d2a2e..ea95b4187 100644 --- a/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml +++ b/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml @@ -52,6 +52,12 @@ + + + + {resource:ContentTypes_IdeaColumn_Name} + + @@ -772,6 +778,7 @@ 200 300 True + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995
1010 @@ -783,6 +790,7 @@ 200 300 True + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1020 @@ -794,6 +802,7 @@ 200 300 True + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1030 @@ -806,6 +815,7 @@ 200 True True + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1040 @@ -817,6 +827,7 @@ 200 300 True + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1050 @@ -828,6 +839,7 @@ 200 300 True + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1060 @@ -839,6 +851,7 @@ 200 300 True + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1070 @@ -850,6 +863,7 @@ 200 300 True + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1080 @@ -862,6 +876,7 @@ 200 True True + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1090 @@ -873,6 +888,7 @@ 100 200 True + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1100 @@ -883,6 +899,7 @@ Idémodul 200 300 + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1110 @@ -893,6 +910,7 @@ Idémodul 200 300 + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1120 @@ -903,6 +921,7 @@ Idémodul 200 300 + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1130 @@ -913,6 +932,7 @@ Idémodul 200 300 + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1140 @@ -923,6 +943,7 @@ Idémodul 200 300 + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1150 @@ -933,6 +954,7 @@ Idémodul 200 300 + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1160 @@ -944,6 +966,7 @@ 200 300 True + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1170 @@ -956,6 +979,7 @@ 200 True True + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1180 @@ -967,6 +991,7 @@ 200 300 True + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1190 @@ -978,6 +1003,7 @@ 100 200 True + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 1200 @@ -988,6 +1014,7 @@ Idémodul 200 300 + 0x0100F2E3AE5113CA4CCDA12439D77D6DD995 From 6f3cced0bd0c5ac509a5a448fc7e8f87492ad18d Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Thu, 14 Nov 2024 12:09:10 +0100 Subject: [PATCH 25/31] Refine field copying logic in registrationList [packages-only] --- .../src/extensions/ideaRegistration/index.tsx | 17 +++++++++++++++-- .../src/components/IdeaModule/IdeaModule.tsx | 6 +++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/SharePointFramework/PortfolioExtensions/src/extensions/ideaRegistration/index.tsx b/SharePointFramework/PortfolioExtensions/src/extensions/ideaRegistration/index.tsx index 632cac4ac..a1b5b8e04 100644 --- a/SharePointFramework/PortfolioExtensions/src/extensions/ideaRegistration/index.tsx +++ b/SharePointFramework/PortfolioExtensions/src/extensions/ideaRegistration/index.tsx @@ -259,7 +259,16 @@ export default class IdeaRegistrationCommand extends BaseListViewCommandSet const field = registrationList.fields.find( (f) => f.internalName === col.InternalName || f.displayName === col.Title ) - return field && columns.some((contentCol) => contentCol.internalName === col.InternalName) + + const fieldsToCopy = + field && + columns.find( + (contentCol) => + contentCol.internalName === field.internalName || + contentCol.fieldName === field.displayName + ) + + return fieldsToCopy }) const copyData = columnsToCopy @@ -270,8 +279,12 @@ export default class IdeaRegistrationCommand extends BaseListViewCommandSet } } + const internalName = registrationList.fields.find( + (fld) => fld.displayName === col.Title || fld.internalName === col.InternalName + )?.internalName + return { - [col.InternalName]: registrationList.getValueByName(col.InternalName) + [col.InternalName]: registrationList.getValueByName(internalName) } }) .reduce((acc, val) => ({ ...acc, ...val }), {}) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index 87403c5cb..caec221e9 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -231,7 +231,11 @@ export const IdeaModule: FC = (props) => { {state.selectedIdea.item.processing && ( - + Registrert idé From bb7d907ae758c32bd767938bca9b19f7c6c9f5d5 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Fri, 15 Nov 2024 14:49:28 +0100 Subject: [PATCH 26/31] Add Commandbar --- .../IdeaModule/Commands/Commands.module.scss | 7 ++ .../IdeaModule/Commands/Commands.tsx | 14 +++ .../components/IdeaModule/Commands/index.ts | 1 + .../IdeaModule/Commands/useCreateNewIdea.ts | 49 +++++++++ .../IdeaModule/Commands/useDecision.ts | 14 +++ .../IdeaModule/Commands/useDelete.ts | 14 +++ .../IdeaModule/Commands/useToolbarItems.tsx | 55 ++++++++++ .../IdeaModule/IdeaModule.module.scss | 3 +- .../src/components/IdeaModule/IdeaModule.tsx | 86 +-------------- .../components/IdeaModule/useIdeaModule.tsx | 103 ++++++++++++++++-- .../PortfolioWebParts/src/data/index.ts | 2 +- 11 files changed, 254 insertions(+), 94 deletions(-) create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/Commands.module.scss create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/Commands.tsx create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/index.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useCreateNewIdea.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useDecision.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useDelete.ts create mode 100644 SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useToolbarItems.tsx diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/Commands.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/Commands.module.scss new file mode 100644 index 000000000..2f3aa7104 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/Commands.module.scss @@ -0,0 +1,7 @@ +.commands { + display: flex; + flex-direction: column; + width: 100%; + height: 42px; + margin: 0px auto; +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/Commands.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/Commands.tsx new file mode 100644 index 000000000..0ea79c6c6 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/Commands.tsx @@ -0,0 +1,14 @@ +import React, { FC } from 'react' +import { Toolbar } from 'pp365-shared-library' +import { useToolbarItems } from './useToolbarItems' +import styles from './Commands.module.scss' + +export const Commands: FC = () => { + const { menuItems, farMenuItems } = useToolbarItems() + + return ( +
+ +
+ ) +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/index.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/index.ts new file mode 100644 index 000000000..aad501dd2 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/index.ts @@ -0,0 +1 @@ +export * from './Commands' diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useCreateNewIdea.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useCreateNewIdea.ts new file mode 100644 index 000000000..3bed7e283 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useCreateNewIdea.ts @@ -0,0 +1,49 @@ +import { useIdeaModuleContext } from '../context' + +/** + * Hook for creating new status reports. Returns a callback function + * for creating a new status report. + */ +export const useCreateNewIdea = () => { + const context = useIdeaModuleContext() + + console.log('create', context) + // const { state, dispatch, props } = useProjectStatusContext() + // const [lastReport] = state.data.reports + + // /** + // * Get the report fields that are not read-only and not the + // * `GtSectionDataJson` or `GtLastReportDate` fields. + // */ + // const reportFields = state.data.reportFields.filter( + // (field) => + // !field.isReadOnly && !['GtSectionDataJson', 'GtLastReportDate'].includes(field.internalName) + // ) + + // /** + // * Creates a new status report with the given properties and adds it to the portal. + // * If there is a last report, it will use its field values for the new report. + // */ + // const createNewStatusReport = async () => { + // let properties: Record = { + // Title: format(strings.NewStatusReportTitle, props.webTitle), + // GtSiteId: props.siteId, + // GtModerationStatus: strings.GtModerationStatus_Choice_Draft + // } + // if (lastReport?.fieldValues) { + // properties = reportFields.reduce((obj, field) => { + // const fieldValue = lastReport.fieldValues.get(field.internalName)?.value + // if (fieldValue && !obj[field.InternalName]) obj[field.internalName] = fieldValue + // return obj + // }, properties) + // } + // const report = await SPDataAdapter.portalDataService.addStatusReport( + // properties, + // state.data.properties.templateParameters?.ProjectStatusContentTypeId + // ) + // dispatch(SELECT_REPORT({ report })) + // dispatch(OPEN_PANEL({ name: 'EditStatusPanel', headerText: strings.NewStatusPanelTitle })) + // } + + // return createNewStatusReport +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useDecision.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useDecision.ts new file mode 100644 index 000000000..c1cef8c27 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useDecision.ts @@ -0,0 +1,14 @@ +import { useIdeaModuleContext } from '../context' + + +/** + * Hook for setting the decision for ideas. Returns a callback function for setting the decision. + * + * @returns A function callback that returns a promise of void + */ +export const useDecision = () => { + const context = useIdeaModuleContext() + return async (): Promise => { + console.log('decide', context) + } +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useDelete.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useDelete.ts new file mode 100644 index 000000000..2cd2fd3e2 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useDelete.ts @@ -0,0 +1,14 @@ +import { useIdeaModuleContext } from '../context' + +/** + * Hook for deletion of idea. Returns a callback function + * for deleting the selected report. + * + * @returns A function callback + */ +export const useDelete = () => { + const context = useIdeaModuleContext() + return async (): Promise => { + console.log('delete', context) + } +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useToolbarItems.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useToolbarItems.tsx new file mode 100644 index 000000000..2e44d50e0 --- /dev/null +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useToolbarItems.tsx @@ -0,0 +1,55 @@ +import { ListMenuItem } from 'pp365-shared-library' +import { useMemo } from 'react' +import { useIdeaModuleContext } from '../context' +import { useCreateNewIdea } from './useCreateNewIdea' +import { useDelete } from './useDelete' +import { useDecision } from './useDecision' + +/** + * Returns an array of menu items for the toolbar in the ProjectStatus component. + * + * @returns An array of IListMenuItem objects representing the toolbar items. + */ +export function useToolbarItems() { + const context = useIdeaModuleContext() + const createNewStatusReport = useCreateNewIdea() + const deleteIdea = useDelete() + const decideIdea = useDecision() + + const menuItems = useMemo( + () => + [ + new ListMenuItem('Ny idé', 'Opprett en ny idé') + .setIcon('QuizNew') + .setOnClick(() => { + createNewStatusReport + }), + new ListMenuItem('Rediger', 'Rediger idéen') + .setIcon('Edit') + .setOnClick(() => { + console.log('edit') + }), + new ListMenuItem('Godkjenn', 'Godkjenn idéen') + .setIcon('CloudArrowUp') + .setOnClick(() => { + decideIdea() + }) + ].filter(Boolean), + [context.state] + ) + + const farMenuItems = useMemo( + () => + [ + new ListMenuItem('Slett', 'Slett idéen') + + .setIcon('Delete') + .setOnClick(() => { + deleteIdea() + }) + ].filter(Boolean), + [context.state] + ) + + return { menuItems, farMenuItems } +} diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss index b742125fb..2fa2774ce 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss @@ -18,7 +18,7 @@ flex-direction: column; gap: 16px; - .toolbar { + .phasebar { display: flex; flex-direction: column; gap: 16px; @@ -42,7 +42,6 @@ .accordion { width: fit-content; padding: 0 8px; - margin-bottom: 16px; &:hover { background-color: var(--colorNeutralBackground1Hover); diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index caec221e9..a04de60bd 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -1,4 +1,4 @@ -import React, { FC, useState } from 'react' +import React, { FC } from 'react' import styles from './IdeaModule.module.scss' import { IdeaModuleContext } from './context' import { IIdeaModuleProps } from './types' @@ -8,7 +8,6 @@ import { AccordionHeader, AccordionItem, AccordionPanel, - AccordionToggleEventHandler, Divider, FluentProvider, IdPrefixProvider, @@ -40,90 +39,16 @@ import { } from '@fluentui/react-icons' import { IdeaField } from './IdeaField' import { IdeaPhaseBar } from './IdeaPhaseBar' +import { Commands } from './Commands' const Dashboard = bundleIcon(Board20Filled, Board20Regular) const Lightbulb = bundleIcon(Lightbulb20Filled, Lightbulb20Regular) const JobPostings = bundleIcon(NotePin20Filled, NotePin20Regular) export const IdeaModule: FC = (props) => { - const { state, setState, getSelectedIdea, isOpen, renderHamburger, fluentProviderId } = + const { state, setState, getSelectedIdea, isOpen, renderHamburger, renderStatus, handleToggle, openItems, ignoreFields, fluentProviderId } = useIdeaModule(props) - const [openItems, setOpenItems] = useState(['registration']) - const handleToggle: AccordionToggleEventHandler = (event, data) => { - setOpenItems(data.openItems) - } - - const ignoreFields = [ - 'GtIdeaRecommendation', - 'GtIdeaRecommendationComment', - 'GtIdeaDecision', - 'GtIdeaDecisionComment' - ] - - const renderStatus = () => { - const isInProcessing = state.selectedIdea.item.processing - const processing = state.configuration.processing - const registration = state.configuration.registration - - const approveValue = isInProcessing - ? processing.find((p) => p.key === 'approve')?.recommendation - : registration.find((p) => p.key === 'approve')?.recommendation - const considerationValue = isInProcessing - ? processing.find((p) => p.key === 'consideration')?.recommendation - : registration.find((p) => p.key === 'consideration')?.recommendation - const rejectValue = isInProcessing - ? processing.find((p) => p.key === 'reject')?.recommendation - : registration.find((p) => p.key === 'reject')?.recommendation - - const statusStyles = { - [approveValue]: { - backgroundColor: 'var(--colorPaletteLightGreenBackground1)', - borderColor: 'var(--colorPaletteLightGreenBorder1)' - }, - [considerationValue]: { - backgroundColor: 'var(--colorPaletteYellowBackground1)', - borderColor: 'var(--colorPaletteYellowBorder1)' - }, - [rejectValue]: { - backgroundColor: 'var(--colorPaletteRedBackground1)', - borderColor: 'var(--colorPaletteRedBorder1)' - } - } - - const statusValue = isInProcessing - ? state.selectedIdea.item.processing.GtIdeaDecision - : state.selectedIdea.item.GtIdeaRecommendation - - const backgroundColor = - statusStyles[statusValue]?.backgroundColor || 'var(--colorNeutralBackground2)' - const borderColor = statusStyles[statusValue]?.borderColor || 'var(--colorNeutralBackground4)' - - const fieldValues = isInProcessing - ? state.selectedIdea.processingFieldValues - : state.selectedIdea.registeredFieldValues - - const filterKey = isInProcessing ? 'GtIdeaDecision' : 'GtIdeaRecommendation' - - return ( -
-

Status

-
- {fieldValues - .filter((model) => model.internalName.includes(filterKey)) - .map((model, idx) => ( -
- -
- ))} -
-
- ) - } - return ( @@ -211,8 +136,9 @@ export const IdeaModule: FC = (props) => { )} {state.selectedIdea ? ( <> +
-
+
{!isOpen && renderHamburger()}
@@ -238,7 +164,7 @@ export const IdeaModule: FC = (props) => { > Registrert idé - +
{state.selectedIdea.registeredFieldValues.map((model, idx) => ( diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.tsx index 9fa2b4592..dd2acc209 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/useIdeaModule.tsx @@ -1,6 +1,6 @@ /* eslint-disable no-console */ import React from 'react' -import { Tooltip, useId } from '@fluentui/react-components' +import { AccordionToggleEventHandler, Tooltip, useId } from '@fluentui/react-components' import { IdeaPhase, IIdeaModuleHashState, IIdeaModuleProps } from './types' import { useIdeaModuleState } from './useIdeaModuleState' import { useIdeaModuleDataFetch } from './useIdeaModuleDataFetch' @@ -8,6 +8,8 @@ import { EditableSPField, ItemFieldValues, parseUrlHash, setUrlHash } from 'pp36 import _ from 'lodash' import { useEffect, useState } from 'react' import { Hamburger } from '@fluentui/react-nav-preview' +import styles from './IdeaModule.module.scss' +import { IdeaField } from './IdeaField' /** * Component logic hook for `IdeaModule` component. @@ -19,16 +21,6 @@ export function useIdeaModule(props: IIdeaModuleProps) { useIdeaModuleDataFetch(props, state.refetch, setState) - const [isOpen, setIsOpen] = useState(true) - - const renderHamburger = () => { - return ( - - setIsOpen(!isOpen)} /> - - ) - } - const getSelectedIdea = () => { const hashState = parseUrlHash() @@ -121,6 +113,91 @@ export function useIdeaModule(props: IIdeaModuleProps) { }) } + const [openItems, setOpenItems] = useState(['registration']) + const [isOpen, setIsOpen] = useState(true) + + const handleToggle: AccordionToggleEventHandler = (event, data) => { + setOpenItems(data.openItems) + } + + const ignoreFields = [ + 'GtIdeaRecommendation', + 'GtIdeaRecommendationComment', + 'GtIdeaDecision', + 'GtIdeaDecisionComment' + ] + + const renderHamburger = () => { + return ( + + setIsOpen(!isOpen)} /> + + ) + } + + const renderStatus = () => { + const isInProcessing = state.selectedIdea.item.processing + const processing = state.configuration.processing + const registration = state.configuration.registration + + const approveValue = isInProcessing + ? processing.find((p) => p.key === 'approve')?.recommendation + : registration.find((p) => p.key === 'approve')?.recommendation + const considerationValue = isInProcessing + ? processing.find((p) => p.key === 'consideration')?.recommendation + : registration.find((p) => p.key === 'consideration')?.recommendation + const rejectValue = isInProcessing + ? processing.find((p) => p.key === 'reject')?.recommendation + : registration.find((p) => p.key === 'reject')?.recommendation + + const statusStyles = { + [approveValue]: { + backgroundColor: 'var(--colorPaletteLightGreenBackground1)', + borderColor: 'var(--colorPaletteLightGreenBorder1)' + }, + [considerationValue]: { + backgroundColor: 'var(--colorPaletteYellowBackground1)', + borderColor: 'var(--colorPaletteYellowBorder1)' + }, + [rejectValue]: { + backgroundColor: 'var(--colorPaletteRedBackground1)', + borderColor: 'var(--colorPaletteRedBorder1)' + } + } + + const statusValue = isInProcessing + ? state.selectedIdea.item.processing.GtIdeaDecision + : state.selectedIdea.item.GtIdeaRecommendation + + const backgroundColor = + statusStyles[statusValue]?.backgroundColor || 'var(--colorNeutralBackground2)' + const borderColor = statusStyles[statusValue]?.borderColor || 'var(--colorNeutralBackground4)' + + const fieldValues = isInProcessing + ? state.selectedIdea.processingFieldValues + : state.selectedIdea.registeredFieldValues + + const filterKey = isInProcessing ? 'GtIdeaDecision' : 'GtIdeaRecommendation' + + return ( +
+

Status

+
+ {fieldValues + .filter((model) => model.internalName.includes(filterKey)) + .map((model, idx) => ( +
+ +
+ ))} +
+
+ ) + } + useEffect(() => { if (!state.loading) { getSelectedIdea() @@ -132,7 +209,11 @@ export function useIdeaModule(props: IIdeaModuleProps) { setState, getSelectedIdea, renderHamburger, + renderStatus, + ignoreFields, + handleToggle, isOpen, + openItems, fluentProviderId } } diff --git a/SharePointFramework/PortfolioWebParts/src/data/index.ts b/SharePointFramework/PortfolioWebParts/src/data/index.ts index fd770577e..b10fe986c 100644 --- a/SharePointFramework/PortfolioWebParts/src/data/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/data/index.ts @@ -1083,7 +1083,7 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { const fields = await list.fields .select(...getClassProperties(SPField)) .filter( - "substringof('Gt', InternalName) or InternalName eq 'Title' or InternalName eq 'Id'" + 'substringof(\'Gt\', InternalName) or InternalName eq \'Title\' or InternalName eq \'Id\'' )() const userFields = fields From 703e4b9f5f70808a8684edb688f2a54c30629488 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Wed, 27 Nov 2024 14:13:02 +0100 Subject: [PATCH 27/31] Build IdeaModule pre-release --- .github/workflows/build-release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 04cbd6a0c..0331d2198 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -2,7 +2,9 @@ name: Build release (main) on: push: - branches: [main] + branches: + - main + - feat/ideamodule paths: - 'SharePointFramework/**' - 'Install/**' From b9fa0035690a3836644a1e521670e3faa77f0ba7 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Mon, 2 Dec 2024 18:13:58 +0100 Subject: [PATCH 28/31] Bugfix for URL type fields [skpi-ci] --- .github/workflows/build-release.yml | 1 - .../shared-library/src/util/createFieldValueMap.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 0331d2198..32fcc62c2 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -4,7 +4,6 @@ on: push: branches: - main - - feat/ideamodule paths: - 'SharePointFramework/**' - 'Install/**' diff --git a/SharePointFramework/shared-library/src/util/createFieldValueMap.ts b/SharePointFramework/shared-library/src/util/createFieldValueMap.ts index e415409f4..ca9935b4d 100644 --- a/SharePointFramework/shared-library/src/util/createFieldValueMap.ts +++ b/SharePointFramework/shared-library/src/util/createFieldValueMap.ts @@ -13,8 +13,8 @@ export const createFieldValueMap = (): Map { try { - const url = value['Url'] - const description = value['Description'] + const url = value?.['Url'] + const description = value?.['Description'] return { url, description } } catch (error) { throw new Error(`Feil ved mapping av URL felt: ${error}`) From ad3845d6ff79f1395412d8077e5e45834f3421d2 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Mon, 2 Dec 2024 19:18:06 +0100 Subject: [PATCH 29/31] Minor adjustment [skip-ci] --- .../src/components/IdeaModule/IdeaModule.tsx | 15 ++++++++------- .../Objects/Lists/Prosjektinnholdskolonner.xml | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index a04de60bd..69da167f0 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -66,6 +66,7 @@ export const IdeaModule: FC = (props) => { open={isOpen} type='inline' size='small' + style={{ width: '320px' }} > @@ -74,13 +75,13 @@ export const IdeaModule: FC = (props) => { Idémodul - } value='total'> + {/* } value='total'> Totaloversikt - + */} Registrering - } value='registrering'> + {/* } value='registrering'> Oversikt - + */} }>Registrerte idéer @@ -102,9 +103,9 @@ export const IdeaModule: FC = (props) => { Behandling - } value='behandling'> + {/* } value='behandling'> Oversikt - + */} }>Idéer i behandling @@ -136,7 +137,7 @@ export const IdeaModule: FC = (props) => { )} {state.selectedIdea ? ( <> - + {/* */}
{!isOpen && renderHamburger()}
diff --git a/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml b/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml index ea95b4187..8858169be 100644 --- a/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml +++ b/Templates/Portfolio/Objects/Lists/Prosjektinnholdskolonner.xml @@ -831,7 +831,7 @@ 1050 - Løsningsforslag + Overordnet gjennomføringsplan GtIdeaExecutionPlan GtIdeaExecutionPlanOWSMTXT Note From 89201704b7cb31a6c2110044104fc906b7ed4778 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Thu, 12 Dec 2024 13:57:29 +0100 Subject: [PATCH 30/31] Fix styling and nav logic --- .../IdeaModule/IdeaModule.module.scss | 10 +++++++++- .../src/components/IdeaModule/IdeaModule.tsx | 18 +++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss index 2fa2774ce..fab8b939d 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.module.scss @@ -2,7 +2,6 @@ margin: 0; overflow: hidden; display: flex; - height: 100vh; .content { flex: 1; @@ -87,6 +86,15 @@ } } } + + .nav { + min-height: -webkit-fill-available; + width: 340px; + + .navBody { + padding-bottom: 20px; + } + } } .loading { diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index 69da167f0..0b7f0a2d9 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -21,7 +21,6 @@ import { NavDrawer, NavDrawerBody, NavDrawerHeader, - NavItem, NavSectionHeader, NavSubItem, NavSubItemGroup, @@ -39,7 +38,6 @@ import { } from '@fluentui/react-icons' import { IdeaField } from './IdeaField' import { IdeaPhaseBar } from './IdeaPhaseBar' -import { Commands } from './Commands' const Dashboard = bundleIcon(Board20Filled, Board20Regular) const Lightbulb = bundleIcon(Lightbulb20Filled, Lightbulb20Regular) @@ -59,21 +57,19 @@ export const IdeaModule: FC = (props) => {
- + {renderHamburger()} - + Idémodul {/* } value='total'> Totaloversikt @@ -83,7 +79,7 @@ export const IdeaModule: FC = (props) => { Oversikt */} - }>Registrerte idéer + } value='registrering'>Registrerte idéer {state.ideas.data.items .filter((idea) => !idea.processing) @@ -107,7 +103,7 @@ export const IdeaModule: FC = (props) => { Oversikt */} - }>Idéer i behandling + } value='behandling'>Idéer i behandling {state.ideas.data.items .filter((idea) => idea.processing) From 4ed5ca046453aacf94ce2f092d58a3b1c289139a Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Thu, 12 Dec 2024 14:36:14 +0100 Subject: [PATCH 31/31] Changelog ++ --- CHANGELOG.md | 1 + .../IdeaModule/Commands/useCreateNewIdea.ts | 46 ++----------------- .../IdeaModule/Commands/useDecision.ts | 4 +- .../IdeaModule/Commands/useDelete.ts | 3 +- .../IdeaModule/Commands/useToolbarItems.tsx | 33 +++++-------- .../src/components/IdeaModule/IdeaModule.tsx | 32 ++++++++++--- .../PortfolioWebParts/src/data/index.ts | 2 +- 7 files changed, 48 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f706b5957..a638cb439 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Sjekk ut [release notes](./releasenotes/1.10.0.md) for høydepunkter og mer deta ### Ny funksjonalitet - Lagt til instrumentvisning for 'Siste måling' på Gevinstoversikt [#1572](https://github.com/Puzzlepart/prosjektportalen365/issues/1572) +- Ny Idémodul for visning av idéer (registrering/behandling) samt feltkonfigurasjon slik at relevant data fra idéregistreringen kan videreføres til behandling [#1573](https://github.com/Puzzlepart/prosjektportalen365/issues/1573) ## 1.10.1 - TBA diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useCreateNewIdea.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useCreateNewIdea.ts index 3bed7e283..2d9ba8e49 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useCreateNewIdea.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useCreateNewIdea.ts @@ -1,49 +1,11 @@ import { useIdeaModuleContext } from '../context' /** - * Hook for creating new status reports. Returns a callback function - * for creating a new status report. + * Hook for creating new ideas. Returns a callback function + * for creating a new idea */ export const useCreateNewIdea = () => { const context = useIdeaModuleContext() - - console.log('create', context) - // const { state, dispatch, props } = useProjectStatusContext() - // const [lastReport] = state.data.reports - - // /** - // * Get the report fields that are not read-only and not the - // * `GtSectionDataJson` or `GtLastReportDate` fields. - // */ - // const reportFields = state.data.reportFields.filter( - // (field) => - // !field.isReadOnly && !['GtSectionDataJson', 'GtLastReportDate'].includes(field.internalName) - // ) - - // /** - // * Creates a new status report with the given properties and adds it to the portal. - // * If there is a last report, it will use its field values for the new report. - // */ - // const createNewStatusReport = async () => { - // let properties: Record = { - // Title: format(strings.NewStatusReportTitle, props.webTitle), - // GtSiteId: props.siteId, - // GtModerationStatus: strings.GtModerationStatus_Choice_Draft - // } - // if (lastReport?.fieldValues) { - // properties = reportFields.reduce((obj, field) => { - // const fieldValue = lastReport.fieldValues.get(field.internalName)?.value - // if (fieldValue && !obj[field.InternalName]) obj[field.internalName] = fieldValue - // return obj - // }, properties) - // } - // const report = await SPDataAdapter.portalDataService.addStatusReport( - // properties, - // state.data.properties.templateParameters?.ProjectStatusContentTypeId - // ) - // dispatch(SELECT_REPORT({ report })) - // dispatch(OPEN_PANEL({ name: 'EditStatusPanel', headerText: strings.NewStatusPanelTitle })) - // } - - // return createNewStatusReport + // TODO: Implement the useCreateNewIdea hook + // console.log('create', context) } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useDecision.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useDecision.ts index c1cef8c27..3d8db707f 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useDecision.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useDecision.ts @@ -1,6 +1,5 @@ import { useIdeaModuleContext } from '../context' - /** * Hook for setting the decision for ideas. Returns a callback function for setting the decision. * @@ -9,6 +8,7 @@ import { useIdeaModuleContext } from '../context' export const useDecision = () => { const context = useIdeaModuleContext() return async (): Promise => { - console.log('decide', context) + // TODO: Implement the useDecision hook + // console.log('decide', context) } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useDelete.ts b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useDelete.ts index 2cd2fd3e2..6706d4f1b 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useDelete.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useDelete.ts @@ -9,6 +9,7 @@ import { useIdeaModuleContext } from '../context' export const useDelete = () => { const context = useIdeaModuleContext() return async (): Promise => { - console.log('delete', context) + // TODO: Implement the useDelete hook + // console.log('delete', context) } } diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useToolbarItems.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useToolbarItems.tsx index 2e44d50e0..a48b92261 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useToolbarItems.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/Commands/useToolbarItems.tsx @@ -19,21 +19,15 @@ export function useToolbarItems() { const menuItems = useMemo( () => [ - new ListMenuItem('Ny idé', 'Opprett en ny idé') - .setIcon('QuizNew') - .setOnClick(() => { - createNewStatusReport - }), - new ListMenuItem('Rediger', 'Rediger idéen') - .setIcon('Edit') - .setOnClick(() => { - console.log('edit') - }), - new ListMenuItem('Godkjenn', 'Godkjenn idéen') - .setIcon('CloudArrowUp') - .setOnClick(() => { - decideIdea() - }) + new ListMenuItem('Ny idé', 'Opprett en ny idé').setIcon('QuizNew').setOnClick(() => { + createNewStatusReport + }), + new ListMenuItem('Rediger', 'Rediger idéen').setIcon('Edit').setOnClick(() => { + // console.log('edit') + }), + new ListMenuItem('Godkjenn', 'Godkjenn idéen').setIcon('CloudArrowUp').setOnClick(() => { + decideIdea() + }) ].filter(Boolean), [context.state] ) @@ -41,12 +35,9 @@ export function useToolbarItems() { const farMenuItems = useMemo( () => [ - new ListMenuItem('Slett', 'Slett idéen') - - .setIcon('Delete') - .setOnClick(() => { - deleteIdea() - }) + new ListMenuItem('Slett', 'Slett idéen').setIcon('Delete').setOnClick(() => { + deleteIdea() + }) ].filter(Boolean), [context.state] ) diff --git a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx index 0b7f0a2d9..718ec280a 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/IdeaModule/IdeaModule.tsx @@ -44,8 +44,18 @@ const Lightbulb = bundleIcon(Lightbulb20Filled, Lightbulb20Regular) const JobPostings = bundleIcon(NotePin20Filled, NotePin20Regular) export const IdeaModule: FC = (props) => { - const { state, setState, getSelectedIdea, isOpen, renderHamburger, renderStatus, handleToggle, openItems, ignoreFields, fluentProviderId } = - useIdeaModule(props) + const { + state, + setState, + getSelectedIdea, + isOpen, + renderHamburger, + renderStatus, + handleToggle, + openItems, + ignoreFields, + fluentProviderId + } = useIdeaModule(props) return ( @@ -57,8 +67,14 @@ export const IdeaModule: FC = (props) => {
= (props) => { Oversikt */} - } value='registrering'>Registrerte idéer + } value='registrering'> + Registrerte idéer + {state.ideas.data.items .filter((idea) => !idea.processing) @@ -103,7 +121,9 @@ export const IdeaModule: FC = (props) => { Oversikt */} - } value='behandling'>Idéer i behandling + } value='behandling'> + Idéer i behandling + {state.ideas.data.items .filter((idea) => idea.processing) diff --git a/SharePointFramework/PortfolioWebParts/src/data/index.ts b/SharePointFramework/PortfolioWebParts/src/data/index.ts index b10fe986c..fd770577e 100644 --- a/SharePointFramework/PortfolioWebParts/src/data/index.ts +++ b/SharePointFramework/PortfolioWebParts/src/data/index.ts @@ -1083,7 +1083,7 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { const fields = await list.fields .select(...getClassProperties(SPField)) .filter( - 'substringof(\'Gt\', InternalName) or InternalName eq \'Title\' or InternalName eq \'Id\'' + "substringof('Gt', InternalName) or InternalName eq 'Title' or InternalName eq 'Id'" )() const userFields = fields