TP-51166 | resolving PR reviews

This commit is contained in:
pooja-jaiswal_navi
2024-01-12 00:51:51 +05:30
parent abb30e3edd
commit 3bd7191d66
8 changed files with 319 additions and 174 deletions

View File

@@ -16,9 +16,6 @@
display: flex;
margin-top: 10px;
}
.team-oncall {
margin-right: 24px;
}
.team-name {
margin: 24px 24px 24px 0px;
color: var(--navi-color-gray-c1);
@@ -33,6 +30,7 @@
}
.on-call-handler {
position: relative;
margin-right: 24px;
}
.search-input {
min-width: 320px !important;
@@ -50,15 +48,16 @@
margin-right: 4px;
}
.update-details {
margin-left: 900px;
margin-left: calc(100% - 120px);
margin-bottom: 31px;
margin-top: 12px;
}
.fallback-component {
height: 100px;
margin-left: 450px;
}
.update-details-disabled {
margin-left: 900px;
margin-left: calc(100% - 120px);
opacity: 0;
pointer-events: none;
}

View File

@@ -1,30 +1,41 @@
import { useDispatch, useSelector } from 'react-redux';
import { useEffect, useState, useRef, MutableRefObject } from 'react';
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 { UPDATE_TEAM_DATA } from '../partials/constants';
import { ApiService } from '@src/services/api';
import { toast } from '@navi/web-ui/lib/primitives/Toast';
import { AppDispatch } from '@src/store';
import { getBots } from '../partials/Bots';
import { TeamState } from '@src/slices/team1Slice';
import TeamMemberDetails from '../partials/TeamMemberDetails';
import useOutsideClick from '@src/services/hooks/useOustideClick';
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';
const TeamDetails = () => {
const dispatch = useDispatch<AppDispatch>();
const [state, dispatchData] = useReducer(reducer, initialState);
const teamData: TeamState = useSelector(selectSearchTeamData);
const { teamDetails } = teamData;
const teamId = teamDetails?.id;
@@ -32,25 +43,17 @@ const TeamDetails = () => {
const Role = useAuthData();
const userData = JSON.parse(localStorage.getItem('user-data') || '{}');
const userEmail = userData?.emailId || '';
const [slackChannelId, setSlackChannelId] = useState('');
const [oncall, setOncall] = useState({
label: '',
value: '',
});
const [psecOncall, setPsecOncallValue] = useState({
label: '',
value: '',
});
const [input, setInput] = useState('');
const [psecInput, setPsecInput] = useState('');
const [openOnCall, setOpenOnCall] = useState(false);
const [openPsecOnCall, setOpenPsecOnCall] = useState(false);
const searchRef = useRef<HTMLInputElement>(null);
useEffect(() => {
setInput('');
setPsecInput('');
dispatchData({
type: actionTypes.SET_INPUT,
payload: '',
});
dispatchData({
type: actionTypes.SET_PSEC_INPUT,
payload: '',
});
}, [teamData]);
const DEFAULT_TEAM_ONCALL = teamDetails?.oncall?.name
@@ -68,30 +71,59 @@ const TeamDetails = () => {
: teamDetails?.webhookSlackChannelId
? teamDetails?.webhookSlackChannelId
: '';
const handleslackChannelId = (e: React.ChangeEvent<HTMLInputElement>) => {
setSlackChannelId(e.target.value);
};
const handleOncallChange = (event: SelectPickerOptionProps) => {
setOncall({
label: event.label,
value: event.value.toString(),
const handleslackChannelId = (
e: React.ChangeEvent<HTMLInputElement>,
): void => {
dispatchData({
type: actionTypes.SET_SLACK_CHANNEL_ID,
payload: e.target.value,
});
setInput(event?.label);
setOpenOnCall(false);
};
const handlePsecOncall = (event: SelectPickerOptionProps) => {
setPsecOncallValue({
label: event.label,
value: event.value.toString(),
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,
});
setPsecInput(event?.label);
setOpenPsecOnCall(false);
};
const handleOncallOutsideClick = (): void => {
setOpenOnCall(false);
dispatchData({
type: actionTypes.SET_OPEN_ONCALL,
payload: false,
});
};
const handlePsecOncallOutsideClick = (): void => {
setOpenPsecOnCall(false);
dispatchData({
type: actionTypes.SET_PSEC_OPEN_ONCALL,
payload: false,
});
};
const refOncall = useOutsideClick({
callback: handleOncallOutsideClick,
@@ -103,25 +135,51 @@ const TeamDetails = () => {
const handleOncallPicker = (): void => {
if (!isUserParticipant(teamDetails) && !Role.includes('Admin')) return;
setOpenOnCall(!openOnCall);
dispatchData({
type: actionTypes.SET_OPEN_ONCALL,
payload: !state.openOnCall,
});
};
const handleOpenPsecOnCallPicker = (): void => {
if (!isUserParticipant(teamDetails) && !Role.includes('Admin')) return;
setOpenPsecOnCall(!openPsecOnCall);
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: slackChannelId,
on_call_handle: oncall.value,
pse_on_call_id: psecOncall.value,
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() || ''));
setOpenOnCall(false);
setOpenPsecOnCall(false);
dispatchData({
type: actionTypes.SET_OPEN_ONCALL,
payload: false,
});
dispatchData({
type: actionTypes.SET_PSEC_OPEN_ONCALL,
payload: false,
});
})
.catch(error => {
const toastMessage = `${
@@ -186,85 +244,85 @@ const TeamDetails = () => {
/>
</div>
<div className={styles['on-call-wrapper']}>
<div className={styles['team-oncall']}>
<div ref={refOncall}>
<Typography variant="h6">Team on call</Typography>{' '}
<div ref={refOncall}>
<div
onClick={handleOncallPicker}
className={styles['on-call-handler']}
>
<BorderedInput
onChange={e => {
setInput(e.target.value);
}}
placeholder="@oncall_handle"
containerClassName={styles['search-input']}
value={input || DEFAULT_TEAM_ONCALL}
disabled={
!isUserParticipant(teamDetails) && !Role.includes('Admin')
}
></BorderedInput>
</div>
{openOnCall && (
<SelectPicker
options={getBots()}
onSelectionChange={handleOncallChange as any}
selectedValue={oncall.value || DEFAULT_TEAM_ONCALL_ID}
multiSelect={false}
updateSearch={input || searchRef.current ? input : ''}
wrapperClasses={styles['select-picker-wrapper']}
></SelectPicker>
)}
<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 className={styles['psec-oncall']}>
<div ref={refPsecOncall}>
<Typography variant="h6">PSEC on call</Typography>
<div
onClick={handleOpenPsecOnCallPicker}
className={styles['on-call-handler']}
<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
}
>
<BorderedInput
onChange={e => setPsecInput(e.target.value)}
placeholder="@psec_oncall_handle"
containerClassName={styles['search-input']}
value={psecInput || DEFAULT_PSEC_ONCALL}
disabled={
!isUserParticipant(teamDetails) && !Role.includes('Admin')
}
></BorderedInput>
</div>
{openPsecOnCall && (
<SelectPicker
options={getBots()}
onSelectionChange={handlePsecOncall as any}
multiSelect={false}
selectedValue={psecOncall.value || DEFAULT_TEAM_PSEC_ID}
updateSearch={psecInput || searchRef.current ? psecInput : ''}
wrapperClasses={styles['select-picker-wrapper']}
></SelectPicker>
)}
Update details
</Button>
</div>
</div>
</div>
</div>
<div
className={
managerEmail === userEmail ||
isUserParticipant(teamDetails) ||
Role.includes('Admin')
? styles['update-details']
: styles['update-details-disabled']
}
>
<Button
variant="text"
onClick={submitHandler}
disabled={!slackChannelId && !psecInput && !input}
>
Update details
</Button>
</div>
<TeamMemberDetails />
</div>
);

View File

@@ -11,7 +11,12 @@ import CreateTeam from '@src/Pages/TeamRevamp/partials/CreateTeam/CreateTeam';
import { fetchTeamDetails, setModalOpen } from '@src/slices/team1Slice';
import styles from './TeamList.module.scss';
const CutomeTeamOptions = option => {
type Options = {
label: string;
value: number;
count: number;
};
const CustomTeamOptions = (option: Options): JSX.Element => {
return (
<div className={styles['custom-options']} id={option.value?.toString()}>
<section>{option.label}</section>
@@ -138,7 +143,7 @@ const TeamList = () => {
multiSelect={false}
updateSearch={input || ''}
wrapperClasses={styles['select-picker-wrapper']}
customOptionTemplate={CutomeTeamOptions}
customOptionTemplate={CustomTeamOptions as any}
selectedValue={team?.value ?? defaultTeam?.id ?? ''}
/>
</div>

View File

@@ -91,13 +91,12 @@
.horizontal-line {
display: flex;
}
.hr {
width: calc(90vh - 10px);
.hr-tag {
width: 100%;
border: 1px (--navi-color-gray-border);
margin-top: 12px;
margin-left: 5px;
}
.fallback-component {
height: 100px;
margin-left: calc(100vh - 24px);
height: 200px;
}

View File

@@ -47,7 +47,7 @@ const TeamMemberDetails = () => {
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(
@@ -74,7 +74,13 @@ const TeamMemberDetails = () => {
payload: e.target.value,
});
};
if (loading) {
return (
<div className={styles['fallback-component']}>
<FallbackComponent />
</div>
);
}
if (isPending) {
return (
<div className={styles['fallback-component']}>
@@ -92,6 +98,7 @@ const TeamMemberDetails = () => {
});
};
const addMemberHandler = (): void => {
setLoading(true);
const endPoint = UPDATE_TEAM_DATA();
const finalSlackData = state.emailIds?.includes(',')
? state.emailIds.split(',').map(item => item?.trim())
@@ -102,6 +109,7 @@ const TeamMemberDetails = () => {
})
.then(response => {
toast.success(response?.data?.data);
setLoading(false);
dispatch(fetchTeamDetails(teamId?.toString() || ''));
dispatch(fetchTeams());
})
@@ -114,12 +122,33 @@ const TeamMemberDetails = () => {
toast.error(toastMessage);
});
};
const handleMakeManager = (memberID: string) => {
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 {
@@ -163,24 +192,7 @@ const TeamMemberDetails = () => {
payload: false,
});
};
const removeMemberHandler = (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 handleHoverchange = (item: MemberType) => {
if (userEmail === managerEmail || Role.includes('Admin')) {
dispatchData({
@@ -208,7 +220,7 @@ const TeamMemberDetails = () => {
<div className={styles['add-member']}>
<div className={styles['horizontal-line']}>
<Typography variant="h5">MEMBERS</Typography>
<hr className={styles['hr']}></hr>{' '}
<hr className={styles['hr-tag']}></hr>{' '}
</div>
<BorderedInput

View File

@@ -89,8 +89,39 @@ export const actionTypes = {
SET_EMAIL_IDS: 'SET_EMAIL_IDS',
SET_SELECTED_MEMBER: 'SET_SELECTED_MEMBER',
SET_HOVERED: 'SET_HOVERED',
SET_SLACK_CHANNEL_ID: 'SET_SLACK_CHANNEL_ID',
SET_ONCALL: 'SET_ONCALL',
SET_PSEC_ONCALL: 'SET_PSEC_ONCALL',
SET_INPUT: 'SET_INPUT',
SET_PSEC_INPUT: 'SET_PSEC_INPUT',
SET_OPEN_ONCALL: 'SET_OPEN_ONCALL',
SET_PSEC_OPEN_ONCALL: 'SET_PSEC_OPEN_ONCALL',
};
export const initialState = {
interface AppState {
openDialog: boolean;
openModal: boolean;
emailIds: string;
selectedMember?: Participant;
hovered: {
ishovered: boolean;
id: string;
};
slackChannelId: string;
oncall: {
label: string;
value: string;
};
psecOncall: {
label: string;
value: string;
};
input: string;
psecInput: string;
openOnCall: boolean;
openPsecOnCall: boolean;
}
export const initialState: AppState = {
openDialog: false,
openModal: false,
emailIds: '',
@@ -99,9 +130,25 @@ export const initialState = {
ishovered: false,
id: '',
},
slackChannelId: '',
oncall: {
label: '',
value: '',
},
psecOncall: {
label: '',
value: '',
},
input: '',
psecInput: '',
openOnCall: false,
openPsecOnCall: false,
};
export const reducer = (state, action) => {
interface ActionType {
type: string;
payload: any;
}
export const reducer = (state: AppState, action: ActionType): AppState => {
switch (action.type) {
case actionTypes.SET_OPEN_DIALOG:
return {
@@ -126,8 +173,45 @@ export const reducer = (state, action) => {
case actionTypes.SET_HOVERED:
return {
...state,
hovered: action.payload,
id: action.payload.id,
hovered: {
...state.hovered,
...action.payload,
},
};
case actionTypes.SET_SLACK_CHANNEL_ID:
return {
...state,
slackChannelId: action.payload,
};
case actionTypes.SET_ONCALL:
return {
...state,
oncall: action.payload,
};
case actionTypes.SET_PSEC_ONCALL:
return {
...state,
psecOncall: action.payload,
};
case actionTypes.SET_INPUT:
return {
...state,
input: action.payload,
};
case actionTypes.SET_PSEC_INPUT:
return {
...state,
psecInput: action.payload,
};
case actionTypes.SET_OPEN_ONCALL:
return {
...state,
openOnCall: action.payload,
};
case actionTypes.SET_PSEC_OPEN_ONCALL:
return {
...state,
openPsecOnCall: action.payload,
};
default:
return state;

View File

@@ -55,13 +55,6 @@ const LeftNav: React.FC<LeftNavProps> = ({ children }) => {
Icon: AlertOutlineIcon,
handleNavigation: () => navigate('/severity'),
},
{
itemType: 'simpleNavItem',
label: 'Team',
route: '/team',
Icon: LeadIcon,
handleNavigation: () => navigate('/team'),
},
{
itemType: 'simpleNavItem',
label: 'Houston metrics',
@@ -71,10 +64,10 @@ const LeftNav: React.FC<LeftNavProps> = ({ children }) => {
},
{
itemType: 'simpleNavItem',
label: 'TEAM1',
route: '/Team-Revamp',
label: 'Team',
route: '/Teams',
Icon: TeamIcon,
handleNavigation: () => navigate('/Team-Revamp'),
handleNavigation: () => navigate('/Teams'),
},
{
itemType: 'simpleNavItem',

View File

@@ -20,11 +20,6 @@ const routes: CustomRouteObject[] = [
path: '/incident/:incidentId',
element: <Incident />,
},
{
id: 'TEAM',
path: '/team',
element: <Team />,
},
{
id: 'SEVERITY',
path: '/severity',
@@ -37,7 +32,7 @@ const routes: CustomRouteObject[] = [
},
{
id: 'TEAM',
path: '/Team-Revamp',
path: '/Teams',
element: <TeamRevamp />,
},
{