diff --git a/.gitignore b/.gitignore index ae76ddb08..fff1f7473 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ common/temp **/.rush/temp/ **/*.build.log **/package-lock.json -.vscode/launch.json* \ No newline at end of file +.vscode/launch.json* +SharePointFramework/ProgramWebParts/.vscode/chrome-debug-user-data diff --git a/SharePointFramework/PortfolioExtensions/src/components/IdeaApprovalDialog/index.tsx b/SharePointFramework/PortfolioExtensions/src/components/IdeaApprovalDialog/index.tsx index 92328c48e..060f19450 100644 --- a/SharePointFramework/PortfolioExtensions/src/components/IdeaApprovalDialog/index.tsx +++ b/SharePointFramework/PortfolioExtensions/src/components/IdeaApprovalDialog/index.tsx @@ -35,7 +35,7 @@ export const IdeaApprovalDialog: FC = (props) => { diff --git a/SharePointFramework/PortfolioExtensions/src/components/IdeaDialog/index.tsx b/SharePointFramework/PortfolioExtensions/src/components/IdeaDialog/index.tsx index 02986b416..a89167aa2 100644 --- a/SharePointFramework/PortfolioExtensions/src/components/IdeaDialog/index.tsx +++ b/SharePointFramework/PortfolioExtensions/src/components/IdeaDialog/index.tsx @@ -32,7 +32,7 @@ export const IdeaDialog: FC = (props) => { ? strings.IdeaProjectDataDialogBlockedTitle : strings.IdeaProjectDataDialogInfoTitle } - message={format( + text={format( props.isBlocked ? strings.IdeaProjectDataDialogBlockedMessage : props.dialogMessage, encodeURIComponent(window.location.href) )} diff --git a/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/index.tsx index 9e66eda05..c2a9fb96f 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/index.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/LatestProjects/index.tsx @@ -30,7 +30,7 @@ export const LatestProjects: FC = (props) => { return ( ) } diff --git a/SharePointFramework/PortfolioWebParts/src/components/List/ItemColumn/DialogColumn/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/List/ItemColumn/DialogColumn/index.tsx index 5580baf98..f022652e0 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/List/ItemColumn/DialogColumn/index.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/List/ItemColumn/DialogColumn/index.tsx @@ -84,7 +84,7 @@ export const DialogColumn: ColumnRenderComponent = (props) = ) : ( )} diff --git a/SharePointFramework/PortfolioWebParts/src/components/List/ListHeader/ListHeader.tsx b/SharePointFramework/PortfolioWebParts/src/components/List/ListHeader/ListHeader.tsx index 8abe5e157..25f599239 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/List/ListHeader/ListHeader.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/List/ListHeader/ListHeader.tsx @@ -25,7 +25,7 @@ export const ListHeader: FC = (props) => { {hasError && (
- +
)}
diff --git a/SharePointFramework/PortfolioWebParts/src/components/PortfolioInsights/Chart/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/PortfolioInsights/Chart/index.tsx index c9700e37e..6163b3d91 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/PortfolioInsights/Chart/index.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/PortfolioInsights/Chart/index.tsx @@ -41,7 +41,7 @@ export default class Chart extends Component {
- +
diff --git a/SharePointFramework/PortfolioWebParts/src/components/PortfolioInsights/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/PortfolioInsights/index.tsx index 4f515d0aa..02ea3c21e 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/PortfolioInsights/index.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/PortfolioInsights/index.tsx @@ -78,7 +78,7 @@ export class PortfolioInsights extends Component
@@ -89,7 +89,7 @@ export class PortfolioInsights extends Component ) diff --git a/SharePointFramework/PortfolioWebParts/src/components/PortfolioOverview/ColumnFormPanel/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/PortfolioOverview/ColumnFormPanel/index.tsx index 5d09b7ff5..7b855fa12 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/PortfolioOverview/ColumnFormPanel/index.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/PortfolioOverview/ColumnFormPanel/index.tsx @@ -102,7 +102,7 @@ export const ColumnFormPanel: FC = () => { {columnMessages.get('fieldName') && ( )} diff --git a/SharePointFramework/PortfolioWebParts/src/components/PortfolioOverview/ViewFormPanel/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/PortfolioOverview/ViewFormPanel/index.tsx index 70f13af23..1fd23f4ed 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/PortfolioOverview/ViewFormPanel/index.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/PortfolioOverview/ViewFormPanel/index.tsx @@ -97,7 +97,7 @@ export const ViewFormPanel: FC = () => { {isDefaultViewSet && ( )} diff --git a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/index.tsx index a20e3e989..e9f380b91 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ProjectList/index.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/ProjectList/index.tsx @@ -71,7 +71,7 @@ export const ProjectList: FC = (props) => {
@@ -83,7 +83,7 @@ export const ProjectList: FC = (props) => {
@@ -140,7 +140,7 @@ export const ProjectList: FC = (props) => {
)} diff --git a/SharePointFramework/PortfolioWebParts/src/components/ResourceAllocation/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/ResourceAllocation/index.tsx index 6268d499e..679dee221 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/ResourceAllocation/index.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/ResourceAllocation/index.tsx @@ -16,7 +16,7 @@ export const ResourceAllocation: FC = (props) => { return (
- +
) diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/AddProjectDialog/AddProjectDialog.module.scss b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/AddProjectDialog/AddProjectDialog.module.scss index 98098cf32..86f18fcce 100644 --- a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/AddProjectDialog/AddProjectDialog.module.scss +++ b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/AddProjectDialog/AddProjectDialog.module.scss @@ -1,24 +1,20 @@ @import '~@fluentui/react/dist/sass/References.scss'; -.root { +.addProjectDialog { width: 950px; max-width: 950px; box-sizing: border-box; position: relative; - .dialogContent { - height: 650px; + .content { + height: 550px; margin: 20px 0 0 0; box-sizing: border-box; - - .searchBox { - width: 625px; - margin: 10px 0 0 0; - box-sizing: border-box; - } + overflow-y: auto; + overflow-x: hidden; } - div[class*="ms-List-cell"] { - min-height: 0px; + .actions { + padding: 20px 0 0 0; } } diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/AddProjectDialog/AddProjectDialog.tsx b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/AddProjectDialog/AddProjectDialog.tsx index b66462478..8a5667edd 100644 --- a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/AddProjectDialog/AddProjectDialog.tsx +++ b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/AddProjectDialog/AddProjectDialog.tsx @@ -1,76 +1,64 @@ import { - DefaultButton, + Button, Dialog, - DialogFooter, - DialogType, - PrimaryButton, - SelectionMode, - ShimmeredDetailsList, - ScrollablePane -} from '@fluentui/react' -import _ from 'lodash' + DialogActions, + DialogContent, + DialogSurface, + DialogTitle +} from '@fluentui/react-components' import * as strings from 'ProgramWebPartsStrings' +import _ from 'lodash' import React, { FC, useContext } from 'react' -import { columns } from '../columns' +import { ProjectList } from '../ProjectList' import { ProgramAdministrationContext } from '../context' -import { ListHeaderSearch } from '../ListHeaderSearch/ListHeaderSearch' -import { ADD_CHILD_PROJECTS, TOGGLE_ADD_PROJECT_DIALOG } from '../reducer' +import { SET_SELECTED_TO_ADD, TOGGLE_ADD_PROJECT_DIALOG } from '../reducer' import styles from './AddProjectDialog.module.scss' import { useAddProjectDialog } from './useAddProjectDialog' export const AddProjectDialog: FC = () => { const context = useContext(ProgramAdministrationContext) - const { selection, availableProjects, onSearch, onRenderRow } = useAddProjectDialog() + const { availableProjects, onAddChildProjects } = useAddProjectDialog() return ( ) } diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/AddProjectDialog/useAddProjectDialog.ts b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/AddProjectDialog/useAddProjectDialog.ts index 754415f23..0c23c82a5 100644 --- a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/AddProjectDialog/useAddProjectDialog.ts +++ b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/AddProjectDialog/useAddProjectDialog.ts @@ -1,16 +1,9 @@ import { useContext, useEffect } from 'react' import { ProgramAdministrationContext } from '../context' -import { DATA_LOADED, SET_SELECTED_TO_ADD } from '../reducer' -import { useRowRenderer } from '../useRowRenderer' -import { useSelectionList } from '../useSelectionList' +import { ADD_CHILD_PROJECTS, DATA_LOADED } from '../reducer' export const useAddProjectDialog = () => { const context = useContext(ProgramAdministrationContext) - const selectedKeys = context.state.selectedProjectsToAdd.map((p) => p.key) - - const selectionList = useSelectionList(selectedKeys, (selected) => { - context.dispatch(SET_SELECTED_TO_ADD({ selected })) - }) useEffect(() => { context.props.dataAdapter @@ -26,19 +19,25 @@ export const useAddProjectDialog = () => { }, []) const availableProjects = context.state.availableProjects.filter( - (project) => - !context.state.childProjects.some((el) => el.SiteId === project.SiteId) && - project.SiteId !== context.props.context.pageContext.site.id.toString() + ({SiteId}) => + !context.state.childProjects.some((c) => c.SiteId === SiteId) && + SiteId !== context.props.context.pageContext.site.id.toString() ) - const onRenderRow = useRowRenderer({ - selectedKeys, - searchTerm: selectionList.searchTerm - }) + /** + * Adds projects to the parent project. This function is called when the user clicks the "Add" button in the + * `` component. + */ + const onAddChildProjects = async () => { + const projects = availableProjects.filter(({SiteId}) => + context.state.addProjectDialog?.selectedProjects.includes(SiteId) + ) + await context.props.dataAdapter.addChildProjects(projects) + context.dispatch(ADD_CHILD_PROJECTS(projects)) + } return { - ...selectionList, availableProjects, - onRenderRow + onAddChildProjects } } diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/Commands/Commands.module.scss b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/Commands/Commands.module.scss new file mode 100644 index 000000000..197b2acfa --- /dev/null +++ b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/Commands/Commands.module.scss @@ -0,0 +1,3 @@ +.commands { + margin: 20px; +} \ No newline at end of file diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/Commands/Commands.tsx b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/Commands/Commands.tsx index f2465f096..ca7fc2fb4 100644 --- a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/Commands/Commands.tsx +++ b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/Commands/Commands.tsx @@ -1,37 +1,37 @@ -import { CommandBar, ICommandBarItemProps } from '@fluentui/react' -import { isEmpty } from '@microsoft/sp-lodash-subset' import * as strings from 'ProgramWebPartsStrings' +import _ from 'lodash' +import { ListMenuItem, Toolbar } from 'pp365-shared-library' import React, { FC, useContext } from 'react' import { ProgramAdministrationContext } from '../context' import { CHILD_PROJECTS_REMOVED, TOGGLE_ADD_PROJECT_DIALOG } from '../reducer' +import styles from './Commands.module.scss' export const Commands: FC = () => { const context = useContext(ProgramAdministrationContext) - const items: ICommandBarItemProps[] = [ - { - key: 'ProgramAddChilds', - text: strings.ProgramAdministrationAddChildsButtonText, - iconProps: { iconName: 'Add' }, - buttonStyles: { root: { border: 'none' } }, - onClick: () => context.dispatch(TOGGLE_ADD_PROJECT_DIALOG()), - disabled: !context.state.userHasManagePermission - }, - { - key: 'ProgramRemoveChilds', - text: strings.ProgramRemoveChildsButtonText, - iconProps: { iconName: 'Delete' }, - buttonStyles: { root: { border: 'none' } }, - disabled: - isEmpty(context.state.selectedProjectsToDelete) || !context.state.userHasManagePermission, - onClick: () => { - context.props.dataAdapter - .removeChildProjects(context.state.selectedProjectsToDelete) - .then((childProjects) => { - context.dispatch(CHILD_PROJECTS_REMOVED({ childProjects })) - }) - } - } + + const items = [ + new ListMenuItem(strings.ProgramAdministrationAddChildsButtonText) + .setDisabled(!context.state.userHasManagePermission) + .setIcon('Add') + .setOnClick(() => context.dispatch(TOGGLE_ADD_PROJECT_DIALOG())), + new ListMenuItem(strings.ProgramRemoveChildsButtonText) + .setIcon('Delete') + .setDisabled( + _.isEmpty(context.state.selectedProjects) || !context.state.userHasManagePermission + ) + .setOnClick(() => { + const projects = context.state.childProjects.filter(({ SiteId }) => + context.state.selectedProjects.includes(SiteId) + ) + context.props.dataAdapter.removeChildProjects(projects).then((childProjects) => { + context.dispatch(CHILD_PROJECTS_REMOVED({ childProjects })) + }) + }) ] - return + return ( +
+ +
+ ) } diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ListHeaderSearch/ListHeaderSearch.module.scss b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ListHeaderSearch/ListHeaderSearch.module.scss deleted file mode 100644 index c8bff9ab2..000000000 --- a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ListHeaderSearch/ListHeaderSearch.module.scss +++ /dev/null @@ -1,28 +0,0 @@ -@import '~@fluentui/react/dist/sass/References.scss'; - -.searchBox { - width: 625px; - margin: 10px 0 0 0; - box-sizing: border-box; -} - -.selectionCount { - margin: 0; - cursor: pointer; -} - -.selectionCountTooltip { - width: 300px; - margin: 0; - padding: 6px 16px; - - p { - font-size: $ms-font-size-m; - } - - ul { - margin: 0; - padding: 0; - list-style-type: none; - } -} diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ListHeaderSearch/ListHeaderSearch.tsx b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ListHeaderSearch/ListHeaderSearch.tsx deleted file mode 100644 index 0314aa2ec..000000000 --- a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ListHeaderSearch/ListHeaderSearch.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { - CommandBar, - Icon, - Link, - SearchBox, - SelectAllVisibility, - Sticky, - StickyPositionType, - TooltipHost -} from '@fluentui/react' -import { format } from '@uifabric/utilities' -import strings from 'ProgramWebPartsStrings' -import React, { FC } from 'react' -import styles from './ListHeaderSearch.module.scss' -import { IListHeaderSearchProps } from './types' -import _ from 'underscore' - -export const ListHeaderSearch: FC = (props) => { - return ( - - ( - - ) - } - ]} - farItems={[ - { - key: 'cmdSelectionCount', - onRender: () => ( -
- { - if (_.isEmpty(props.selectedItems)) return null - return ( - - ) - } - }} - > - {format(strings.CmdSelectionCountText, props.selectedItems.length)} - -
- ) - } - ]} - /> - {props.defaultRender({ - ...props.detailsHeaderProps, - selectAllVisibility: props.selectAllVisibility - })} -
- ) -} - -ListHeaderSearch.displayName = 'ListHeaderSearch' -ListHeaderSearch.defaultProps = { - search: { - hidden: true, - placeholder: '', - // eslint-disable-next-line @typescript-eslint/no-empty-function - onSearch: () => {} - }, - selectAllVisibility: SelectAllVisibility.hidden -} diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ListHeaderSearch/index.ts b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ListHeaderSearch/index.ts deleted file mode 100644 index bcf304dfc..000000000 --- a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ListHeaderSearch/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ListHeaderSearch' diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ListHeaderSearch/types.ts b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ListHeaderSearch/types.ts deleted file mode 100644 index 0e852ca7d..000000000 --- a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ListHeaderSearch/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { IDetailsHeaderProps, ISearchBoxProps, SelectAllVisibility } from '@fluentui/react' - -export interface IListHeaderSearchProps { - /** - * Current selected items - */ - selectedItems: Record[] - - /** - * Search box properties - */ - search?: ISearchBoxProps - - /** - * Details header properties - */ - detailsHeaderProps: IDetailsHeaderProps - - /** - * Default render function for the details header - */ - defaultRender: (props?: IDetailsHeaderProps) => JSX.Element - - /** - * Select all visibility - */ - selectAllVisibility?: SelectAllVisibility -} diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProgramAdministration.module.scss b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProgramAdministration.module.scss index b01745862..7b2379574 100644 --- a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProgramAdministration.module.scss +++ b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProgramAdministration.module.scss @@ -1,6 +1,6 @@ @import '~@fluentui/react/dist/sass/References.scss'; -.root { +.programAdministration { height: 100vh; margin: 20px; diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProgramAdministration.tsx b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProgramAdministration.tsx index ca5ad776e..43231beb6 100644 --- a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProgramAdministration.tsx +++ b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProgramAdministration.tsx @@ -1,70 +1,58 @@ -import { SelectionMode, ShimmeredDetailsList } from '@fluentui/react' +import { FluentProvider, webLightTheme } from '@fluentui/react-components' import { isEmpty } from '@microsoft/sp-lodash-subset' import * as strings from 'ProgramWebPartsStrings' import { UserMessage, WebPartTitle } from 'pp365-shared-library' import React, { FC } from 'react' import { AddProjectDialog } from './AddProjectDialog/AddProjectDialog' import { Commands } from './Commands/Commands' -import { ListHeaderSearch } from './ListHeaderSearch/ListHeaderSearch' import styles from './ProgramAdministration.module.scss' -import { columns } from './columns' +import { ProjectList } from './ProjectList' import { ProgramAdministrationContext } from './context' import { IProgramAdministrationProps } from './types' import { useProgramAdministration } from './useProgramAdministration' export const ProgramAdministration: FC = (props) => { - const { state, dispatch, selection, onSearch, onRenderRow } = useProgramAdministration(props) + const { context, childProjects, onSelectionChange } = useProgramAdministration(props) - if (state.error) { + if (context.state.error) { return ( <> -
+

{strings.ProgramAdministrationHeader}

- +
) } return ( - - -
- -
- {!isEmpty(state.childProjects) || state.loading.root ? ( - ( - - )} - /> - ) : ( - - )} + + + +
+ +
+ {!isEmpty(context.state.childProjects) || context.state.loading ? ( + + ) : ( + + )} +
+ {context.state.addProjectDialog && }
- {state.displayAddProjectDialog && } -
- + + ) } diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/ProjectList.module.scss b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/ProjectList.module.scss new file mode 100644 index 000000000..2e26fb3aa --- /dev/null +++ b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/ProjectList.module.scss @@ -0,0 +1,7 @@ +.projectList { + .searchBox { + margin: 10px 0; + width: 100%; + max-width: 100%; + } +} \ No newline at end of file diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/ProjectList.tsx b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/ProjectList.tsx new file mode 100644 index 000000000..112c40e75 --- /dev/null +++ b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/ProjectList.tsx @@ -0,0 +1,54 @@ +import { + DataGrid, + DataGridBody, + DataGridCell, + DataGridHeader, + DataGridHeaderCell, + DataGridRow +} from '@fluentui/react-components' +import { SearchBox } from '@fluentui/react-search-preview' +import React, { FC, useContext } from 'react' +import { ProgramAdministrationContext } from '../context' +import styles from './ProjectList.module.scss' +import { IProjectListProps } from './types' +import { useProjectList } from './useProjectList' + +export const ProjectList: FC = (props) => { + const context = useContext(ProgramAdministrationContext) + const { items, columns, onSearch } = useProjectList(props) + return ( +
+ onSearch(null, { value: '' }) }} + /> + SiteId} + > + + + {({ renderHeaderCell }) => ( + {renderHeaderCell()} + )} + + + >> + {({ item, rowId }) => ( + > key={rowId}> + {({ renderCell }) => {renderCell(item)}} + + )} + + +
+ ) +} diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/index.ts b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/index.ts new file mode 100644 index 000000000..a90dac670 --- /dev/null +++ b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/index.ts @@ -0,0 +1,2 @@ +export * from './ProjectList' +export * from './types' diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/types.ts b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/types.ts new file mode 100644 index 000000000..ec395440f --- /dev/null +++ b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/types.ts @@ -0,0 +1,8 @@ +import { DataGridProps } from '@fluentui/react-components' +import { SearchBoxProps } from '@fluentui/react-search-preview' + +export interface IProjectListProps { + items: Record[] + onSelectionChange: DataGridProps['onSelectionChange'] + search: Pick +} diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/useColumns.tsx b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/useColumns.tsx new file mode 100644 index 000000000..4922036e4 --- /dev/null +++ b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/useColumns.tsx @@ -0,0 +1,27 @@ +import { Link } from '@fluentui/react' +import { TableCellLayout, TableColumnDefinition } from '@fluentui/react-components' +import strings from 'ProgramWebPartsStrings' +import React from 'react' + +export const useColumns = (): TableColumnDefinition>[] => { + return [ + { + columnId: 'title', + compare: (a, b) => { + return (a.Title ?? '').localeCompare(b.Title ?? '') + }, + renderHeaderCell: () => { + return strings.TitleLabel + }, + renderCell: (item) => { + return ( + + + {item.Title} + + + ) + } + } + ] +} diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/useProjectList.tsx b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/useProjectList.tsx new file mode 100644 index 000000000..eabe66f5c --- /dev/null +++ b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/ProjectList/useProjectList.tsx @@ -0,0 +1,19 @@ +import { SearchBoxProps } from '@fluentui/react-search-preview' +import { useMemo, useState } from 'react' +import { IProjectListProps } from './types' +import { useColumns } from './useColumns' + +export function useProjectList(props: IProjectListProps) { + const [searchTerm, setSearchTerm] = useState('') + const items = useMemo( + () => props.items.filter((item) => item.Title.toLowerCase().includes(searchTerm)), + [props.items, searchTerm] + ) + const columns = useColumns() + + const onSearch: SearchBoxProps['onChange'] = (_, data) => { + setSearchTerm(data?.value?.toLowerCase() ?? '') + } + + return { items, columns, onSearch, searchTerm } +} diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/columns.tsx b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/columns.tsx deleted file mode 100644 index 43a141d33..000000000 --- a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/columns.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { IColumn, Link, Icon } from '@fluentui/react' -import React from 'react' - -/** - * Returns an array of columns for the ShimmeredDetailsList. If SPPWebURL is not set, the project - * is not accessible by the user, but the user can still add it to the program. If renderAsLink is - * set to true, the project title will be rendered as a link to the project. - */ -export function columns({ renderAsLink = false }) { - return [ - { - key: 'Title', - fieldName: 'Title', - name: 'Tittel', - onRender: (item) => { - if (!item.SPWebURL) { - return ( -
- {item.Title} - -
- ) - } - if (renderAsLink) { - return ( - - {item.Title} - - ) - } else return item.Title - }, - minWidth: 100 - } - ] as IColumn[] -} diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/reducer.ts b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/reducer.ts index a8c4fd873..562744f72 100644 --- a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/reducer.ts +++ b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/reducer.ts @@ -1,31 +1,36 @@ import { createAction, createReducer } from '@reduxjs/toolkit' -import { IProgramAdministrationState } from './types' +import { IProgramAdministrationProject, IProgramAdministrationState } from './types' export const DATA_LOADED = createAction<{ data: Partial scope: string }>('DATA_LOADED') export const TOGGLE_ADD_PROJECT_DIALOG = createAction('TOGGLE_ADD_PROJECT_DIALOG') -export const ADD_CHILD_PROJECTS = createAction('ADD_CHILD_PROJECTS') +export const ADD_CHILD_PROJECTS = + createAction('ADD_CHILD_PROJECTS') export const CHILD_PROJECTS_REMOVED = createAction<{ childProjects: Record[] }>( 'CHILD_PROJECTS_REMOVED' ) -export const SET_SELECTED_TO_ADD = createAction<{ selected: Record[] }>( - 'SET_SELECTED_TO_ADD' -) -export const SET_SELECTED_TO_DELETE = createAction<{ selected: Record[] }>( - 'SET_SELECTED_TO_DELETE' -) +export const SET_SELECTED_TO_ADD = + createAction( + 'SET_SELECTED_TO_ADD' + ) +export const SET_SELECTED_TO_DELETE = + createAction('SET_SELECTED_TO_DELETE') +/** + * Initial state for the `ProgramAdministration` reducer. + */ export const initialState: IProgramAdministrationState = { - loading: { - root: true, - AddProjectDialog: true + loading: true, + addProjectDialog: { + open: false, + loading: false, + selectedProjects: [] }, childProjects: [], availableProjects: [], - selectedProjectsToAdd: [], - selectedProjectsToDelete: [], + selectedProjects: [], error: null } @@ -55,37 +60,55 @@ export default createReducer(initialState, { : state.availableProjects state.userHasManagePermission = payload.data.userHasManagePermission ?? state.userHasManagePermission - state.loading = { - ...state.loading, - [payload.scope]: false + if (payload.scope === 'AddProjectDialog') { + state.addProjectDialog = { + ...state.addProjectDialog, + loading: false + } + } else { + state.loading = false } }, [TOGGLE_ADD_PROJECT_DIALOG.type]: (state: IProgramAdministrationState) => { - state.displayAddProjectDialog = !state.displayAddProjectDialog - state.selectedProjectsToDelete = [] + state.addProjectDialog = { + open: !state.addProjectDialog.open, + loading: false, + selectedProjects: [] + } + state.selectedProjects = [] }, - [ADD_CHILD_PROJECTS.type]: (state: IProgramAdministrationState) => { - state.childProjects = [...state.childProjects, ...state.selectedProjectsToAdd] - state.selectedProjectsToAdd = [] - state.displayAddProjectDialog = false + [ADD_CHILD_PROJECTS.type]: ( + state: IProgramAdministrationState, + { payload }: ReturnType + ) => { + state.childProjects = [...state.childProjects, ...payload] + state.selectedProjects = [] + state.addProjectDialog = { + open: false, + loading: false, + selectedProjects: [] + } }, [CHILD_PROJECTS_REMOVED.type]: ( state: IProgramAdministrationState, { payload }: ReturnType ) => { state.childProjects = payload.childProjects - state.selectedProjectsToDelete = [] + state.selectedProjects = [] }, [SET_SELECTED_TO_ADD.type]: ( state: IProgramAdministrationState, { payload }: ReturnType ) => { - state.selectedProjectsToAdd = payload.selected + state.addProjectDialog = { + ...state.addProjectDialog, + selectedProjects: payload + } }, [SET_SELECTED_TO_DELETE.type]: ( state: IProgramAdministrationState, { payload }: ReturnType ) => { - state.selectedProjectsToDelete = payload.selected + state.selectedProjects = payload } }) diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/types.ts b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/types.ts index b30c1000c..bbfd7a3fd 100644 --- a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/types.ts +++ b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/types.ts @@ -1,7 +1,8 @@ +import { TableRowId } from '@fluentui/react-components' import { WebPartContext } from '@microsoft/sp-webpart-base' import { SPDataAdapter } from 'data/SPDataAdapter' -export interface IProgramAdministrationProject { +export interface IProgramAdministrationProject extends Record { SiteId: string Title: string SPWebURL: string @@ -16,12 +17,9 @@ export interface IProgramAdministrationProps { export interface IProgramAdministrationState { /** - * Loading state for scopes `root` and `AddProjectDialog` + * Loading state */ - loading: { - root: boolean - AddProjectDialog: boolean - } + loading: boolean /** * Child projects @@ -29,24 +27,34 @@ export interface IProgramAdministrationState { childProjects: Record[] /** - * True if `` should be displayed to the user + * Properties for the add project dialog */ - displayAddProjectDialog?: boolean + addProjectDialog?: { + /** + * Dialog open state + */ + open: boolean + + /** + * Loading state + */ + loading: boolean + + /** + * Projects selected by user to add + */ + selectedProjects: TableRowId[] + } /** * Projects available to add to parent project */ availableProjects: IProgramAdministrationProject[] - /** - * Projects selected by user to add - */ - selectedProjectsToAdd: Record[] - /** * Projects selected by user for deletion */ - selectedProjectsToDelete: Record[] + selectedProjects: TableRowId[] /** * User has manage permission, meaning `ChildProjectsAdmin` diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/useProgramAdministration.ts b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/useProgramAdministration.ts index f406f4078..a0bbdd14e 100644 --- a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/useProgramAdministration.ts +++ b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/useProgramAdministration.ts @@ -1,17 +1,10 @@ import { ProjectAdminPermission } from 'pp365-shared-library/lib' -import { useEffect, useReducer } from 'react' +import { useEffect, useMemo, useReducer } from 'react' import reducer, { DATA_LOADED, SET_SELECTED_TO_DELETE, initialState } from './reducer' import { IProgramAdministrationProps } from './types' -import { useRowRenderer } from './useRowRenderer' -import { useSelectionList } from './useSelectionList' export const useProgramAdministration = (props: IProgramAdministrationProps) => { const [state, dispatch] = useReducer(reducer, initialState) - const selectedKeys = state.selectedProjectsToDelete.map((p) => p.key) - - const { selection, onSearch, searchTerm } = useSelectionList(selectedKeys, (selected) => { - dispatch(SET_SELECTED_TO_DELETE({ selected })) - }) useEffect(() => { props.dataAdapter.project.getProjectInformationData().then((properties) => { @@ -22,15 +15,26 @@ export const useProgramAdministration = (props: IProgramAdministrationProps) => properties.fieldValues ) ]).then(([childProjects, userHasManagePermission]) => { - dispatch(DATA_LOADED({ data: { childProjects, userHasManagePermission }, scope: 'root' })) + dispatch( + DATA_LOADED({ + data: { childProjects, userHasManagePermission }, + scope: 'ProgramAdministration' + }) + ) }) }) }, []) - const onRenderRow = useRowRenderer({ - selectedKeys, - searchTerm - }) + const context = useMemo(() => ({ props, state, dispatch }), [props, state]) + + /** + * Callback function for handling selection change in the `ProjectList` component. + */ + const onSelectionChange = (_: any, { selectedItems }) => { + dispatch(SET_SELECTED_TO_DELETE(Array.from(selectedItems))) + } + + const childProjects = [...state.childProjects] - return { state, dispatch, selection, onSearch, searchTerm, onRenderRow } as const + return { context, childProjects, onSelectionChange } } diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/useRowRenderer.tsx b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/useRowRenderer.tsx deleted file mode 100644 index 20b96a54b..000000000 --- a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/useRowRenderer.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { IDetailsRowProps } from '@fluentui/react' - -/** - * Row renderer hook for `ProgramAdministration`. Returns an instance of - * `onRenderRow`. - */ -export function useRowRenderer({ selectedKeys, searchTerm }) { - return ( - detailsRowProps: IDetailsRowProps, - defaultRender: (props?: IDetailsRowProps) => JSX.Element - ) => { - const shouldRenderRow = - detailsRowProps.item.Title.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1 || - selectedKeys.includes(detailsRowProps.item.key) - return shouldRenderRow ? defaultRender(detailsRowProps) : null - } -} diff --git a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/useSelectionList.ts b/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/useSelectionList.ts deleted file mode 100644 index 013ff083c..000000000 --- a/SharePointFramework/ProgramWebParts/src/components/ProgramAdministration/useSelectionList.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { IObjectWithKey, Selection } from '@fluentui/react' -import { useEffect, useState } from 'react' - -/** - * Component logic hook for selection list - * - * @param selectedKeys Selected keys - * @param onSelectionChanged On selection changed - */ -export function useSelectionList( - selectedKeys: (string | number)[], - onSelectionChanged: (items: any[]) => void -) { - const $selection = new Selection({ - onSelectionChanged: () => { - onSelectionChanged(selection.getSelection()) - } - }) - const [selection, setSelection] = useState>($selection) - const [searchTerm, setSearchTerm] = useState('') - - useEffect(() => { - $selection.setChangeEvents(false) - selectedKeys.forEach((key) => $selection.setKeySelected(key as any, true, true)) - $selection.setChangeEvents(true) - setSelection($selection) - }, [searchTerm]) - - return { selection, onSearch: setSearchTerm, searchTerm } as const -} diff --git a/SharePointFramework/ProgramWebParts/src/data/SPDataAdapter.ts b/SharePointFramework/ProgramWebParts/src/data/SPDataAdapter.ts index 7099597b4..350bc7755 100644 --- a/SharePointFramework/ProgramWebParts/src/data/SPDataAdapter.ts +++ b/SharePointFramework/ProgramWebParts/src/data/SPDataAdapter.ts @@ -916,7 +916,7 @@ export class SPDataAdapter public async removeChildProjects( projectToRemove: Array> ): Promise>> { - const [{ GtChildProjects }] = await this._propertyItem.select('GtChildProjects')() + const { GtChildProjects } = await this._propertyItem.select('GtChildProjects')() const projects: Array> = JSON.parse(GtChildProjects) const updatedProjects = projects.filter( (p) => !projectToRemove.some((el) => el.SiteId === p.SiteId) diff --git a/SharePointFramework/ProgramWebParts/src/loc/mystrings.d.ts b/SharePointFramework/ProgramWebParts/src/loc/mystrings.d.ts index 6a5751a03..ad00edd4a 100644 --- a/SharePointFramework/ProgramWebParts/src/loc/mystrings.d.ts +++ b/SharePointFramework/ProgramWebParts/src/loc/mystrings.d.ts @@ -1,4 +1,5 @@ declare interface IProgramWebPartsStrings { + TitleLabel: string Add: string AddProjectDialogSearchBoxPlaceholder: string BarLabel: string diff --git a/SharePointFramework/ProgramWebParts/src/loc/nb-no.js b/SharePointFramework/ProgramWebParts/src/loc/nb-no.js index 40643e5e1..0554c1dc0 100644 --- a/SharePointFramework/ProgramWebParts/src/loc/nb-no.js +++ b/SharePointFramework/ProgramWebParts/src/loc/nb-no.js @@ -1,5 +1,6 @@ define([], function () { return { + TitleLabel: 'Tittel', Add: 'Legg til', AddProjectDialogSearchBoxPlaceholder: 'Søk i prosjekter...', BarLabel: 'Bar', diff --git a/SharePointFramework/ProgramWebParts/src/webparts/baseProgramWebPart/index.ts b/SharePointFramework/ProgramWebParts/src/webparts/baseProgramWebPart/index.ts index bde9a2414..f27d74e07 100644 --- a/SharePointFramework/ProgramWebParts/src/webparts/baseProgramWebPart/index.ts +++ b/SharePointFramework/ProgramWebParts/src/webparts/baseProgramWebPart/index.ts @@ -2,11 +2,11 @@ import { IPropertyPaneConfiguration } from '@microsoft/sp-property-pane' import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base' import { LogLevel } from '@pnp/logging' import { SPFI } from '@pnp/sp' +import { SPDataAdapter } from 'data/SPDataAdapter' import { createSpfiInstance } from 'pp365-shared-library' import { IHubSite } from 'pp365-shared-library/lib/interfaces' import { ComponentClass, FC, ReactElement, createElement } from 'react' import { render } from 'react-dom' -import { SPDataAdapter } from 'data/SPDataAdapter' import { IBaseProgramWebPartProps } from './types' export abstract class BaseProgramWebPart< diff --git a/SharePointFramework/ProjectExtensions/src/components/DocumentTemplateDialog/EditCopyScreen/index.tsx b/SharePointFramework/ProjectExtensions/src/components/DocumentTemplateDialog/EditCopyScreen/index.tsx index cd152e9cd..574c74391 100644 --- a/SharePointFramework/ProjectExtensions/src/components/DocumentTemplateDialog/EditCopyScreen/index.tsx +++ b/SharePointFramework/ProjectExtensions/src/components/DocumentTemplateDialog/EditCopyScreen/index.tsx @@ -44,7 +44,7 @@ export const EditCopyScreen: FC = ({ onStartCopy }) => {
{state.selected.map((item, idx) => ( diff --git a/SharePointFramework/ProjectExtensions/src/components/DocumentTemplateDialog/SelectScreen/index.tsx b/SharePointFramework/ProjectExtensions/src/components/DocumentTemplateDialog/SelectScreen/index.tsx index 639bc4ba3..9fcdbe8d0 100644 --- a/SharePointFramework/ProjectExtensions/src/components/DocumentTemplateDialog/SelectScreen/index.tsx +++ b/SharePointFramework/ProjectExtensions/src/components/DocumentTemplateDialog/SelectScreen/index.tsx @@ -34,7 +34,7 @@ export const SelectScreen = (props: ISelectScreenProps) => { <> {
{ [DocumentTemplateDialogScreen.Summary]: ( ) diff --git a/SharePointFramework/ProjectExtensions/src/components/ErrorDialog/index.tsx b/SharePointFramework/ProjectExtensions/src/components/ErrorDialog/index.tsx index 51da8e736..b34a246f6 100644 --- a/SharePointFramework/ProjectExtensions/src/components/ErrorDialog/index.tsx +++ b/SharePointFramework/ProjectExtensions/src/components/ErrorDialog/index.tsx @@ -41,7 +41,7 @@ export const ErrorDialog: FC = ({ onDismiss={onDismiss} > {onRenderFooter()} diff --git a/SharePointFramework/ProjectExtensions/src/components/ProjectSetupDialog/TemplateConfigMessage/index.tsx b/SharePointFramework/ProjectExtensions/src/components/ProjectSetupDialog/TemplateConfigMessage/index.tsx index 845c06984..e9e1b0ee9 100644 --- a/SharePointFramework/ProjectExtensions/src/components/ProjectSetupDialog/TemplateConfigMessage/index.tsx +++ b/SharePointFramework/ProjectExtensions/src/components/ProjectSetupDialog/TemplateConfigMessage/index.tsx @@ -9,7 +9,7 @@ export const TemplateConfigMessage: FC = (props) => return ( ) } diff --git a/SharePointFramework/ProjectExtensions/src/components/ProjectSetupDialog/TemplateConfigMessage/useTemplateConfigMessage.tsx b/SharePointFramework/ProjectExtensions/src/components/ProjectSetupDialog/TemplateConfigMessage/useTemplateConfigMessage.tsx index c11948d4e..0e55e43c3 100644 --- a/SharePointFramework/ProjectExtensions/src/components/ProjectSetupDialog/TemplateConfigMessage/useTemplateConfigMessage.tsx +++ b/SharePointFramework/ProjectExtensions/src/components/ProjectSetupDialog/TemplateConfigMessage/useTemplateConfigMessage.tsx @@ -29,5 +29,5 @@ export function useTemplateConfigMessage({ section }: ITemplateConfigMessageProp .join(' og ') .toLowerCase() ) - return { hidden, message } as const + return { hidden, message } } diff --git a/SharePointFramework/ProjectExtensions/src/components/ProjectSetupDialog/index.tsx b/SharePointFramework/ProjectExtensions/src/components/ProjectSetupDialog/index.tsx index 27864a421..999316dc3 100644 --- a/SharePointFramework/ProjectExtensions/src/components/ProjectSetupDialog/index.tsx +++ b/SharePointFramework/ProjectExtensions/src/components/ProjectSetupDialog/index.tsx @@ -65,7 +65,7 @@ export const ProjectSetupDialog: FC = (props) => { @@ -73,7 +73,7 @@ export const ProjectSetupDialog: FC = (props) => { {props.tasks && ( )}
diff --git a/SharePointFramework/ProjectWebParts/src/components/ProjectInformation/ProjectInformation.tsx b/SharePointFramework/ProjectWebParts/src/components/ProjectInformation/ProjectInformation.tsx index 3a9cca301..dc0dfc246 100644 --- a/SharePointFramework/ProjectWebParts/src/components/ProjectInformation/ProjectInformation.tsx +++ b/SharePointFramework/ProjectWebParts/src/components/ProjectInformation/ProjectInformation.tsx @@ -50,7 +50,7 @@ export const ProjectInformation: FC = (props) => { {context.state.error && ( )} diff --git a/SharePointFramework/ProjectWebParts/src/components/ProjectInformation/ProjectProperties/index.tsx b/SharePointFramework/ProjectWebParts/src/components/ProjectInformation/ProjectProperties/index.tsx index 44743b1a7..706b43025 100644 --- a/SharePointFramework/ProjectWebParts/src/components/ProjectInformation/ProjectProperties/index.tsx +++ b/SharePointFramework/ProjectWebParts/src/components/ProjectInformation/ProjectProperties/index.tsx @@ -51,11 +51,11 @@ export const ProjectProperties: FC = (props) => { <>