From bfd886b838669e83c38a8fb138c62ce900381f85 Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Thu, 14 Mar 2024 09:57:38 +0100 Subject: [PATCH 01/18] Add base skeleton for edit user in team sidebar modal DPSTAT-894 --- src/pages/TeamDetail/TeamDetail.tsx | 53 ++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index 157e8f48..6473ea37 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -23,6 +23,7 @@ import { Input, Dropdown, Tag, + Link, } from '@statisticsnorway/ssb-component-library' import PageSkeleton from '../../components/PageSkeleton/PageSkeleton' import { Skeleton, CircularProgress } from '@mui/material' @@ -46,6 +47,10 @@ const TEAM_USERS_TAB = { id: 'epost', label: 'Epost ?', }, + { + id: 'editUser', + label: '', + }, ], } @@ -73,6 +78,7 @@ const defaultSelectedItem = { const TeamDetail = () => { const [activeTab, setActiveTab] = useState(TEAM_USERS_TAB) + const [showSpinner, setShowSpinner] = useState(false) const [tokenData, setTokenData] = useState() const { setBreadcrumbTeamDetailDisplayName } = useContext(DaplaCtrlContext) @@ -85,7 +91,8 @@ const TeamDetail = () => { ) const [teamDetailTableData, setTeamDetailTableData] = useState() - const [openSidebar, setOpenSidebar] = useState(false) + // Add users to team + const [openAddUserSidebarModal, setAddUserSidebarModal] = useState(false) const [email, setEmail] = useState({ error: false, errorMessage: `Ugyldig epost`, @@ -98,7 +105,9 @@ const TeamDetail = () => { errorMessage: 'Velg minst én tilgangsgruppe', }) const [addUserToTeamErrors, setAddUserToTeamErrors] = useState>([]) - const [showSpinner, setShowSpinner] = useState(false) + + // Edit users in team + const [openEditUserSidebarModal, setOpenEditUserSidebarModal] = useState(false) const { teamId } = useParams<{ teamId: string }>() const teamDetailTab = (activeTab as TabProps)?.path ?? activeTab @@ -140,6 +149,7 @@ const TeamDetail = () => { .map((group) => getGroupType(group.uniform_name)) .join(', '), epost: principal_name, + editUser: setOpenEditUserSidebarModal(true)}>Endre, } }) } @@ -281,7 +291,7 @@ const TeamDetail = () => { if (errorsList.length) { setAddUserToTeamErrors(errorsList) } else { - setOpenSidebar(false) + setAddUserSidebarModal(false) // setEmail({ ...email, value: '' }) setSelectedItem({ ...defaultSelectedItem }) // setTeamGroupTags([]) // TODO: Re-implement when clearing input fields work @@ -314,13 +324,14 @@ const TeamDetail = () => { ) } - const renderSidebarModal = () => { + const renderAddUserSidebarModal = () => { if (teamDetailData) { const teamGroups = (teamDetailData?.team as Team).groups ?? [] + // TODO: modal header is the same for both renderAddUserSidebarModal and renderEditUserSidebarModal return ( setOpenSidebar(false)} + open={openAddUserSidebarModal} + onClose={() => setAddUserSidebarModal(false)} header={{ modalType: 'Medlem', modalTitle: `${(teamDetailData?.team as Team).display_name}`, @@ -383,10 +394,36 @@ const TeamDetail = () => { } } + const renderEditUserSidebarModal = () => { + const display_name = '' + if (teamDetailData) { + return ( + setOpenEditUserSidebarModal(false)} + header={{ + modalType: 'Medlem', + modalTitle: `${(teamDetailData?.team as Team).display_name}`, + modalDescription: `${(teamDetailData?.team as Team).uniform_name}`, + }} + footer={{ + submitButtonText: 'Oppdater Tilgang', + handleSubmit: () => {}, + }} + body={{ + modalBodyTitle: `Endre tilgang til ${display_name}`, + modalBody: <>, + }} + /> + ) + } + } + const teamManager = teamDetailData ? (teamDetailData?.team as Team).manager?.principal_name : '' return ( <> - {renderSidebarModal()} + {renderAddUserSidebarModal()} + {renderEditUserSidebarModal()} { content={renderContent()} button={ tokenData?.email === teamManager ? ( - + ) : undefined } /> From f961ce0c140799b2517bd178a5f3e6e8dd0aca67 Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Thu, 14 Mar 2024 10:06:58 +0100 Subject: [PATCH 02/18] added removeUserfromGroups function --- src/pages/TeamDetail/TeamDetail.tsx | 2 +- src/services/teamDetail.ts | 36 +++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index 6473ea37..b9708dca 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -6,7 +6,7 @@ import { DropdownItems, TabProps } from '../../@types/pageTypes' import { useCallback, useContext, useEffect, useState } from 'react' import PageLayout from '../../components/PageLayout/PageLayout' -import { TeamDetailData, getTeamDetail, Team, SharedBuckets, addUserToGroups } from '../../services/teamDetail' +import { TeamDetailData, getTeamDetail, Team, SharedBuckets, addUserToGroups, removeUserFromGroups } from '../../services/teamDetail' import { useParams } from 'react-router-dom' import { ApiError, TokenData, fetchUserInformationFromAuthToken } from '../../utils/services' diff --git a/src/services/teamDetail.ts b/src/services/teamDetail.ts index 14f0b20b..804e9a88 100644 --- a/src/services/teamDetail.ts +++ b/src/services/teamDetail.ts @@ -61,6 +61,8 @@ export interface JobResponse { detail?: string } +type Method = 'POST' | 'DELETE' // POST = ADD, DELETE = REMOVE + export const fetchTeamInfo = async (teamId: string): Promise => { const teamsUrl = new URL(`${TEAMS_URL}/${teamId}`, window.location.origin) const embeds = ['users', 'users.groups', 'managers', 'groups'] @@ -173,7 +175,9 @@ export const getTeamDetail = async (teamId: string): Promise => export const addUserToGroups = async (groupIds: string[], userPrincipalName: string): Promise => { try { - const jobResponses = await Promise.all(groupIds.map((groupId) => addUserToGroup(groupId, userPrincipalName))) + const jobResponses = await Promise.all( + groupIds.map((groupId) => updateGroupMembership(groupId, userPrincipalName, 'POST')) + ) return jobResponses } catch (error) { if (error instanceof ApiError) { @@ -187,11 +191,33 @@ export const addUserToGroups = async (groupIds: string[], userPrincipalName: str } } -const addUserToGroup = async (groupId: string, userPrincipalName: string): Promise => { +export const removeUserFromGroups = async (groupIds: string[], userPrincipalName: string): Promise => { + try { + const jobResponses = await Promise.all( + groupIds.map((groupId) => updateGroupMembership(groupId, userPrincipalName, 'DELETE')) + ) + return jobResponses + } catch (error) { + if (error instanceof ApiError) { + console.error('Failed to remove user from groups: ', error) + throw error + } else { + const apiError = new ApiError(500, 'An unexpected error occurred') + console.error('Failed to remove user from groups: ', apiError) + throw apiError + } + } +} + +const updateGroupMembership = async ( + groupId: string, + userPrincipalName: string, + method: Method +): Promise => { const groupsUrl = `${GROUPS_URL}/${groupId}/users` try { const response = await fetch(groupsUrl, { - method: 'POST', + method: method, headers: { 'content-type': 'application/json', }, @@ -212,11 +238,11 @@ const addUserToGroup = async (groupId: string, userPrincipalName: string): Promi return flattendResponse } catch (error) { if (error instanceof ApiError) { - console.error('Failed to add user to group: ', error) + console.error('Failed to update group membership: ', error) throw error } else { const apiError = new ApiError(500, 'An unexpected error occurred') - console.error('Failed to add user to group: ', apiError) + console.error('Failed to update group membership: ', apiError) throw apiError } } From 05fd92e5a6211b4c8a4aa53b3b15effdb9af4a4e Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Thu, 14 Mar 2024 11:15:20 +0100 Subject: [PATCH 03/18] Update view for in edit user in team Sidebar Modal with correct data --- src/pages/TeamDetail/TeamDetail.tsx | 78 ++++++++++++++++++++++++----- src/services/teamDetail.ts | 2 +- 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index b9708dca..b8a3b5f2 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -6,7 +6,15 @@ import { DropdownItems, TabProps } from '../../@types/pageTypes' import { useCallback, useContext, useEffect, useState } from 'react' import PageLayout from '../../components/PageLayout/PageLayout' -import { TeamDetailData, getTeamDetail, Team, SharedBuckets, addUserToGroups, removeUserFromGroups } from '../../services/teamDetail' +import { + TeamDetailData, + getTeamDetail, + Team, + SharedBuckets, + addUserToGroups, + removeUserFromGroups, + Group, +} from '../../services/teamDetail' import { useParams } from 'react-router-dom' import { ApiError, TokenData, fetchUserInformationFromAuthToken } from '../../utils/services' @@ -31,6 +39,12 @@ import { XCircle } from 'react-feather' import FormattedTableColumn from '../../components/FormattedTableColumn' import SidebarModal from '../../components/SidebarModal/SidebarModal' +interface UserInfo { + name?: string + email?: string + groups: Group[] +} + const TEAM_USERS_TAB = { title: 'Teammedlemmer', path: 'team', @@ -108,6 +122,7 @@ const TeamDetail = () => { // Edit users in team const [openEditUserSidebarModal, setOpenEditUserSidebarModal] = useState(false) + const [editUserInfo, setEditUserInfo] = useState({ name: '', email: '', groups: [] }) const { teamId } = useParams<{ teamId: string }>() const teamDetailTab = (activeTab as TabProps)?.path ?? activeTab @@ -134,8 +149,13 @@ const TeamDetail = () => { if (!teamUsers) return [] return teamUsers.map(({ display_name, principal_name, section_name, groups }) => { + const userFullName = formatDisplayName(display_name) + const userGroups = groups?.filter((group) => + group.uniform_name.startsWith((response.team as Team).uniform_name) + ) + return { - id: formatDisplayName(display_name), + id: userFullName, navn: ( { /> ), seksjon: section_name, // Makes section name searchable and sortable in table by including the field - gruppe: groups - ?.filter((group) => group.uniform_name.startsWith((response.team as Team).uniform_name)) - .map((group) => getGroupType(group.uniform_name)) - .join(', '), + gruppe: userGroups.map((group) => getGroupType(group.uniform_name)).join(', '), epost: principal_name, - editUser: setOpenEditUserSidebarModal(true)}>Endre, + editUser: ( + + { + setOpenEditUserSidebarModal(true) + setEditUserInfo({ + name: formatDisplayName(display_name), + email: principal_name, + groups: userGroups, + }) + }} + > + Endre + + + ), } }) } @@ -305,6 +337,7 @@ const TeamDetail = () => { const renderSidebarModalAlert = () => { return (
+ {/* TODO: Also used in edit user in team */} Det kan ta opp til 45 minutter før personen kan bruke tilgangen {addUserToTeamErrors.length ? ( @@ -326,7 +359,7 @@ const TeamDetail = () => { const renderAddUserSidebarModal = () => { if (teamDetailData) { - const teamGroups = (teamDetailData?.team as Team).groups ?? [] + const teamGroups = (teamDetailData?.team as Team).groups ?? [] // TODO: Duplicate // TODO: modal header is the same for both renderAddUserSidebarModal and renderEditUserSidebarModal return ( { } const renderEditUserSidebarModal = () => { - const display_name = '' - if (teamDetailData) { + if (teamDetailData && editUserInfo) { + const teamGroups = (teamDetailData?.team as Team).groups ?? [] return ( { handleSubmit: () => {}, }} body={{ - modalBodyTitle: `Endre tilgang til ${display_name}`, - modalBody: <>, + modalBodyTitle: `Endre tilgang til "${editUserInfo.name}"`, + modalBody: ( + <> + ({ + id: uniform_name, + title: getGroupType(uniform_name), + }))} + onSelect={() => {}} + /> +
+ {editUserInfo.groups.length && + editUserInfo.groups.map(({ uniform_name }) => ( + } onClick={() => {}}> + {getGroupType(uniform_name)} + + ))} +
+ + ), }} /> ) diff --git a/src/services/teamDetail.ts b/src/services/teamDetail.ts index 804e9a88..b6196883 100644 --- a/src/services/teamDetail.ts +++ b/src/services/teamDetail.ts @@ -33,7 +33,7 @@ export interface User { groups: Group[] } -interface Group { +export interface Group { uniform_name: string display_name: string } From d00fe31fd7fe0bab563534990a80c5a994c30d7f Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Thu, 14 Mar 2024 12:39:24 +0100 Subject: [PATCH 04/18] Added functionality to edit user group in team dropdown --- src/pages/TeamDetail/TeamDetail.tsx | 60 ++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index b8a3b5f2..ea3b96d1 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -42,7 +42,6 @@ import SidebarModal from '../../components/SidebarModal/SidebarModal' interface UserInfo { name?: string email?: string - groups: Group[] } const TEAM_USERS_TAB = { @@ -122,7 +121,8 @@ const TeamDetail = () => { // Edit users in team const [openEditUserSidebarModal, setOpenEditUserSidebarModal] = useState(false) - const [editUserInfo, setEditUserInfo] = useState({ name: '', email: '', groups: [] }) + const [editUserInfo, setEditUserInfo] = useState({ name: '', email: '' }) + const [userGroupTags, setUserGroupTags] = useState([]) const { teamId } = useParams<{ teamId: string }>() const teamDetailTab = (activeTab as TabProps)?.path ?? activeTab @@ -153,7 +153,6 @@ const TeamDetail = () => { const userGroups = groups?.filter((group) => group.uniform_name.startsWith((response.team as Team).uniform_name) ) - return { id: userFullName, navn: ( @@ -174,8 +173,12 @@ const TeamDetail = () => { setEditUserInfo({ name: formatDisplayName(display_name), email: principal_name, - groups: userGroups, }) + setUserGroupTags( + userGroups.map(({ uniform_name }) => { + return { id: uniform_name, title: getGroupType(uniform_name) } + }) + ) }} > Endre @@ -271,21 +274,39 @@ const TeamDetail = () => { } } - const handleAddTeamGroupTag = (item: DropdownItems) => { - const teamGroupsTags = [...teamGroupTags, item].reduce((acc: DropdownItems[], dropdownItem: DropdownItems) => { + const removeDuplicateDropdownItems = (items: DropdownItems[]) => { + return items.reduce((acc: DropdownItems[], dropdownItem: DropdownItems) => { const ids = acc.map((obj) => obj.id) if (!ids.includes(dropdownItem.id)) { acc.push(dropdownItem) } return acc }, []) - setTeamGroupTags(teamGroupsTags) - setTeamGroupTagsError({ ...teamGroupTagsError, error: false }) } - const handleDeleteGroupTag = (item: DropdownItems) => { - const teamGroupsTags = teamGroupTags.filter((items) => items !== item) - setTeamGroupTags(teamGroupsTags) + const handleAddGroupTag = (item: DropdownItems, action: string) => { + if (action === 'add') { + const teamGroupsTags = removeDuplicateDropdownItems([...teamGroupTags, item]) + setTeamGroupTags(teamGroupsTags) + setTeamGroupTagsError({ ...teamGroupTagsError, error: false }) + } + + if (action === 'edit') { + const userGroupsTagsList = removeDuplicateDropdownItems([...userGroupTags, item]) + setUserGroupTags(userGroupsTagsList) + } + } + + const handleDeleteGroupTag = (item: DropdownItems, action: string) => { + if (action === 'add') { + const teamGroupsTags = teamGroupTags.filter((items) => items !== item) + setTeamGroupTags(teamGroupsTags) + } + + if (action === 'edit') { + const userGroupsTags = userGroupTags.filter((items) => items !== item) + setUserGroupTags(userGroupsTags) + } } const isUserInputValid = (value?: string) => { @@ -406,14 +427,14 @@ const TeamDetail = () => { id: uniform_name, title: getGroupType(uniform_name), }))} - onSelect={handleAddTeamGroupTag} + onSelect={(item: DropdownItems) => handleAddGroupTag(item, 'add')} error={teamGroupTagsError.error} errorMessage={teamGroupTagsError.errorMessage} />
{teamGroupTags && teamGroupTags.map((group) => ( - } onClick={() => handleDeleteGroupTag(group)}> + } onClick={() => handleDeleteGroupTag(group, 'add')}> {group.title} ))} @@ -455,13 +476,13 @@ const TeamDetail = () => { id: uniform_name, title: getGroupType(uniform_name), }))} - onSelect={() => {}} + onSelect={(item: DropdownItems) => handleAddGroupTag(item, 'edit')} />
- {editUserInfo.groups.length && - editUserInfo.groups.map(({ uniform_name }) => ( - } onClick={() => {}}> - {getGroupType(uniform_name)} + {userGroupTags && + userGroupTags.map((group) => ( + } onClick={() => handleDeleteGroupTag(group, 'edit')}> + {group.title} ))}
@@ -473,7 +494,8 @@ const TeamDetail = () => { } } - const teamManager = teamDetailData ? (teamDetailData?.team as Team).manager?.principal_name : '' + //const teamManager = teamDetailData ? (teamDetailData?.team as Team).manager?.principal_name : '' + const teamManager = 'jnk@ssb.no' // TODO: REPLACE WITH ABOVE return ( <> {renderAddUserSidebarModal()} From ee6d4939a8e246e41080a12c79c845a97dc68ff9 Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Thu, 14 Mar 2024 13:59:03 +0100 Subject: [PATCH 05/18] . --- server.js | 1 - src/pages/TeamDetail/TeamDetail.tsx | 58 +++++++++++++++++++++++++++-- src/services/teamDetail.ts | 3 ++ 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/server.js b/server.js index d28f5bc2..2132916e 100644 --- a/server.js +++ b/server.js @@ -13,7 +13,6 @@ const app = express() const PORT = process.env.PORT || 3000 const DAPLA_TEAM_API_URL = process.env.DAPLA_TEAM_API_URL || 'https://dapla-team-api-v2.staging-bip-app.ssb.no' -// Proxy, note this middleware must be place before all else.. THIS TOOK ME 3 HOURS TO FIGURE OUT! TODO: Remove comment app.use( '/api', proxy(DAPLA_TEAM_API_URL, { diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index ea3b96d1..5c087685 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -14,6 +14,7 @@ import { addUserToGroups, removeUserFromGroups, Group, + JobResponse, } from '../../services/teamDetail' import { useParams } from 'react-router-dom' import { ApiError, TokenData, fetchUserInformationFromAuthToken } from '../../utils/services' @@ -34,7 +35,7 @@ import { Link, } from '@statisticsnorway/ssb-component-library' import PageSkeleton from '../../components/PageSkeleton/PageSkeleton' -import { Skeleton, CircularProgress } from '@mui/material' +import { Skeleton, CircularProgress, useRadioGroup } from '@mui/material' import { XCircle } from 'react-feather' import FormattedTableColumn from '../../components/FormattedTableColumn' import SidebarModal from '../../components/SidebarModal/SidebarModal' @@ -42,6 +43,7 @@ import SidebarModal from '../../components/SidebarModal/SidebarModal' interface UserInfo { name?: string email?: string + groups?: Group[] } const TEAM_USERS_TAB = { @@ -121,7 +123,7 @@ const TeamDetail = () => { // Edit users in team const [openEditUserSidebarModal, setOpenEditUserSidebarModal] = useState(false) - const [editUserInfo, setEditUserInfo] = useState({ name: '', email: '' }) + const [editUserInfo, setEditUserInfo] = useState({ name: '', email: '', groups: [] }) const [userGroupTags, setUserGroupTags] = useState([]) const { teamId } = useParams<{ teamId: string }>() @@ -173,6 +175,7 @@ const TeamDetail = () => { setEditUserInfo({ name: formatDisplayName(display_name), email: principal_name, + groups: userGroups, }) setUserGroupTags( userGroups.map(({ uniform_name }) => { @@ -355,6 +358,55 @@ const TeamDetail = () => { } } + const handleEditUserOnSubmit = () => { + const addedGroups = + userGroupTags?.filter((groupTag) => !editUserInfo.groups?.some((group) => groupTag.id === group.uniform_name)) ?? + [] + const removedGroups = + editUserInfo.groups?.filter((group) => !userGroupTags?.some((groupTag) => groupTag.id === group.uniform_name)) ?? + [] + if (addedGroups.length && removedGroups.length) { + console.log(`Adding user to groups: ${JSON.stringify(addedGroups)}`) + console.log(`Removing user from groups: ${JSON.stringify(removedGroups)}`) + Promise.all([ + addUserToGroups( + addedGroups.map((group) => group.id), + editUserInfo?.email as string + ), + removeUserFromGroups( + removedGroups.map((group) => group.uniform_name), + editUserInfo?.email as string + ), + ]) + .then((response) => { + const flattenedResponse = [...response[0], ...response[1]] + console.log(flattenedResponse) + }) + .catch((e) => { + console.log(e) + }) + return + } + + if (removedGroups.length) { + console.log(`Removing user from groups: ${JSON.stringify(removedGroups)}`) + removeUserFromGroups( + removedGroups?.map((group) => group.uniform_name), + editUserInfo.email as string + ) + return + } + + if (addedGroups.length) { + console.log(`Adding user to groups: ${JSON.stringify(addedGroups)}`) + addUserToGroups( + addedGroups.map((group) => group.id), + editUserInfo?.email as string + ) + return + } + } + const renderSidebarModalAlert = () => { return (
@@ -462,7 +514,7 @@ const TeamDetail = () => { }} footer={{ submitButtonText: 'Oppdater Tilgang', - handleSubmit: () => {}, + handleSubmit: handleEditUserOnSubmit, }} body={{ modalBodyTitle: `Endre tilgang til "${editUserInfo.name}"`, diff --git a/src/services/teamDetail.ts b/src/services/teamDetail.ts index b6196883..6d2881bd 100644 --- a/src/services/teamDetail.ts +++ b/src/services/teamDetail.ts @@ -215,6 +215,9 @@ const updateGroupMembership = async ( method: Method ): Promise => { const groupsUrl = `${GROUPS_URL}/${groupId}/users` + console.log(JSON.stringify({ + users: [userPrincipalName], + })) try { const response = await fetch(groupsUrl, { method: method, From 61b8acdd126399a50ec28df6c5ed07a87f449465 Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Thu, 14 Mar 2024 14:43:41 +0100 Subject: [PATCH 06/18] Refactors SidebarModal duplicated code for add/edit user in team --- src/pages/TeamDetail/TeamDetail.tsx | 80 ++++++++++++++++------------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index 5c087685..c645244a 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -35,7 +35,7 @@ import { Link, } from '@statisticsnorway/ssb-component-library' import PageSkeleton from '../../components/PageSkeleton/PageSkeleton' -import { Skeleton, CircularProgress, useRadioGroup } from '@mui/material' +import { Skeleton, CircularProgress } from '@mui/material' import { XCircle } from 'react-feather' import FormattedTableColumn from '../../components/FormattedTableColumn' import SidebarModal from '../../components/SidebarModal/SidebarModal' @@ -125,6 +125,7 @@ const TeamDetail = () => { const [openEditUserSidebarModal, setOpenEditUserSidebarModal] = useState(false) const [editUserInfo, setEditUserInfo] = useState({ name: '', email: '', groups: [] }) const [userGroupTags, setUserGroupTags] = useState([]) + const [editUserErrors, setEditUserErrors] = useState>([]) const { teamId } = useParams<{ teamId: string }>() const teamDetailTab = (activeTab as TabProps)?.path ?? activeTab @@ -393,10 +394,10 @@ const TeamDetail = () => { removeUserFromGroups( removedGroups?.map((group) => group.uniform_name), editUserInfo.email as string - ) - return - } - + ) + return + } + if (addedGroups.length) { console.log(`Adding user to groups: ${JSON.stringify(addedGroups)}`) addUserToGroups( @@ -407,42 +408,55 @@ const TeamDetail = () => { } } - const renderSidebarModalAlert = () => { + const renderSidebarModalWarning = (errorList: string[]) => { + return ( + + {typeof errorList === 'string' ? ( + errorList + ) : ( +
    + {errorList.map((errors) => ( +
  • {errors}
  • + ))} +
+ )} +
+ ) + } + + const renderSidebarModalInfo = (action: string) => { + const shownInAddUserToTeamModal = action === 'add' && addUserToTeamErrors.length + const showInEditUserModal = action === 'edit' && editUserErrors.length + return (
- {/* TODO: Also used in edit user in team */} Det kan ta opp til 45 minutter før personen kan bruke tilgangen - {addUserToTeamErrors.length ? ( - - {typeof addUserToTeamErrors === 'string' ? ( - addUserToTeamErrors - ) : ( -
    - {addUserToTeamErrors.map((errors) => ( -
  • {errors}
  • - ))} -
- )} -
- ) : null} - {showSpinner && } + {shownInAddUserToTeamModal ? renderSidebarModalWarning(addUserToTeamErrors) : null} + {showInEditUserModal ? renderSidebarModalWarning(editUserErrors) : null} + {showSpinner && (shownInAddUserToTeamModal || showInEditUserModal) && }
) } + const teamModalHeader = teamDetailData + ? { + modalType: 'Medlem', + modalTitle: `${(teamDetailData?.team as Team).display_name}`, + modalDescription: `${(teamDetailData?.team as Team).uniform_name}`, + } + : { + modalType: '', + modalTitle: '', + modalDescription: '', + } + const teamGroups = teamDetailData ? ((teamDetailData.team as Team).groups as Group[]) : [] const renderAddUserSidebarModal = () => { if (teamDetailData) { - const teamGroups = (teamDetailData?.team as Team).groups ?? [] // TODO: Duplicate - // TODO: modal header is the same for both renderAddUserSidebarModal and renderEditUserSidebarModal return ( setAddUserSidebarModal(false)} - header={{ - modalType: 'Medlem', - modalTitle: `${(teamDetailData?.team as Team).display_name}`, - modalDescription: `${(teamDetailData?.team as Team).uniform_name}`, - }} + header={teamModalHeader} footer={{ submitButtonText: 'Legg til medlem', handleSubmit: handleAddUserOnSubmit, @@ -491,7 +505,7 @@ const TeamDetail = () => { ))}
- {renderSidebarModalAlert()} + {renderSidebarModalInfo('add')} ), }} @@ -502,16 +516,11 @@ const TeamDetail = () => { const renderEditUserSidebarModal = () => { if (teamDetailData && editUserInfo) { - const teamGroups = (teamDetailData?.team as Team).groups ?? [] return ( setOpenEditUserSidebarModal(false)} - header={{ - modalType: 'Medlem', - modalTitle: `${(teamDetailData?.team as Team).display_name}`, - modalDescription: `${(teamDetailData?.team as Team).uniform_name}`, - }} + header={teamModalHeader} footer={{ submitButtonText: 'Oppdater Tilgang', handleSubmit: handleEditUserOnSubmit, @@ -538,6 +547,7 @@ const TeamDetail = () => {
))}
+ {renderSidebarModalInfo('edit')} ), }} From 331a855de3b2a17c562dc3d9b87a1f66952ba9c2 Mon Sep 17 00:00:00 2001 From: ssb-jnk Date: Fri, 15 Mar 2024 06:55:59 +0100 Subject: [PATCH 07/18] temporary fix for removing user from a group --- package-lock.json | 137 ++++++++++++++++++++++++++++++++++++- package.json | 1 + server.js | 41 +++++++++++ src/services/teamDetail.ts | 38 ++++++---- 4 files changed, 200 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 141a4389..e3eeb87a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "eslint-plugin-prettier": "^5.1.3", "express": "4.18.3", "express-http-proxy": "^2.0.0", + "http-proxy-middleware": "^2.0.6", "http-status-codes": "^2.3.0", "jsonwebtoken": "^9.0.2", "jwks-rsa": "^3.1.0", @@ -1679,6 +1680,14 @@ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" }, + "node_modules/@types/http-proxy": { + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2939,6 +2948,11 @@ "node": ">=6" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -3280,6 +3294,25 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -3515,6 +3548,42 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, "node_modules/http-status-codes": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", @@ -3680,6 +3749,17 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4003,7 +4083,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -4727,6 +4806,11 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -6491,6 +6575,14 @@ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" }, + "@types/http-proxy": { + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "requires": { + "@types/node": "*" + } + }, "@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -7383,6 +7475,11 @@ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -7688,6 +7785,11 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" }, + "follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -7852,6 +7954,28 @@ "toidentifier": "1.0.1" } }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "requires": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + } + }, "http-status-codes": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", @@ -7967,6 +8091,11 @@ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" }, + "is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==" + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -8254,7 +8383,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "requires": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -8742,6 +8870,11 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", diff --git a/package.json b/package.json index af1fb132..372f8e3f 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "eslint-plugin-prettier": "^5.1.3", "express": "4.18.3", "express-http-proxy": "^2.0.0", + "http-proxy-middleware": "^2.0.6", "http-status-codes": "^2.3.0", "jsonwebtoken": "^9.0.2", "jwks-rsa": "^3.1.0", diff --git a/server.js b/server.js index 2132916e..1e4129d2 100644 --- a/server.js +++ b/server.js @@ -16,6 +16,10 @@ const DAPLA_TEAM_API_URL = process.env.DAPLA_TEAM_API_URL || 'https://dapla-team app.use( '/api', proxy(DAPLA_TEAM_API_URL, { + proxyReqBodyDecorator: function (bodyContent, srcReq) { + console.log(`Request Body: ${bodyContent}`) + return bodyContent + }, proxyReqOptDecorator: function (proxyReqOpts, srcReq) { console.log(`Request Headers:`, srcReq.headers) if (srcReq.body) { @@ -33,6 +37,10 @@ app.use( console.log(`Response Headers:`, proxyRes.headers) return proxyResData }, + proxyErrorHandler: function (err, res) { + console.error('Proxy Error:', err) + res.status(500).send('Proxy Error') + }, }) ) @@ -65,6 +73,39 @@ async function fetchPhoto(accessToken, url, fallbackErrorMessage) { return photoBuffer.toString('base64') } +//TODO: Remove me once DELETE with proxy is fixed +app.delete('/localApi/groups/:groupUniformName/:userPrincipalName', async (req, res) => { + const token = req.headers.authorization + const groupUniformName = req.params.groupUniformName + const userPrincipalName = req.params.userPrincipalName + const groupsUrl = `${DAPLA_TEAM_API_URL}/groups/${groupUniformName}/users` + + try { + const response = await fetch(groupsUrl, { + method: 'DELETE', + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + Authorization: token, + }, + body: JSON.stringify({ + users: [userPrincipalName], + }), + }) + + if (!response.ok) { + const err = await response.text() + res.status(response.status).send(err) + } else { + const data = await response.json() + res.status(response.status).send(data) + } + } catch (error) { + console.log(error) + res.status(500).send('Internal Server Error') + } +}) + app.get('/localApi/fetch-token', (req, res) => { if (!req.headers.authorization || !req.headers.authorization.startsWith('Bearer')) { return res.status(401).json({ message: 'No token provided' }) diff --git a/src/services/teamDetail.ts b/src/services/teamDetail.ts index 6d2881bd..6053b885 100644 --- a/src/services/teamDetail.ts +++ b/src/services/teamDetail.ts @@ -214,20 +214,28 @@ const updateGroupMembership = async ( userPrincipalName: string, method: Method ): Promise => { - const groupsUrl = `${GROUPS_URL}/${groupId}/users` - console.log(JSON.stringify({ - users: [userPrincipalName], - })) + let groupsUrl = `${GROUPS_URL}/${groupId}/users` + let fetchOptions: RequestInit = { + method: method, + headers: { + Accept: '*/*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + users: [userPrincipalName], + }), + } + + // TODO: Remove me once DELETE with proxy is fixed + if (method === 'DELETE') { + groupsUrl = `/localApi/groups/${groupId}/${userPrincipalName}` + // Don't include body in fetch options for DELETE method + delete fetchOptions.body + } + + try { - const response = await fetch(groupsUrl, { - method: method, - headers: { - 'content-type': 'application/json', - }, - body: JSON.stringify({ - users: [userPrincipalName], - }), - }) + const response = await fetch(groupsUrl, fetchOptions) if (!response.ok) { const errorMessage = (await response.text()) || 'An error occurred' @@ -236,9 +244,9 @@ const updateGroupMembership = async ( } const responseJson = await response.json() - const flattendResponse = { ...responseJson._embedded.results[0] } + const flattenedResponse = { ...responseJson._embedded.results[0] } - return flattendResponse + return flattenedResponse } catch (error) { if (error instanceof ApiError) { console.error('Failed to update group membership: ', error) From 7c76eda74d336a23eed7e8d915e1bb96481f8a27 Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Fri, 15 Mar 2024 08:04:42 +0100 Subject: [PATCH 08/18] . --- src/services/teamDetail.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/services/teamDetail.ts b/src/services/teamDetail.ts index 6053b885..f8d7f665 100644 --- a/src/services/teamDetail.ts +++ b/src/services/teamDetail.ts @@ -215,7 +215,7 @@ const updateGroupMembership = async ( method: Method ): Promise => { let groupsUrl = `${GROUPS_URL}/${groupId}/users` - let fetchOptions: RequestInit = { + const fetchOptions: RequestInit = { method: method, headers: { Accept: '*/*', @@ -232,7 +232,6 @@ const updateGroupMembership = async ( // Don't include body in fetch options for DELETE method delete fetchOptions.body } - try { const response = await fetch(groupsUrl, fetchOptions) From b6777487b33207ab23c69f2952b1946659dd536b Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Mon, 18 Mar 2024 10:04:50 +0100 Subject: [PATCH 09/18] Reset select group dropdown for edit user --- src/pages/TeamDetail/TeamDetail.tsx | 45 ++++++++++++++++++----------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index a0739f6c..d0671fea 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -92,8 +92,9 @@ const defaultEmail = { value: '', } -const defaultSelectedItem = { - key: 'add-user-selected-group', +const defaultAddUserKey = 'add-user-selected-group' +const defaultEditUserKey = 'edit-user-selected-group' +const defaultSelectedGroup = { id: 'velg', title: 'Velg ...', } @@ -116,7 +117,10 @@ const TeamDetail = () => { // Add users to team const [openAddUserSidebarModal, setAddUserSidebarModal] = useState(false) const [email, setEmail] = useState(defaultEmail) - const [selectedItem, setSelectedItem] = useState(defaultSelectedItem) + const [selectedGroupAddUser, setSelectedGroupAddUser] = useState({ + ...defaultSelectedGroup, + key: defaultAddUserKey, + }) const [teamGroupTags, setTeamGroupTags] = useState([]) const [teamGroupTagsError, setTeamGroupTagsError] = useState({ error: false, @@ -127,6 +131,10 @@ const TeamDetail = () => { // Edit users in team const [openEditUserSidebarModal, setOpenEditUserSidebarModal] = useState(false) const [editUserInfo, setEditUserInfo] = useState({ name: '', email: '', groups: [] }) + const [selectedGroupEditUser, setSelectedGroupEditUser] = useState({ + ...defaultSelectedGroup, + key: defaultEditUserKey, + }) const [userGroupTags, setUserGroupTags] = useState([]) const [editUserErrors, setEditUserErrors] = useState>([]) @@ -158,7 +166,7 @@ const TeamDetail = () => { const userFullName = formatDisplayName(display_name) const userGroups = groups?.filter((group) => group.uniform_name.startsWith((response.team as Team).uniform_name) - ) + ) as Group[] return { id: userFullName, navn: ( @@ -296,12 +304,13 @@ const TeamDetail = () => { const teamGroupsTags = removeDuplicateDropdownItems([...teamGroupTags, item]) setTeamGroupTags(teamGroupsTags) setTeamGroupTagsError({ ...teamGroupTagsError, error: false }) - setSelectedItem({ ...item, key: `${selectedItem.key}-${item.id}` }) + setSelectedGroupAddUser({ ...item, key: `${defaultAddUserKey}-${item.id}` }) } if (action === 'edit') { const userGroupsTagsList = removeDuplicateDropdownItems([...userGroupTags, item]) setUserGroupTags(userGroupsTagsList) + setSelectedGroupEditUser({ ...item, key: `${defaultEditUserKey}-${item.id}` }) } } @@ -317,13 +326,6 @@ const TeamDetail = () => { } } - const isUserInputValid = (value?: string) => { - const regEx = /^[\w-]+@ssb\.no$/ - const userVal = value || email.value - const testUser = userVal.match(regEx) - return !!testUser - } - const handleAddUserOnSubmit = () => { if (email.value === '') setEmail({ ...email, error: true }) if (!teamGroupTags.length) @@ -357,7 +359,7 @@ const TeamDetail = () => { setTeamGroupTags([]) // Reset fields with their respective keys; re-initializes component setEmail({ ...defaultEmail }) - setSelectedItem({ ...defaultSelectedItem }) + setSelectedGroupAddUser({ ...defaultSelectedGroup, key: defaultAddUserKey }) } }) .catch((e) => setAddUserToTeamErrors(e.message)) @@ -439,11 +441,19 @@ const TeamDetail = () => { Det kan ta opp til 45 minutter før personen kan bruke tilgangen {shownInAddUserToTeamModal ? renderSidebarModalWarning(addUserToTeamErrors) : null} {showInEditUserModal ? renderSidebarModalWarning(editUserErrors) : null} - {showSpinner && (shownInAddUserToTeamModal || showInEditUserModal) && } + {showSpinner && (shownInAddUserToTeamModal || showInEditUserModal) && }{' '} + {/* TODO: Fix; currently shows on both modals still */}
) } + const isUserInputValid = (value?: string) => { + const regEx = /^[\w-]+@ssb\.no$/ + const userVal = value || email.value + const testUser = userVal.match(regEx) + return !!testUser + } + const teamModalHeader = teamDetailData ? { modalType: 'Medlem', @@ -493,10 +503,10 @@ const TeamDetail = () => { } /> ({ id: uniform_name, title: getGroupType(uniform_name), @@ -542,9 +552,10 @@ const TeamDetail = () => { modalBody: ( <> ({ id: uniform_name, title: getGroupType(uniform_name), From b54e618ba20b489da81224ceb93120b7c67b791b Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Mon, 18 Mar 2024 10:43:04 +0100 Subject: [PATCH 10/18] Update edit user requests; include form resets, spinnger and error messages --- src/pages/TeamDetail/TeamDetail.tsx | 74 +++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index d0671fea..df145243 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -14,6 +14,7 @@ import { addUserToGroups, removeUserFromGroups, Group, + JobResponse, } from '../../services/teamDetail' import { useParams } from 'react-router-dom' import { ApiError, TokenData, fetchUserInformationFromAuthToken } from '../../utils/services' @@ -326,6 +327,17 @@ const TeamDetail = () => { } } + const getErrorList = (response: JobResponse[]) => { + return response + .map(({ status, detail }) => { + if ((detail && status === 'ERROR') || (detail && status === 'IGNORED')) { + return detail + } + return '' + }) + .filter((str) => str !== '') + } + const handleAddUserOnSubmit = () => { if (email.value === '') setEmail({ ...email, error: true }) if (!teamGroupTags.length) @@ -343,15 +355,7 @@ const TeamDetail = () => { email.value ) .then((response) => { - const errorsList = response - .map(({ status, detail }) => { - if ((detail && status === 'ERROR') || (detail && status === 'IGNORED')) { - return detail - } - return '' - }) - .filter((str) => str !== '') - + const errorsList = getErrorList(response) if (errorsList.length) { setAddUserToTeamErrors(errorsList) } else { @@ -377,6 +381,8 @@ const TeamDetail = () => { if (addedGroups.length && removedGroups.length) { console.log(`Adding user to groups: ${JSON.stringify(addedGroups)}`) console.log(`Removing user from groups: ${JSON.stringify(removedGroups)}`) + setEditUserErrors([]) + setShowSpinner(true) Promise.all([ addUserToGroups( addedGroups.map((group) => group.id), @@ -390,28 +396,63 @@ const TeamDetail = () => { .then((response) => { const flattenedResponse = [...response[0], ...response[1]] console.log(flattenedResponse) + const errorsList = getErrorList(flattenedResponse) + if (errorsList.length) { + setEditUserErrors(errorsList) + } else { + setOpenEditUserSidebarModal(false) + // Reset fields with their respective keys; re-initializes component + setSelectedGroupEditUser({ ...defaultSelectedGroup, key: defaultEditUserKey }) + } }) - .catch((e) => { - console.log(e) - }) + .catch((e) => setEditUserErrors(e.message)) + .finally(() => setShowSpinner(false)) return } if (removedGroups.length) { console.log(`Removing user from groups: ${JSON.stringify(removedGroups)}`) + setEditUserErrors([]) + setShowSpinner(true) removeUserFromGroups( removedGroups?.map((group) => group.uniform_name), editUserInfo.email as string ) + .then((response) => { + const errorsList = getErrorList(response) + if (errorsList.length) { + setEditUserErrors(errorsList) + } else { + setOpenEditUserSidebarModal(false) + // Reset fields with their respective keys; re-initializes component + setSelectedGroupEditUser({ ...defaultSelectedGroup, key: defaultEditUserKey }) + } + }) + .catch((e) => setEditUserErrors(e.message)) + .finally(() => setShowSpinner(false)) return } if (addedGroups.length) { console.log(`Adding user to groups: ${JSON.stringify(addedGroups)}`) + setEditUserErrors([]) + setShowSpinner(true) addUserToGroups( addedGroups.map((group) => group.id), editUserInfo?.email as string ) + .then((response) => { + const errorsList = getErrorList(response) + if (errorsList.length) { + setEditUserErrors(errorsList) + } else { + setOpenEditUserSidebarModal(false) + // Reset fields with their respective keys; re-initializes component + setSelectedGroupEditUser({ ...defaultSelectedGroup, key: defaultEditUserKey }) + } + }) + .catch((e) => setEditUserErrors(e.message)) + .finally(() => setShowSpinner(false)) return } } @@ -441,8 +482,9 @@ const TeamDetail = () => { Det kan ta opp til 45 minutter før personen kan bruke tilgangen {shownInAddUserToTeamModal ? renderSidebarModalWarning(addUserToTeamErrors) : null} {showInEditUserModal ? renderSidebarModalWarning(editUserErrors) : null} - {showSpinner && (shownInAddUserToTeamModal || showInEditUserModal) && }{' '} {/* TODO: Fix; currently shows on both modals still */} + {showSpinner && action === 'add' && } + {showSpinner && action === 'edit' && } ) } @@ -565,7 +607,11 @@ const TeamDetail = () => {
{userGroupTags && userGroupTags.map((group) => ( - } onClick={() => handleDeleteGroupTag(group, 'edit')}> + } + onClick={() => handleDeleteGroupTag(group, 'edit')} + > {group.title} ))} From 8a7a7bc1f11cb8dbbda0b818103585a367dacf34 Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Mon, 18 Mar 2024 11:33:08 +0100 Subject: [PATCH 11/18] Fix double spinner and refactor modal code --- src/pages/TeamDetail/TeamDetail.tsx | 94 +++++++++++++++-------------- 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index df145243..cedccdc7 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -4,7 +4,7 @@ import styles from './teamDetail.module.scss' import { DropdownItems, TabProps } from '../../@types/pageTypes' -import { useCallback, useContext, useEffect, useState } from 'react' +import { ReactElement, useCallback, useContext, useEffect, useState } from 'react' import PageLayout from '../../components/PageLayout/PageLayout' import { TeamDetailData, @@ -102,7 +102,6 @@ const defaultSelectedGroup = { const TeamDetail = () => { const [activeTab, setActiveTab] = useState(TEAM_USERS_TAB) - const [showSpinner, setShowSpinner] = useState(false) const [tokenData, setTokenData] = useState() const { setBreadcrumbTeamDetailDisplayName } = useContext(DaplaCtrlContext) @@ -116,7 +115,7 @@ const TeamDetail = () => { const [teamDetailTableData, setTeamDetailTableData] = useState() // Add users to team - const [openAddUserSidebarModal, setAddUserSidebarModal] = useState(false) + const [openAddUserSidebarModal, setOpenAddUserSidebarModal] = useState(false) const [email, setEmail] = useState(defaultEmail) const [selectedGroupAddUser, setSelectedGroupAddUser] = useState({ ...defaultSelectedGroup, @@ -128,6 +127,7 @@ const TeamDetail = () => { errorMessage: 'Velg minst én tilgangsgruppe', }) const [addUserToTeamErrors, setAddUserToTeamErrors] = useState>([]) + const [showAddUserSpinner, setShowAddUserSpinner] = useState(false) // Edit users in team const [openEditUserSidebarModal, setOpenEditUserSidebarModal] = useState(false) @@ -138,6 +138,7 @@ const TeamDetail = () => { }) const [userGroupTags, setUserGroupTags] = useState([]) const [editUserErrors, setEditUserErrors] = useState>([]) + const [showEditUserSpinner, setShowEditUserSpinner] = useState(false) const { teamId } = useParams<{ teamId: string }>() const teamDetailTab = (activeTab as TabProps)?.path ?? activeTab @@ -300,28 +301,28 @@ const TeamDetail = () => { }, []) } - const handleAddGroupTag = (item: DropdownItems, action: string) => { - if (action === 'add') { + const handleAddGroupTag = (item: DropdownItems) => { + if (openAddUserSidebarModal) { const teamGroupsTags = removeDuplicateDropdownItems([...teamGroupTags, item]) setTeamGroupTags(teamGroupsTags) setTeamGroupTagsError({ ...teamGroupTagsError, error: false }) setSelectedGroupAddUser({ ...item, key: `${defaultAddUserKey}-${item.id}` }) } - if (action === 'edit') { + if (openEditUserSidebarModal) { const userGroupsTagsList = removeDuplicateDropdownItems([...userGroupTags, item]) setUserGroupTags(userGroupsTagsList) setSelectedGroupEditUser({ ...item, key: `${defaultEditUserKey}-${item.id}` }) } } - const handleDeleteGroupTag = (item: DropdownItems, action: string) => { - if (action === 'add') { + const handleDeleteGroupTag = (item: DropdownItems) => { + if (openAddUserSidebarModal) { const teamGroupsTags = teamGroupTags.filter((items) => items !== item) setTeamGroupTags(teamGroupsTags) } - if (action === 'edit') { + if (openEditUserSidebarModal) { const userGroupsTags = userGroupTags.filter((items) => items !== item) setUserGroupTags(userGroupsTags) } @@ -349,7 +350,7 @@ const TeamDetail = () => { if (email.value !== '' && teamGroupTags.length) { setEmail({ ...email, key: `add-user-${email.value}` }) setAddUserToTeamErrors([]) - setShowSpinner(true) + setShowAddUserSpinner(true) addUserToGroups( teamGroupTags.map((group) => group.id), email.value @@ -359,7 +360,7 @@ const TeamDetail = () => { if (errorsList.length) { setAddUserToTeamErrors(errorsList) } else { - setAddUserSidebarModal(false) + setOpenAddUserSidebarModal(false) setTeamGroupTags([]) // Reset fields with their respective keys; re-initializes component setEmail({ ...defaultEmail }) @@ -367,7 +368,7 @@ const TeamDetail = () => { } }) .catch((e) => setAddUserToTeamErrors(e.message)) - .finally(() => setShowSpinner(false)) + .finally(() => setShowAddUserSpinner(false)) } } @@ -382,7 +383,7 @@ const TeamDetail = () => { console.log(`Adding user to groups: ${JSON.stringify(addedGroups)}`) console.log(`Removing user from groups: ${JSON.stringify(removedGroups)}`) setEditUserErrors([]) - setShowSpinner(true) + setShowEditUserSpinner(true) Promise.all([ addUserToGroups( addedGroups.map((group) => group.id), @@ -406,14 +407,14 @@ const TeamDetail = () => { } }) .catch((e) => setEditUserErrors(e.message)) - .finally(() => setShowSpinner(false)) + .finally(() => setShowEditUserSpinner(false)) return } if (removedGroups.length) { console.log(`Removing user from groups: ${JSON.stringify(removedGroups)}`) setEditUserErrors([]) - setShowSpinner(true) + setShowEditUserSpinner(true) removeUserFromGroups( removedGroups?.map((group) => group.uniform_name), editUserInfo.email as string @@ -429,14 +430,14 @@ const TeamDetail = () => { } }) .catch((e) => setEditUserErrors(e.message)) - .finally(() => setShowSpinner(false)) + .finally(() => setShowEditUserSpinner(false)) return } if (addedGroups.length) { console.log(`Adding user to groups: ${JSON.stringify(addedGroups)}`) setEditUserErrors([]) - setShowSpinner(true) + setShowEditUserSpinner(true) addUserToGroups( addedGroups.map((group) => group.id), editUserInfo?.email as string @@ -452,11 +453,20 @@ const TeamDetail = () => { } }) .catch((e) => setEditUserErrors(e.message)) - .finally(() => setShowSpinner(false)) + .finally(() => setShowEditUserSpinner(false)) return } } + const renderSidebarModalInfo = (children: ReactElement) => { + return ( +
+ Det kan ta opp til 45 minutter før personen kan bruke tilgangen + {children} +
+ ) + } + const renderSidebarModalWarning = (errorList: string[]) => { return ( @@ -473,22 +483,6 @@ const TeamDetail = () => { ) } - const renderSidebarModalInfo = (action: string) => { - const shownInAddUserToTeamModal = action === 'add' && addUserToTeamErrors.length - const showInEditUserModal = action === 'edit' && editUserErrors.length - - return ( -
- Det kan ta opp til 45 minutter før personen kan bruke tilgangen - {shownInAddUserToTeamModal ? renderSidebarModalWarning(addUserToTeamErrors) : null} - {showInEditUserModal ? renderSidebarModalWarning(editUserErrors) : null} - {/* TODO: Fix; currently shows on both modals still */} - {showSpinner && action === 'add' && } - {showSpinner && action === 'edit' && } -
- ) - } - const isUserInputValid = (value?: string) => { const regEx = /^[\w-]+@ssb\.no$/ const userVal = value || email.value @@ -503,9 +497,7 @@ const TeamDetail = () => { modalDescription: `${(teamDetailData?.team as Team).uniform_name}`, } : { - modalType: '', modalTitle: '', - modalDescription: '', } const teamGroups = teamDetailData ? ((teamDetailData.team as Team).groups as Group[]) : [] const renderAddUserSidebarModal = () => { @@ -513,7 +505,7 @@ const TeamDetail = () => { return ( setAddUserSidebarModal(false)} + onClose={() => setOpenAddUserSidebarModal(false)} header={teamModalHeader} footer={{ submitButtonText: 'Legg til medlem', @@ -553,7 +545,7 @@ const TeamDetail = () => { id: uniform_name, title: getGroupType(uniform_name), }))} - onSelect={(item: DropdownItems) => handleAddGroupTag(item, 'add')} + onSelect={(item: DropdownItems) => handleAddGroupTag(item)} error={teamGroupTagsError.error} errorMessage={teamGroupTagsError.errorMessage} /> @@ -563,13 +555,20 @@ const TeamDetail = () => { } - onClick={() => handleDeleteGroupTag(group, 'add')} + onClick={() => handleDeleteGroupTag(group)} > {group.title} ))}
- {renderSidebarModalInfo('add')} +
+ {renderSidebarModalInfo( + <> + {addUserToTeamErrors.length ? renderSidebarModalWarning(addUserToTeamErrors) : null} + {showAddUserSpinner && } + + )} +
), }} @@ -602,7 +601,7 @@ const TeamDetail = () => { id: uniform_name, title: getGroupType(uniform_name), }))} - onSelect={(item: DropdownItems) => handleAddGroupTag(item, 'edit')} + onSelect={(item: DropdownItems) => handleAddGroupTag(item)} />
{userGroupTags && @@ -610,13 +609,20 @@ const TeamDetail = () => { } - onClick={() => handleDeleteGroupTag(group, 'edit')} + onClick={() => handleDeleteGroupTag(group)} > {group.title} ))}
- {renderSidebarModalInfo('edit')} +
+ {renderSidebarModalInfo( + <> + {editUserErrors.length ? renderSidebarModalWarning(editUserErrors) : null} + {showEditUserSpinner && } + + )} +
), }} @@ -641,7 +647,7 @@ const TeamDetail = () => { content={renderContent()} button={ teamManager?.some((manager) => manager.principal_name === tokenData?.email) ? ( - + ) : undefined } /> From 40f9b91b080c4f4137dacd3521b63380c89f9f28 Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Mon, 18 Mar 2024 13:11:55 +0100 Subject: [PATCH 12/18] Add alignment options for Table and center edit user column data --- src/components/Table/Table.tsx | 6 +++++- src/components/Table/table.module.scss | 7 +++++++ src/pages/TeamDetail/TeamDetail.tsx | 2 ++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index 763a1a99..b2e28158 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -14,11 +14,13 @@ interface TableProps extends TableData { interface TableDesktopViewProps extends TableData { activeTab?: string } + export interface TableData { columns: { id: string label: string unsortable?: boolean + align?: string }[] data: { id: string @@ -133,7 +135,9 @@ const TableDesktopView = ({ columns, data, activeTab }: TableDesktopViewProps) = return ( {columns.map((column) => ( - {row[column.id]} + + {row[column.id]} + ))} ) diff --git a/src/components/Table/table.module.scss b/src/components/Table/table.module.scss index e66ad532..830d20a1 100644 --- a/src/components/Table/table.module.scss +++ b/src/components/Table/table.module.scss @@ -134,4 +134,11 @@ @media #{variables.$mobile} { display: flex; } +} + +.centerText { + span { + display: flex; + justify-content: center; + } } \ No newline at end of file diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index cedccdc7..87c4b034 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -65,6 +65,8 @@ const TEAM_USERS_TAB = { { id: 'editUser', label: '', + unsortable: true, + align: 'center', }, ], } From fe83fb9e660e503e14acb2f4b3265e49d6c50bf9 Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Mon, 18 Mar 2024 13:15:26 +0100 Subject: [PATCH 13/18] . --- src/pages/TeamDetail/TeamDetail.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index 87c4b034..2283fc09 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -382,8 +382,6 @@ const TeamDetail = () => { editUserInfo.groups?.filter((group) => !userGroupTags?.some((groupTag) => groupTag.id === group.uniform_name)) ?? [] if (addedGroups.length && removedGroups.length) { - console.log(`Adding user to groups: ${JSON.stringify(addedGroups)}`) - console.log(`Removing user from groups: ${JSON.stringify(removedGroups)}`) setEditUserErrors([]) setShowEditUserSpinner(true) Promise.all([ @@ -398,7 +396,6 @@ const TeamDetail = () => { ]) .then((response) => { const flattenedResponse = [...response[0], ...response[1]] - console.log(flattenedResponse) const errorsList = getErrorList(flattenedResponse) if (errorsList.length) { setEditUserErrors(errorsList) @@ -414,7 +411,6 @@ const TeamDetail = () => { } if (removedGroups.length) { - console.log(`Removing user from groups: ${JSON.stringify(removedGroups)}`) setEditUserErrors([]) setShowEditUserSpinner(true) removeUserFromGroups( @@ -437,7 +433,6 @@ const TeamDetail = () => { } if (addedGroups.length) { - console.log(`Adding user to groups: ${JSON.stringify(addedGroups)}`) setEditUserErrors([]) setShowEditUserSpinner(true) addUserToGroups( From 1853af56b669a407ee490f8dab4098150d72e4ba Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Mon, 18 Mar 2024 14:39:08 +0100 Subject: [PATCH 14/18] Add delete user feature for edit user in team --- src/pages/TeamDetail/TeamDetail.tsx | 30 ++++++++++++++++++++- src/pages/TeamDetail/teamDetail.module.scss | 21 +++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index 2283fc09..7b13fa5b 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -36,7 +36,7 @@ import { } from '@statisticsnorway/ssb-component-library' import PageSkeleton from '../../components/PageSkeleton/PageSkeleton' import { Skeleton, CircularProgress } from '@mui/material' -import { XCircle } from 'react-feather' +import { XCircle, Trash2 } from 'react-feather' import FormattedTableColumn from '../../components/FormattedTableColumn' import SidebarModal from '../../components/SidebarModal/SidebarModal' @@ -455,6 +455,29 @@ const TeamDetail = () => { } } + const handleDeleteUser = () => { + if (editUserInfo.groups && editUserInfo.groups.length) { + setEditUserErrors([]) + setShowEditUserSpinner(true) + removeUserFromGroups( + editUserInfo.groups.map(({ uniform_name }) => uniform_name), + editUserInfo.email as string + ) + .then((response) => { + const errorsList = getErrorList(response) + if (errorsList.length) { + setEditUserErrors(errorsList) + } else { + setOpenEditUserSidebarModal(false) + // Reset fields with their respective keys; re-initializes component + setSelectedGroupEditUser({ ...defaultSelectedGroup, key: defaultEditUserKey }) + } + }) + .catch((e) => setEditUserErrors(e.message)) + .finally(() => setShowEditUserSpinner(false)) + } + } + const renderSidebarModalInfo = (children: ReactElement) => { return (
@@ -613,6 +636,11 @@ const TeamDetail = () => { ))}
+ {/* TODO: Should be its own component */} + + + Fjern fra teamet + {renderSidebarModalInfo( <> {editUserErrors.length ? renderSidebarModalWarning(editUserErrors) : null} diff --git a/src/pages/TeamDetail/teamDetail.module.scss b/src/pages/TeamDetail/teamDetail.module.scss index 1a548320..1ab2b047 100644 --- a/src/pages/TeamDetail/teamDetail.module.scss +++ b/src/pages/TeamDetail/teamDetail.module.scss @@ -31,4 +31,25 @@ flex-wrap: wrap; gap: .5rem; margin-bottom: 2.5rem; +} + +.removeUserWrapper { + display: inline-flex; + align-items: center; + color: variables.$ssb-red-3; + padding: .5rem; + line-height: 1.7; + cursor: pointer; + + svg { + margin-right: .5rem; + } + + span { + border-bottom: 1px solid; + } + + &:focus { + @include variables.focus-marker; + } } \ No newline at end of file From 2316a492cbf454b2270013ee5c80561ea5e0f425 Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Fri, 22 Mar 2024 09:33:42 +0100 Subject: [PATCH 15/18] Add conditional rendering for user editing in teams --- src/pages/TeamDetail/TeamDetail.tsx | 44 +++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index 7b13fa5b..4d24c125 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -62,12 +62,6 @@ const TEAM_USERS_TAB = { id: 'epost', label: 'Epost', }, - { - id: 'editUser', - label: '', - unsortable: true, - align: 'center', - }, ], } @@ -211,6 +205,11 @@ const TeamDetail = () => { [activeTab] ) + const isTeamManager = () => { + const teamManagers = (teamDetailData && (teamDetailData.team as Team).managers) ?? [] + return teamManagers?.some((manager) => manager.principal_name === tokenData?.email) + } + useEffect(() => { if (!teamId) return fetchUserInformationFromAuthToken() @@ -231,6 +230,20 @@ const TeamDetail = () => { }) }, []) + useEffect(() => { + if (isTeamManager()) { + setTeamDetailTableHeaderColumns([ + ...TEAM_USERS_TAB.columns, + { + id: 'editUser', + label: '', + unsortable: true, + align: 'center', + }, + ]) + } + }, [tokenData, teamDetailData]) + useEffect(() => { if (teamDetailData) { if (teamDetailTab === SHARED_BUCKETS_TAB.path) { @@ -238,7 +251,19 @@ const TeamDetail = () => { setTeamDetailTableHeaderColumns(SHARED_BUCKETS_TAB.columns) } else { setTeamDetailTableTitle(TEAM_USERS_TAB.title) - setTeamDetailTableHeaderColumns(TEAM_USERS_TAB.columns) + if (isTeamManager()) { + setTeamDetailTableHeaderColumns([ + ...TEAM_USERS_TAB.columns, + { + id: 'editUser', + label: '', + unsortable: true, + align: 'center', + }, + ]) + } else { + setTeamDetailTableHeaderColumns(TEAM_USERS_TAB.columns) + } } setTeamDetailTableData(prepTeamData(teamDetailData)) } @@ -656,7 +681,6 @@ const TeamDetail = () => { } } - const teamManager = teamDetailData ? (teamDetailData?.team as Team).managers : [] return ( <> {renderAddUserSidebarModal()} @@ -671,9 +695,7 @@ const TeamDetail = () => { } content={renderContent()} button={ - teamManager?.some((manager) => manager.principal_name === tokenData?.email) ? ( - - ) : undefined + isTeamManager() ? : undefined } /> From 757d36ccd153127e15114ee55083421df7344aa6 Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Fri, 22 Mar 2024 09:56:11 +0100 Subject: [PATCH 16/18] Use useCallback for team manager view conditional --- src/pages/TeamDetail/TeamDetail.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index 4d24c125..3130f31f 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -205,10 +205,10 @@ const TeamDetail = () => { [activeTab] ) - const isTeamManager = () => { + const isTeamManager = useCallback(() => { const teamManagers = (teamDetailData && (teamDetailData.team as Team).managers) ?? [] return teamManagers?.some((manager) => manager.principal_name === tokenData?.email) - } + }, [tokenData, teamDetailData]) useEffect(() => { if (!teamId) return @@ -242,7 +242,7 @@ const TeamDetail = () => { }, ]) } - }, [tokenData, teamDetailData]) + }, [isTeamManager]) useEffect(() => { if (teamDetailData) { From 8042f556f24efc5d2717281f9372f6e51ff32dcc Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Fri, 22 Mar 2024 10:16:18 +0100 Subject: [PATCH 17/18] Create DeleteLink component for editing --- src/components/DeleteLink/DeleteLink.tsx | 21 ++++++++++++++++++ .../DeleteLink/deletelink.module.scss | 22 +++++++++++++++++++ src/pages/TeamDetail/TeamDetail.tsx | 11 +++++----- src/pages/TeamDetail/teamDetail.module.scss | 21 ------------------ 4 files changed, 48 insertions(+), 27 deletions(-) create mode 100644 src/components/DeleteLink/DeleteLink.tsx create mode 100644 src/components/DeleteLink/deletelink.module.scss diff --git a/src/components/DeleteLink/DeleteLink.tsx b/src/components/DeleteLink/DeleteLink.tsx new file mode 100644 index 00000000..a6c0f5ec --- /dev/null +++ b/src/components/DeleteLink/DeleteLink.tsx @@ -0,0 +1,21 @@ +import styles from './deletelink.module.scss' + +import { Trash2 } from 'react-feather' + +interface DeleteLink { + children: string + tabIndex?: number + icon?: boolean + handleDeleteUser: CallableFunction +} + +const DeleteLink = ({ children, tabIndex, icon, handleDeleteUser }: DeleteLink) => { + return ( + handleDeleteUser}> + {icon && } + {children} + + ) +} + +export default DeleteLink diff --git a/src/components/DeleteLink/deletelink.module.scss b/src/components/DeleteLink/deletelink.module.scss new file mode 100644 index 00000000..7ff82185 --- /dev/null +++ b/src/components/DeleteLink/deletelink.module.scss @@ -0,0 +1,22 @@ +@use '@statisticsnorway/ssb-component-library/src/style/variables' as variables; + +.deleteLinkWrapper { + display: inline-flex; + align-items: center; + color: variables.$ssb-red-3; + padding: .5rem; + line-height: 1.7; + cursor: pointer; + + svg { + margin-right: .5rem; + } + + span { + border-bottom: 1px solid; + } + + &:focus { + @include variables.focus-marker; + } +} \ No newline at end of file diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index 3130f31f..e4c94676 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -36,9 +36,10 @@ import { } from '@statisticsnorway/ssb-component-library' import PageSkeleton from '../../components/PageSkeleton/PageSkeleton' import { Skeleton, CircularProgress } from '@mui/material' -import { XCircle, Trash2 } from 'react-feather' +import { XCircle } from 'react-feather' import FormattedTableColumn from '../../components/FormattedTableColumn' import SidebarModal from '../../components/SidebarModal/SidebarModal' +import DeleteLink from '../../components/DeleteLink/DeleteLink' interface UserInfo { name?: string @@ -661,11 +662,9 @@ const TeamDetail = () => { ))}
- {/* TODO: Should be its own component */} - - - Fjern fra teamet - + + Fjern fra teamet + {renderSidebarModalInfo( <> {editUserErrors.length ? renderSidebarModalWarning(editUserErrors) : null} diff --git a/src/pages/TeamDetail/teamDetail.module.scss b/src/pages/TeamDetail/teamDetail.module.scss index 1ab2b047..1a548320 100644 --- a/src/pages/TeamDetail/teamDetail.module.scss +++ b/src/pages/TeamDetail/teamDetail.module.scss @@ -31,25 +31,4 @@ flex-wrap: wrap; gap: .5rem; margin-bottom: 2.5rem; -} - -.removeUserWrapper { - display: inline-flex; - align-items: center; - color: variables.$ssb-red-3; - padding: .5rem; - line-height: 1.7; - cursor: pointer; - - svg { - margin-right: .5rem; - } - - span { - border-bottom: 1px solid; - } - - &:focus { - @include variables.focus-marker; - } } \ No newline at end of file From 3f45b421b261ef871d0fd5465f3b5f15fd7d5238 Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Fri, 22 Mar 2024 10:59:20 +0100 Subject: [PATCH 18/18] Fix view for dropdown on onSelect when editing users --- src/pages/TeamDetail/TeamDetail.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx index 54919c97..476268f6 100644 --- a/src/pages/TeamDetail/TeamDetail.tsx +++ b/src/pages/TeamDetail/TeamDetail.tsx @@ -653,7 +653,7 @@ const TeamDetail = () => { key={selectedGroupEditUser.key} className={styles.dropdownSpacing} header='Tilgangsgrupper(r)' - selectedItem={defaultSelectedGroup} + selectedItem={selectedGroupEditUser} items={teamGroups.map(({ uniform_name }) => ({ id: uniform_name, title: getGroupType(uniform_name),