TP-51166 | Code optimization and pr review comments resolve

This commit is contained in:
pooja-jaiswal_navi
2024-01-15 22:41:55 +05:30
parent 3bd7191d66
commit 00a2867d39
15 changed files with 592 additions and 526 deletions

View File

@@ -1,67 +1,7 @@
.team-details-wrapper {
margin: 20px 0px 0px 79px;
}
.team-input-wrapper {
min-width: (320px);
height: (38px);
}
.add-member {
margin: 72px 0px 24px 0px;
}
.member-div {
margin: 32px 0px 0px 0px;
color: var(--navi-color-gray-c3);
}
.on-call-wrapper {
display: flex;
margin-top: 10px;
}
.team-name {
margin: 24px 24px 24px 0px;
color: var(--navi-color-gray-c1);
}
.slack-details-wrapper {
display: inline-flex;
}
.select-picker-wrapper {
min-height: 80px !important;
position: absolute;
z-index: 1;
}
.on-call-handler {
position: relative;
margin-right: 24px;
}
.search-input {
min-width: 320px !important;
}
.info-icon-wrapper {
display: flex;
align-items: center;
margin-top: 5px;
gap: 2px;
}
.info-icon {
width: 16px;
height: 16px;
margin-right: 4px;
}
.update-details {
margin-left: calc(100% - 120px);
margin-bottom: 31px;
margin-top: 12px;
}
.fallback-component {
height: 100px;
margin-left: 450px;
}
.update-details-disabled {
margin-left: calc(100% - 120px);
opacity: 0;
pointer-events: none;
}
.team-channel-wrapper {
margin-right: 24px;
margin-top: 5px;
}

View File

@@ -1,195 +1,13 @@
import { useDispatch, useSelector } from 'react-redux';
import {
useEffect,
useState,
useRef,
MutableRefObject,
useReducer,
} from 'react';
import {
BorderedInput,
Button,
Tooltip,
Typography,
} from '@navi/web-ui/lib/primitives';
import { SelectPicker } from '@navi/web-ui/lib/components';
import { toast } from '@navi/web-ui/lib/primitives/Toast';
import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types';
import { AlertOutlineIcon } from '@navi/web-ui/lib/icons';
import { fetchTeamDetails, selectSearchTeamData } from '@src/slices/team1Slice';
import { AppDispatch } from '@src/store';
import useOutsideClick from '@src/services/hooks/useOustideClick';
import { useSelector } from 'react-redux';
import { selectSearchTeamData } from '@src/slices/team1Slice';
import FallbackComponent from '@src/components/Fallback';
import { useAuthData } from '@src/services/hooks/useAuth';
import { ApiService } from '@src/services/api';
import { TeamState } from '@src/slices/team1Slice';
import {
UPDATE_TEAM_DATA,
actionTypes,
initialState,
reducer,
} from '../partials/constants';
import { getBots } from '../partials/Bots';
import TeamMemberDetails from '../partials/TeamMemberDetails';
import styles from './TeamDetails.module.scss';
import SlackDetails from '../partials/SlackDetails';
const TeamDetails = () => {
const dispatch = useDispatch<AppDispatch>();
const [state, dispatchData] = useReducer(reducer, initialState);
const teamData: TeamState = useSelector(selectSearchTeamData);
const { teamDetails } = teamData;
const teamId = teamDetails?.id;
const { isPending } = teamData;
const Role = useAuthData();
const userData = JSON.parse(localStorage.getItem('user-data') || '{}');
const userEmail = userData?.emailId || '';
const searchRef = useRef<HTMLInputElement>(null);
const TeamDetails: React.FC = () => {
const { isPending } = useSelector(selectSearchTeamData);
useEffect(() => {
dispatchData({
type: actionTypes.SET_INPUT,
payload: '',
});
dispatchData({
type: actionTypes.SET_PSEC_INPUT,
payload: '',
});
}, [teamData]);
const DEFAULT_TEAM_ONCALL = teamDetails?.oncall?.name
? `@${teamDetails?.oncall?.name}`
: '';
const DEFAULT_TEAM_ONCALL_ID = teamDetails?.oncall?.id || '';
const DEFAULT_TEAM_PSEC_ID = teamDetails?.pse_oncall?.id || '';
const DEFAULT_PSEC_ONCALL =
teamDetails?.pse_oncall && teamDetails?.pse_oncall?.name != ''
? '@' + teamDetails?.pse_oncall?.name
: '';
const webChannelId =
teamDetails?.webhookSlackChannelName !== 'not found'
? `@${teamDetails?.webhookSlackChannelName}`
: teamDetails?.webhookSlackChannelId
? teamDetails?.webhookSlackChannelId
: '';
const handleslackChannelId = (
e: React.ChangeEvent<HTMLInputElement>,
): void => {
dispatchData({
type: actionTypes.SET_SLACK_CHANNEL_ID,
payload: e.target.value,
});
};
const handleOncallChange = (event): void => {
dispatchData({
type: actionTypes.SET_ONCALL,
payload: {
label: event.label,
value: event.value.toString(),
},
});
dispatchData({
type: actionTypes.SET_INPUT,
payload: event.label,
});
dispatchData({
type: actionTypes.SET_OPEN_ONCALL,
payload: false,
});
};
const handlePsecOncall = (event): void => {
dispatchData({
type: actionTypes.SET_PSEC_ONCALL,
payload: {
label: event.label,
value: event.value.toString(),
},
});
dispatchData({
type: actionTypes.SET_PSEC_INPUT,
payload: event.label,
});
dispatchData({
type: actionTypes.SET_PSEC_OPEN_ONCALL,
payload: false,
});
};
const handleOncallOutsideClick = (): void => {
dispatchData({
type: actionTypes.SET_OPEN_ONCALL,
payload: false,
});
};
const handlePsecOncallOutsideClick = (): void => {
dispatchData({
type: actionTypes.SET_PSEC_OPEN_ONCALL,
payload: false,
});
};
const refOncall = useOutsideClick({
callback: handleOncallOutsideClick,
}) as MutableRefObject<HTMLDivElement>;
const refPsecOncall = useOutsideClick({
callback: handlePsecOncallOutsideClick,
}) as MutableRefObject<HTMLDivElement>;
const handleOncallPicker = (): void => {
if (!isUserParticipant(teamDetails) && !Role.includes('Admin')) return;
dispatchData({
type: actionTypes.SET_OPEN_ONCALL,
payload: !state.openOnCall,
});
};
const handleOpenPsecOnCallPicker = (): void => {
if (!isUserParticipant(teamDetails) && !Role.includes('Admin')) return;
dispatchData({
type: actionTypes.SET_PSEC_OPEN_ONCALL,
payload: !state.openPsecOnCall,
});
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
dispatchData({
type: actionTypes.SET_INPUT,
payload: e.target.value,
});
};
const handlePsecInputChange = (
e: React.ChangeEvent<HTMLInputElement>,
): void => {
dispatchData({
type: actionTypes.SET_PSEC_INPUT,
payload: e.target.value,
});
};
const submitHandler = (): void => {
const endPoint = UPDATE_TEAM_DATA();
ApiService.post(endPoint, {
id: teamId,
webhook_slack_channel: state.slackChannelId,
on_call_handle: state.oncall.value,
pse_on_call_id: state.psecOncall.value,
})
.then(response => {
toast.success(response?.data?.data);
dispatch(fetchTeamDetails(teamId?.toString() || ''));
dispatchData({
type: actionTypes.SET_OPEN_ONCALL,
payload: false,
});
dispatchData({
type: actionTypes.SET_PSEC_OPEN_ONCALL,
payload: false,
});
})
.catch(error => {
const toastMessage = `${
error?.response?.data?.error?.message
? `${error?.response?.data?.error?.message}`
: 'Something went wrong,Pls try again later'
}`;
toast.error(toastMessage);
});
};
if (isPending) {
return (
<div className={styles['fallback-component']}>
@@ -197,132 +15,9 @@ const TeamDetails = () => {
</div>
);
}
const managerEmail = teamDetails?.participants?.find(
participant => participant.id === teamDetails?.managerId,
)?.email;
const isUserParticipant = (teamDetails: any) => {
const participantEmails = teamDetails?.participants?.map(
(participant: any) => participant.email,
);
return participantEmails?.includes(userEmail);
};
return (
<div className={styles['team-details-wrapper']}>
<Typography variant="h1" className={styles['team-name']}>
{teamDetails?.name}
</Typography>{' '}
<Typography variant="h5" className={styles['member-div']}>
SLACK DETAILS
</Typography>{' '}
<div className={styles['slack-details-wrapper']}>
<div className={styles['team-channel-wrapper']}>
<div className={styles['info-icon-wrapper']}>
<Typography variant="h6">Team channel</Typography>
<div className={styles['info-icon']}>
<Tooltip
text="Slack channel I.D. can be found at the bottom of
the channel details window"
withPointer
position={'right'}
>
<AlertOutlineIcon />
</Tooltip>
</div>
</div>
<BorderedInput
inputLabel=""
inputSize="medium"
placeholder="Enter channel I.D"
onChange={handleslackChannelId}
containerClassName={styles['team-input-wrapper']}
value={webChannelId}
hintMsg="Channel name populates after entering id"
disabled={
!isUserParticipant(teamDetails) && !Role.includes('Admin')
}
/>
</div>
<div className={styles['on-call-wrapper']}>
<div ref={refOncall}>
<Typography variant="h6">Team on call</Typography>{' '}
<div
onClick={handleOncallPicker}
className={styles['on-call-handler']}
>
<BorderedInput
onChange={handleInputChange}
placeholder="@oncall_handle"
containerClassName={styles['search-input']}
value={state.input || DEFAULT_TEAM_ONCALL}
disabled={
!isUserParticipant(teamDetails) && !Role.includes('Admin')
}
></BorderedInput>
</div>
{state.openOnCall && (
<SelectPicker
options={getBots()}
onSelectionChange={handleOncallChange}
selectedValue={state.oncall.value || DEFAULT_TEAM_ONCALL_ID}
multiSelect={false}
updateSearch={
state.input || searchRef.current ? state.input : ''
}
wrapperClasses={styles['select-picker-wrapper']}
></SelectPicker>
)}
</div>
<div ref={refPsecOncall}>
<Typography variant="h6">PSEC on call</Typography>
<div
onClick={handleOpenPsecOnCallPicker}
className={styles['on-call-handler']}
>
<BorderedInput
onChange={handlePsecInputChange}
placeholder="@psec_oncall_handle"
containerClassName={styles['search-input']}
value={state.psecInput || DEFAULT_PSEC_ONCALL}
disabled={
!isUserParticipant(teamDetails) && !Role.includes('Admin')
}
></BorderedInput>
</div>
{state.openPsecOnCall && (
<SelectPicker
options={getBots()}
onSelectionChange={handlePsecOncall}
multiSelect={false}
selectedValue={state.psecOncall.value || DEFAULT_TEAM_PSEC_ID}
updateSearch={
state.psecInput || searchRef.current ? state.psecInput : ''
}
wrapperClasses={styles['select-picker-wrapper']}
></SelectPicker>
)}
<div
className={
managerEmail === userEmail ||
isUserParticipant(teamDetails) ||
Role.includes('Admin')
? styles['update-details']
: styles['update-details-disabled']
}
>
<Button
variant="text"
onClick={submitHandler}
disabled={
!state.slackChannelId && !state.psecInput && !state.input
}
>
Update details
</Button>
</div>
</div>
</div>
</div>
<SlackDetails />
<TeamMemberDetails />
</div>
);

View File

@@ -0,0 +1,21 @@
import React from 'react';
import { BorderedInput } from '@navi/web-ui/lib/primitives';
import { SearchIcon } from '@navi/web-ui/lib/icons';
import { SearchInputComponentProps } from '../constants';
import styles from './TeamList.module.scss';
const SearchInput: React.FC<SearchInputComponentProps> = ({
value,
onChange,
}) => {
return (
<BorderedInput
LeftInputAdornment={<SearchIcon />}
onChange={onChange}
containerClassName={styles['search-input']}
value={value}
/>
);
};
export default SearchInput;

View File

@@ -2,13 +2,15 @@ import { useEffect, useState, useRef } from 'react';
import { AppDispatch, RootState } from '@src/store';
import { useDispatch, useSelector } from 'react-redux';
import { SelectPicker } from '@navi/web-ui/lib/components';
import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types';
import { BorderedInput, Button, Typography } from '@navi/web-ui/lib/primitives';
import { AddIcon, SearchIcon } from '@navi/web-ui/lib/icons';
import PersonIcon from '@src/assets/PersonIcon';
import { useAuthData } from '@src/services/hooks/useAuth';
import CreateTeam from '@src/Pages/TeamRevamp/partials/CreateTeam/CreateTeam';
import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types';
import { Button, Typography } from '@navi/web-ui/lib/primitives';
import { AddIcon } from '@navi/web-ui/lib/icons';
import PersonIcon from '@src/assets/PersonIcon';
import { fetchTeamDetails, setModalOpen } from '@src/slices/team1Slice';
import CreateTeam from '@src/Pages/TeamRevamp/partials/CreateTeam/CreateTeam';
import SearchInput from './SearchInput';
import styles from './TeamList.module.scss';
type Options = {
@@ -30,7 +32,7 @@ const CustomTeamOptions = (option: Options): JSX.Element => {
);
};
const TeamList = () => {
const TeamList: React.FC = () => {
const dispatch = useDispatch<AppDispatch>();
const Role = useAuthData();
const teamList = useSelector((state: RootState) => state.team1.teams.data);
@@ -104,17 +106,12 @@ const TeamList = () => {
fetchDetails(event.value.toString());
scroll.current = false;
};
return (
<div>
<div className={styles['team-div']}>
<div className={styles['search-wrapper']}>
<BorderedInput
LeftInputAdornment={<SearchIcon />}
onChange={handleInputChange}
containerClassName={styles['search-input']}
value={input || ''}
/>
<SearchInput value={input} onChange={handleInputChange} />
{Role.includes('Admin') && (
<Button
startAdornment={<AddIcon color="white" size="lg" />}

View File

@@ -1,4 +1,7 @@
import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types';
import { createURL } from '@src/services/globalUtils';
import { ChangeEvent } from 'react';
const URL_PREFIX = createURL('/houston');
export const FETCH_TEAM_DATA = `${URL_PREFIX}/teams`;
@@ -22,9 +25,13 @@ export const MAKE_MANAGER = (teamId: string, userId: string): string => {
};
export const regularExpression = /^[a-zA-Z][a-zA-Z0-9_ -]{1,48}[a-zA-Z0-9]$/;
export const validEmail = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
export const emailRegularExpression = /^[a-zA-Z]+\.[a-zA-Z]+@navi\.com$/;
export interface SearchInputComponentProps {
value: string;
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
}
export interface TeamsData {
id?: string;
name?: string;
@@ -32,7 +39,11 @@ export interface TeamsData {
lastUpdatedAt?: string;
expand?: boolean;
}
export interface CreateTeamProps {
setTeam: React.Dispatch<
React.SetStateAction<SelectPickerOptionProps | undefined>
>;
}
export interface BotsData {
id: string;
name: string;

View File

@@ -18,14 +18,11 @@ import styles from './Team.module.scss';
const TeamRevamp: FC = () => {
const dispatch = useDispatch<AppDispatch>();
const teamData: TeamState = useSelector(selectSearchTeamData);
const { data } = teamData;
const { selectedTeam } = teamData;
const { data, selectedTeam } = teamData;
const selectedTeamId = selectedTeam?.id || '';
useEffect(() => {
dispatch(fetchTeams());
}, []);
useEffect(() => {
fetchAllBots();
}, []);
@@ -34,6 +31,7 @@ const TeamRevamp: FC = () => {
dispatch(setSelectedTeam(data[0]));
}
}, []);
useEffect(() => {
if (selectedTeam) {
dispatch(fetchTeamDetails(selectedTeamId.toString()));
@@ -46,7 +44,6 @@ const TeamRevamp: FC = () => {
<Typography variant="h3">Teams</Typography>
<div className={styles['wrapper-class']}>
<TeamList />
<TeamDetails />
</div>
</div>

View File

@@ -1,5 +1,5 @@
import { ApiService } from '@src/services/api';
import { FETCH_ALL_BOTS_DATA, PickerOptionProps } from './constants';
import { FETCH_ALL_BOTS_DATA, PickerOptionProps } from '../constants';
import { toast } from '@navi/web-ui/lib/primitives/Toast';
let bots: Array<PickerOptionProps> = [];

View File

@@ -3,7 +3,6 @@ import { useDispatch, useSelector } from 'react-redux';
import { Typography } from '@navi/web-ui/lib/primitives';
import { AlertOutlineIcon } from '@navi/web-ui/lib/icons';
import { BorderedInput, ModalDialog } from '@navi/web-ui/lib/primitives';
import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types';
import { toast } from '@navi/web-ui/lib/primitives/Toast';
import { AppDispatch } from '@src/store';
import { ApiService } from '@src/services/api';
@@ -14,19 +13,15 @@ import {
setModalOpen,
setNewTeam,
} from '@src/slices/team1Slice';
import { CREATE_TEAM } from '@src/Pages/TeamRevamp/partials/constants';
import { CREATE_TEAM } from '@src/Pages/TeamRevamp/constants';
import {
regularExpression,
emailRegularExpression,
} from '@src/Pages/TeamRevamp/partials/constants';
validEmail,
CreateTeamProps,
} from '@src/Pages/TeamRevamp/constants';
import styles from './CreateTeam.module.scss';
interface CreateTeamProps {
setTeam: React.Dispatch<
React.SetStateAction<SelectPickerOptionProps | undefined>
>;
}
const CreateTeam: React.FC<CreateTeamProps> = ({ setTeam }) => {
const dispatch = useDispatch<AppDispatch>();
const { modalOpen } = useSelector(selectSearchTeamData);
@@ -44,7 +39,6 @@ const CreateTeam: React.FC<CreateTeamProps> = ({ setTeam }) => {
};
const validateEmail = (value: string): void => {
const validEmail = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
setEmailError(
validEmail.test(value)
? !emailRegularExpression.test(value)

View File

@@ -0,0 +1,62 @@
.team-input-wrapper {
min-width: (320px);
height: (38px);
}
.add-member {
margin: 72px 0px 24px 0px;
}
.member-div {
margin: 32px 0px 0px 0px;
color: var(--navi-color-gray-c3);
}
.on-call-wrapper {
display: flex;
margin-top: 10px;
}
.team-name {
margin: 24px 24px 24px 0px;
color: var(--navi-color-gray-c1);
}
.slack-details-wrapper {
display: inline-flex;
}
.select-picker-wrapper {
min-height: 80px !important;
position: absolute;
z-index: 1;
}
.on-call-handler {
position: relative;
margin-right: 24px;
}
.search-input {
min-width: 320px !important;
}
.info-icon-wrapper {
display: flex;
align-items: center;
margin-top: 5px;
gap: 2px;
}
.info-icon {
width: 16px;
height: 16px;
margin-right: 4px;
}
.update-details {
margin-left: calc(100% - 120px);
margin-bottom: 31px;
margin-top: 12px;
}
.update-details-disabled {
margin-left: calc(100% - 120px);
opacity: 0;
pointer-events: none;
margin-bottom: 31px;
margin-top: 12px;
}
.team-channel-wrapper {
margin-right: 24px;
margin-top: 5px;
}

View File

@@ -0,0 +1,311 @@
import { useDispatch, useSelector } from 'react-redux';
import {
useEffect,
useState,
useRef,
MutableRefObject,
useReducer,
} from 'react';
import {
BorderedInput,
Button,
Tooltip,
Typography,
} from '@navi/web-ui/lib/primitives';
import { SelectPicker } from '@navi/web-ui/lib/components';
import { toast } from '@navi/web-ui/lib/primitives/Toast';
import { AlertOutlineIcon } from '@navi/web-ui/lib/icons';
import { fetchTeamDetails } from '@src/slices/team1Slice';
import { AppDispatch } from '@src/store';
import useOutsideClick from '@src/services/hooks/useOustideClick';
import { useAuthData } from '@src/services/hooks/useAuth';
import { ApiService } from '@src/services/api';
import {
TeamsDetail,
UPDATE_TEAM_DATA,
actionTypes,
initialState,
reducer,
} from '../../constants';
import { getBots } from '../Bots';
import useGetTeamDetailsConstants from '../../util';
import styles from './Slack.module.scss';
const TeamDetails: React.FC = () => {
const dispatch = useDispatch<AppDispatch>();
const [state, dispatchData] = useReducer(reducer, initialState);
const Role = useAuthData();
const {
teamId,
userEmail,
managerEmail,
teamDetails,
teamData,
DEFAULT_TEAM_ONCALL,
DEFAULT_TEAM_ONCALL_ID,
DEFAULT_TEAM_PSEC_ID,
DEFAULT_PSEC_ONCALL,
webChannelId,
} = useGetTeamDetailsConstants();
const searchRef = useRef<HTMLInputElement>(null);
useEffect(() => {
dispatchData({
type: actionTypes.SET_INPUT,
payload: '',
});
dispatchData({
type: actionTypes.SET_PSEC_INPUT,
payload: '',
});
}, [teamData]);
const handleslackChannelId = (
e: React.ChangeEvent<HTMLInputElement>,
): void => {
dispatchData({
type: actionTypes.SET_SLACK_CHANNEL_ID,
payload: e.target.value,
});
};
const handleOncallChange = (event): void => {
dispatchData({
type: actionTypes.SET_ONCALL,
payload: {
label: event.label,
value: event.value.toString(),
},
});
dispatchData({
type: actionTypes.SET_INPUT,
payload: event.label,
});
dispatchData({
type: actionTypes.SET_OPEN_ONCALL,
payload: false,
});
};
const handlePsecOncall = (event): void => {
dispatchData({
type: actionTypes.SET_PSEC_ONCALL,
payload: {
label: event.label,
value: event.value.toString(),
},
});
dispatchData({
type: actionTypes.SET_PSEC_INPUT,
payload: event.label,
});
dispatchData({
type: actionTypes.SET_PSEC_OPEN_ONCALL,
payload: false,
});
};
const handleOncallOutsideClick = (): void => {
dispatchData({
type: actionTypes.SET_OPEN_ONCALL,
payload: false,
});
};
const handlePsecOncallOutsideClick = (): void => {
dispatchData({
type: actionTypes.SET_PSEC_OPEN_ONCALL,
payload: false,
});
};
const refOncall = useOutsideClick({
callback: handleOncallOutsideClick,
}) as MutableRefObject<HTMLDivElement>;
const refPsecOncall = useOutsideClick({
callback: handlePsecOncallOutsideClick,
}) as MutableRefObject<HTMLDivElement>;
const handleOncallPicker = (): void => {
if (!isUserParticipant(teamDetails) && !Role.includes('Admin')) return;
dispatchData({
type: actionTypes.SET_OPEN_ONCALL,
payload: !state.openOnCall,
});
};
const handleOpenPsecOnCallPicker = (): void => {
if (!isUserParticipant(teamDetails) && !Role.includes('Admin')) return;
dispatchData({
type: actionTypes.SET_PSEC_OPEN_ONCALL,
payload: !state.openPsecOnCall,
});
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
dispatchData({
type: actionTypes.SET_INPUT,
payload: e.target.value,
});
};
const handlePsecInputChange = (
e: React.ChangeEvent<HTMLInputElement>,
): void => {
dispatchData({
type: actionTypes.SET_PSEC_INPUT,
payload: e.target.value,
});
};
const updateDetails = (): void => {
const endPoint = UPDATE_TEAM_DATA();
ApiService.post(endPoint, {
id: teamId,
webhook_slack_channel: state.slackChannelId,
on_call_handle: state.oncall.value,
pse_on_call_id: state.psecOncall.value,
})
.then(response => {
toast.success(response?.data?.data);
dispatch(fetchTeamDetails(teamId?.toString() || ''));
dispatchData({
type: actionTypes.SET_OPEN_ONCALL,
payload: false,
});
dispatchData({
type: actionTypes.SET_PSEC_OPEN_ONCALL,
payload: false,
});
})
.catch(error => {
const toastMessage = `${
error?.response?.data?.error?.message
? `${error?.response?.data?.error?.message}`
: 'Something went wrong,Pls try again later'
}`;
toast.error(toastMessage);
});
};
const isUserParticipant = (teamDetails: TeamsDetail): boolean | undefined => {
const participantEmails = teamDetails?.participants?.map(
(participant: any) => participant.email,
);
return participantEmails?.includes(userEmail);
};
return (
<div className={styles['team-details-wrapper']}>
<Typography variant="h1" className={styles['team-name']}>
{teamDetails?.name}
</Typography>{' '}
<Typography variant="h5" className={styles['member-div']}>
SLACK DETAILS
</Typography>{' '}
<div className={styles['slack-details-wrapper']}>
<div className={styles['team-channel-wrapper']}>
<div className={styles['info-icon-wrapper']}>
<Typography variant="h6">Team channel</Typography>
<div className={styles['info-icon']}>
<Tooltip
text="Slack channel I.D. can be found at the bottom of
the channel details window"
withPointer
position={'right'}
>
<AlertOutlineIcon />
</Tooltip>
</div>
</div>
<BorderedInput
inputLabel=""
inputSize="medium"
placeholder="Enter channel I.D"
onChange={handleslackChannelId}
containerClassName={styles['team-input-wrapper']}
value={webChannelId}
hintMsg="Channel name populates after entering id"
disabled={
!isUserParticipant(teamDetails) && !Role.includes('Admin')
}
/>
</div>
<div className={styles['on-call-wrapper']}>
<div ref={refOncall}>
<Typography variant="h6">Team on call</Typography>{' '}
<div
onClick={handleOncallPicker}
className={styles['on-call-handler']}
>
<BorderedInput
onChange={handleInputChange}
placeholder="@oncall_handle"
containerClassName={styles['search-input']}
value={state.input || DEFAULT_TEAM_ONCALL}
disabled={
!isUserParticipant(teamDetails) && !Role.includes('Admin')
}
></BorderedInput>
</div>
{state.openOnCall && (
<SelectPicker
options={getBots()}
onSelectionChange={handleOncallChange}
selectedValue={state.oncall.value || DEFAULT_TEAM_ONCALL_ID}
multiSelect={false}
updateSearch={
state.input || searchRef.current ? state.input : ''
}
wrapperClasses={styles['select-picker-wrapper']}
></SelectPicker>
)}
</div>
<div ref={refPsecOncall}>
<Typography variant="h6">PSEC on call</Typography>
<div
onClick={handleOpenPsecOnCallPicker}
className={styles['on-call-handler']}
>
<BorderedInput
onChange={handlePsecInputChange}
placeholder="@psec_oncall_handle"
containerClassName={styles['search-input']}
value={state.psecInput || DEFAULT_PSEC_ONCALL}
disabled={
!isUserParticipant(teamDetails) && !Role.includes('Admin')
}
></BorderedInput>
</div>
{state.openPsecOnCall && (
<SelectPicker
options={getBots()}
onSelectionChange={handlePsecOncall}
multiSelect={false}
selectedValue={state.psecOncall.value || DEFAULT_TEAM_PSEC_ID}
updateSearch={
state.psecInput || searchRef.current ? state.psecInput : ''
}
wrapperClasses={styles['select-picker-wrapper']}
></SelectPicker>
)}
<div
className={
managerEmail === userEmail ||
isUserParticipant(teamDetails) ||
Role.includes('Admin')
? styles['update-details']
: styles['update-details-disabled']
}
>
<Button
variant="text"
onClick={updateDetails}
disabled={
!state.slackChannelId && !state.psecInput && !state.input
}
>
Update details
</Button>
</div>
</div>
</div>
</div>
</div>
);
};
export default TeamDetails;

View File

@@ -9,6 +9,7 @@
display: grid;
grid-template-columns: 1fr 1fr 1fr;
justify-content: space-around;
gap: 12px;
}
.item-details {
margin-top: 12px;

View File

@@ -1,36 +1,23 @@
import { useDispatch, useSelector } from 'react-redux';
import { useState, useReducer } from 'react';
import {
fetchTeamDetails,
fetchTeams,
selectSearchTeamData,
} from '@src/slices/team1Slice';
import { useReducer } from 'react';
import {
BorderedInput,
Button,
Typography,
ModalDialog,
} from '@navi/web-ui/lib/primitives';
import { toast } from '@navi/web-ui/lib/primitives/Toast';
import PersonIconFill from '@src/assets/PersonIconFill';
import ManagerIconFill from '@src/assets/ManagerIconFill';
import MemberIcon from '@src/assets/MemberIcon';
import DeleteIcon from '@src/assets/DeleteIcon';
import AlertIcon from '@src/assets/AlertIcon';
import { ApiService } from '@src/services/api';
import { AppDispatch } from '@src/store';
import FallbackComponent from '@src/components/Fallback';
import { useAuthData } from '@src/services/hooks/useAuth';
import { TeamState } from '@src/slices/team1Slice';
import { useGetTeamDetailsConstants } from '@src/Pages/TeamRevamp/util';
import useTeamApis from '../../useTeamApis';
import {
MAKE_MANAGER,
REMOVE_TEAM_MEMBER,
TeamsDetail,
UPDATE_TEAM_DATA,
actionTypes,
initialState,
reducer,
} from '@src/Pages/TeamRevamp/partials/constants';
} from '@src/Pages/TeamRevamp/constants';
import styles from './TeamMemberDetails.module.scss';
type MemberType = {
@@ -38,31 +25,13 @@ type MemberType = {
name: string;
email: string;
};
const TeamMemberDetails = () => {
const TeamMemberDetails: React.FC = () => {
const [state, dispatchData] = useReducer(reducer, initialState);
const teamData: TeamState = useSelector(selectSearchTeamData);
const dispatch = useDispatch<AppDispatch>();
const userData = JSON.parse(localStorage.getItem('user-data') || '{}');
const Role = useAuthData();
const { teamDetails } = teamData;
const { isPending } = teamData;
const [loading, setLoading] = useState(false);
const teamId = teamDetails?.id;
const userEmail = userData?.emailId || '';
const managerEmail = teamDetails?.participants?.find(
item => item.id === teamDetails?.managerId,
)?.email;
const manager = teamDetails?.participants?.find(
item => item.id === teamDetails?.managerId,
);
const teamMembers = teamDetails?.participants?.map(item => ({
name: item.name,
email: item.email,
id: item.id,
}));
const isUserParticipant = (teamDetails: TeamsDetail) => {
const { teamId, userEmail, managerEmail, manager, teamMembers, teamDetails } =
useGetTeamDetailsConstants();
const { addMemberHandler, removeTeamMember, makeManager } = useTeamApis();
const isUserParticipant = (teamDetails: TeamsDetail): boolean | undefined => {
const participantEmails = teamDetails?.participants?.map(
(participant: any) => participant.email,
);
@@ -74,91 +43,19 @@ const TeamMemberDetails = () => {
payload: e.target.value,
});
};
if (loading) {
return (
<div className={styles['fallback-component']}>
<FallbackComponent />
</div>
);
}
if (isPending) {
return (
<div className={styles['fallback-component']}>
<FallbackComponent />
</div>
);
}
const onKeyPressClickHandler = (event: React.KeyboardEvent): void => {
const onKeyPressClickHandler = event => {
if (event.key === 'Enter') {
addMemberHandler();
addMemberHandler(state.emailIds);
dispatchData({
type: actionTypes.SET_EMAIL_IDS,
payload: '',
});
}
dispatchData({
type: actionTypes.SET_EMAIL_IDS,
payload: '',
});
};
const addMemberHandler = (): void => {
setLoading(true);
const endPoint = UPDATE_TEAM_DATA();
const finalSlackData = state.emailIds?.includes(',')
? state.emailIds.split(',').map(item => item?.trim())
: [state.emailIds];
ApiService.post(endPoint, {
id: teamId,
workEmailIds: finalSlackData,
})
.then(response => {
toast.success(response?.data?.data);
setLoading(false);
dispatch(fetchTeamDetails(teamId?.toString() || ''));
dispatch(fetchTeams());
})
.catch(error => {
const toastMessage = `${
error?.response?.data?.error?.message
? `${error?.response?.data?.error?.message}`
: 'Something went wrong,Pls try again later'
}`;
toast.error(toastMessage);
});
};
const removeMemberHandler = (memberID: string): void => {
setLoading(true);
const endpoint = REMOVE_TEAM_MEMBER(teamId?.toString() || '', memberID);
ApiService.delete(endpoint)
.then(response => {
setLoading(false);
if (response.status === 200) {
toast(response.data.data, {
icon: <AlertIcon />,
});
dispatch(fetchTeamDetails(teamId?.toString() || ''));
dispatch(fetchTeams());
} else {
toast.error(response.data.error.message);
}
})
.catch(error => {
toast.error(`Error removing member from team : ${error.message}`);
});
};
const handleMakeManager = (memberID: string): void => {
setLoading(true);
const endpoint = MAKE_MANAGER(teamId?.toString() || '', memberID);
ApiService.patch(endpoint, {})
.then(response => {
if (response.status === 200) {
setLoading(false);
toast.success(response.data.data);
dispatch(fetchTeamDetails(teamId?.toString() || ''));
} else {
toast.error(response.data.error.message);
}
})
.catch(error => {
toast.error(`Error in making manager of team : ${error.message}`);
});
};
const handleOpenModal = (member: MemberType) => () => {
dispatchData({
@@ -186,14 +83,14 @@ const TeamMemberDetails = () => {
payload: true,
});
};
const handleResetDialog = () => {
const handleResetDialog = (): void => {
dispatchData({
type: actionTypes.SET_OPEN_DIALOG,
payload: false,
});
};
const handleHoverchange = (item: MemberType) => {
const handleHoverchange = (item: MemberType): void => {
if (userEmail === managerEmail || Role.includes('Admin')) {
dispatchData({
type: actionTypes.SET_HOVERED,
@@ -204,7 +101,7 @@ const TeamMemberDetails = () => {
});
}
};
const handleHoverIcon = (item: MemberType) => {
const handleHoverIcon = (item: MemberType): void => {
if (userEmail === managerEmail || Role.includes('Admin')) {
dispatchData({
type: actionTypes.SET_HOVERED,
@@ -302,7 +199,7 @@ const TeamMemberDetails = () => {
{
label: 'Update',
onClick: () => {
handleMakeManager(state.selectedMember?.id.toString() || '');
makeManager(state.selectedMember?.id.toString() || '');
handleResetModal();
},
},
@@ -340,7 +237,7 @@ const TeamMemberDetails = () => {
<Button
variant="text"
onClick={() => {
removeMemberHandler(state.selectedMember?.id.toString() || '');
removeTeamMember(state.selectedMember?.id.toString() || '');
handleResetDialog();
}}
className={styles['remove-button']}

View File

@@ -0,0 +1,85 @@
import { useDispatch } from 'react-redux';
import { toast } from '@navi/web-ui/lib/primitives/Toast';
import { AlertIcon } from '@navi/web-ui/lib/icons';
import { AppDispatch } from '@src/store';
import { ApiService } from '@src/services/api';
import { fetchTeamDetails, fetchTeams } from '@src/slices/team1Slice';
import {
MAKE_MANAGER,
REMOVE_TEAM_MEMBER,
UPDATE_TEAM_DATA,
} from './constants';
import { useGetTeamDetailsConstants } from './util';
const useTeamApis = () => {
const dispatch = useDispatch<AppDispatch>();
const { teamId } = useGetTeamDetailsConstants();
const addMemberHandler = (emailIds: string): void => {
const endPoint = UPDATE_TEAM_DATA();
const finalSlackData = emailIds.includes(',')
? emailIds.split(',').map(item => item?.trim())
: [emailIds];
console.log(teamId, emailIds, finalSlackData);
ApiService.post(endPoint, {
id: teamId,
workEmailIds: finalSlackData,
})
.then(response => {
toast.success(response?.data?.data);
dispatch(fetchTeamDetails(teamId?.toString() || ''));
dispatch(fetchTeams());
})
.catch(error => {
const toastMessage = `${
error?.response?.data?.error?.message
? `${error?.response?.data?.error?.message}`
: 'Something went wrong, please try again later'
}`;
toast.error(toastMessage);
});
};
const removeTeamMember = (memberId: string): void => {
const endpoint = REMOVE_TEAM_MEMBER(teamId?.toString() || '', memberId);
ApiService.delete(endpoint)
.then(response => {
if (response.status === 200) {
toast(response.data.data, {
icon: <AlertIcon />,
});
dispatch(fetchTeamDetails(teamId?.toString() || ''));
dispatch(fetchTeams());
} else {
toast.error(response.data.error.message);
}
})
.catch(error => {
toast.error(`Error removing member from team : ${error.message}`);
});
};
const makeManager = (memberId: string): void => {
const endpoint = MAKE_MANAGER(teamId?.toString() || '', memberId);
ApiService.patch(endpoint, {})
.then(response => {
if (response.status === 200) {
toast.success(response.data.data);
dispatch(fetchTeamDetails(teamId?.toString() || ''));
} else {
toast.error(response.data.error.message);
}
})
.catch(error => {
toast.error(`Error in making manager of team: ${error.message}`);
});
};
return {
addMemberHandler,
removeTeamMember,
makeManager,
};
};
export default useTeamApis;

View File

@@ -0,0 +1,55 @@
import { useSelector } from 'react-redux';
import { TeamState, selectSearchTeamData } from '@src/slices/team1Slice';
export const useTeamDetails = () => {
return useSelector(selectSearchTeamData);
};
export const useGetTeamDetailsConstants = () => {
const userData = JSON.parse(localStorage.getItem('user-data') || '{}');
const teamData = useTeamDetails();
const teamDetails = teamData.teamDetails;
const teamId = teamDetails?.id;
const userEmail = userData?.emailId || '';
const managerEmail = teamDetails?.participants?.find(
item => item.id === teamDetails?.managerId,
)?.email;
const manager = teamDetails?.participants?.find(
item => item.id === teamDetails?.managerId,
);
const teamMembers =
teamDetails?.participants?.map(item => ({
name: item.name,
email: item.email,
id: item.id,
})) || [];
const DEFAULT_TEAM_ONCALL = teamDetails?.oncall?.name
? `@${teamDetails?.oncall?.name}`
: '';
const DEFAULT_TEAM_ONCALL_ID = teamDetails?.oncall?.id || '';
const DEFAULT_TEAM_PSEC_ID = teamDetails?.pse_oncall?.id || '';
const DEFAULT_PSEC_ONCALL =
teamDetails?.pse_oncall && teamDetails?.pse_oncall?.name != ''
? '@' + teamDetails?.pse_oncall?.name
: '';
const webChannelId =
teamDetails?.webhookSlackChannelName !== 'not found'
? `@${teamDetails?.webhookSlackChannelName}`
: teamDetails?.webhookSlackChannelId
? teamDetails?.webhookSlackChannelId
: '';
return {
teamId,
userEmail,
managerEmail,
manager,
teamMembers,
teamDetails,
teamData,
DEFAULT_TEAM_ONCALL,
DEFAULT_TEAM_ONCALL_ID,
DEFAULT_TEAM_PSEC_ID,
DEFAULT_PSEC_ONCALL,
webChannelId,
};
};
export default useGetTeamDetailsConstants;

View File

@@ -5,7 +5,7 @@ import {
FETCH_TEAM_DATA,
TeamsData,
TeamsDetail,
} from '@src/Pages/TeamRevamp/partials/constants';
} from '@src/Pages/TeamRevamp/constants';
import { ApiService } from '@src/services/api';
import { RootState } from '@src/store';