TP-51166 | fixing design

This commit is contained in:
pooja-jaiswal_navi
2024-01-08 15:04:12 +05:30
parent 8f4bddab12
commit 8543bf457d
8 changed files with 193 additions and 197 deletions

View File

@@ -1,14 +1,12 @@
.team-details-wrapper {
margin-left: 79px;
margin-top: 20px;
margin: 20px 0px 0px 79px;
}
.team-input-wrapper {
min-width: (320px);
height: (38px);
}
.add-member {
margin-top: 72px;
margin-bottom: 24px;
margin: 72px 0px 24px 0px;
}
.member-div {
margin: 24px 24px 24px 0px;
@@ -45,7 +43,6 @@
width: 20px;
margin-left: 4px;
margin-top: 5px;
display: flex;
align-items: center;
:hover {
cursor: pointer;

View File

@@ -1,6 +1,5 @@
import { useDispatch, useSelector } from 'react-redux';
import { fetchTeamDetails, selectSearchTeamData } from '@src/slices/team1Slice';
import PersonIcon from '@src/assets/PersonIcon';
import { useEffect, useState, useRef, MutableRefObject } from 'react';
import {
BorderedInput,
Button,
@@ -8,22 +7,21 @@ import {
Typography,
} from '@navi/web-ui/lib/primitives';
import { useEffect, useState, useRef, MutableRefObject } from 'react';
import { SelectPicker } from '@navi/web-ui/lib/components';
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 styles from './TeamDetails.module.scss';
import { SelectPicker } from '@navi/web-ui/lib/components';
import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types';
import { AlertOutlineIcon } from '@navi/web-ui/lib/icons';
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 styles from './TeamDetails.module.scss';
const TeamDetails = () => {
const dispatch = useDispatch<AppDispatch>();
@@ -49,6 +47,11 @@ const TeamDetails = () => {
const [openOnCall, setOpenOnCall] = useState(false);
const [openPsecOnCall, setOpenPsecOnCall] = useState(false);
const searchRef = useRef<HTMLInputElement>(null);
useEffect(() => {
setInput('');
setPsecInput('');
}, [teamData]);
const DEFAULT_TEAM_ONCALL = teamDetails?.oncall?.name
? `@${teamDetails?.oncall?.name}`
: '';
@@ -58,12 +61,12 @@ const TeamDetails = () => {
teamDetails?.pse_oncall && teamDetails?.pse_oncall?.name != ''
? '@' + teamDetails?.pse_oncall?.name
: '';
useEffect(() => {
setInput('');
setPsecInput('');
}, [teamData]);
const webChannelId =
teamDetails?.webhookSlackChannelName !== 'not found'
? `@${teamDetails?.webhookSlackChannelName}`
: teamDetails?.webhookSlackChannelId
? teamDetails?.webhookSlackChannelId
: '';
const handleslackChannelId = (e: React.ChangeEvent<HTMLInputElement>) => {
setSlackChannelId(e.target.value);
};
@@ -135,7 +138,6 @@ const TeamDetails = () => {
</div>
);
}
// const Role = useAuthData();
const managerEmail = teamDetails?.participants?.find(
participant => participant.id === teamDetails?.managerId,
)?.email;
@@ -148,114 +150,102 @@ const TeamDetails = () => {
return (
<div className={styles['team-details-wrapper']}>
<div className={styles['team-name']}>
<Typography variant="h1" className={styles['team-name']}>
{teamDetails?.name}
<Typography variant="h1" className={styles['team-name']}>
{teamDetails?.name}
</Typography>{' '}
<div className={styles['slack-details']}>
<Typography variant="h5" className={styles['member-div']}>
SLACK DETAILS
</Typography>{' '}
</div>
<div>
<div className={styles['slack-details']}>
<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['team-name-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
<div className={styles['slack-details-wrapper']}>
<div className={styles['team-channel-wrapper']}>
<div className={styles['team-name-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>
withPointer
position={'right'}
>
<AlertOutlineIcon />
</Tooltip>
</div>
<BorderedInput
inputLabel=""
inputSize="medium"
placeholder="Enter channel I.D"
onChange={handleslackChannelId}
containerClassName={styles['team-input-wrapper']}
value={
teamDetails?.webhookSlackChannelName !== 'not found'
? `@${teamDetails?.webhookSlackChannelName}`
: ''
}
// value={'#' + teamDetails?.webhookSlackChannelName}
hintMsg="Channel name populates after entering id"
error={teamDetails?.webhookSlackChannelName === ''}
disabled={
!isUserParticipant(teamDetails) && !Role.includes('Admin')
}
/>
</div>
<div className={styles['on-call-wrapper']}>
<div className={styles['team-oncall']}>
<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>
)}
<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 className={styles['team-oncall']}>
<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>
<div className={styles['psec-oncall']}>
<div ref={refPsecOncall}>
<Typography variant="h6">PSEC on call</Typography>
<div
onClick={handleOpenPsecOnCallPicker}
className={styles['on-call-handler']}
>
<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>
)}
</div>
<div className={styles['psec-oncall']}>
<div ref={refPsecOncall}>
<Typography variant="h6">PSEC on call</Typography>
<div
onClick={handleOpenPsecOnCallPicker}
className={styles['on-call-handler']}
>
<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>
)}
</div>
</div>
</div>

View File

@@ -10,6 +10,7 @@ import { useAuthData } from '@src/services/hooks/useAuth';
import CreateTeam from '@src/Pages/TeamRevamp/partials/CreateTeam/CreateTeam';
import { fetchTeamDetails, setModalOpen } from '@src/slices/team1Slice';
import styles from './TeamList.module.scss';
import useIsFirstRender from '@src/services/hooks/isFirstRender';
const CutomeTeamOptions = option => {
return (
@@ -27,15 +28,22 @@ const CutomeTeamOptions = option => {
const TeamList = () => {
const dispatch = useDispatch<AppDispatch>();
const Role = useAuthData();
const teamList = useSelector((state: RootState) => state.team1.teams.data);
const defaultTeam = useSelector(
(state: RootState) => state.team1.teams.data[0],
);
const newTeam = useSelector((state: RootState) => state.team1.teams.newTeam);
const Role = useAuthData();
const [input, setInput] = useState('');
const [team, setTeam] = useState<SelectPickerOptionProps>();
const scroll = useRef<boolean>(false);
console.log('team', team);
// useEffect(() => {
// console.log('defaultTeam', defaultTeam);
// if () {
// setTeam({ label: defaultTeam?.name, value: defaultTeam?.id });
// }
// }, [defaultTeam]);
const scrollintoView = (event: string): void => {
if (!event) return;
@@ -82,7 +90,9 @@ const TeamList = () => {
}
}, [teamList]);
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const handleInputChange = (
event: React.ChangeEvent<HTMLInputElement>,
): void => {
setInput(event.target.value);
};
const createHandler = () => {
@@ -92,12 +102,22 @@ const TeamList = () => {
dispatch(fetchTeamDetails(val));
};
const handleSelectionChange = (event: SelectPickerOptionProps) => {
const handleSelectionChange = (event): void => {
setTeam(event);
fetchDetails(event.value.toString());
scroll.current = false;
};
function getSelectedValue(team, defaultTeam) {
if (team && team.value) {
return team.value;
} else if (defaultTeam && defaultTeam.id) {
return defaultTeam.id;
} else {
return '';
}
}
const selectedTeam = getSelectedValue(team, defaultTeam);
console.log('selectedTeam', selectedTeam);
return (
<div>
<div className={styles['team-div']}>
@@ -133,12 +153,12 @@ const TeamList = () => {
<SelectPicker
options={options}
onSelectionChange={handleSelectionChange as any}
multiSelect={false}
onSelectionChange={handleSelectionChange}
multiSelect
updateSearch={input || ''}
wrapperClasses={styles['select-picker-wrapper']}
customOptionTemplate={CutomeTeamOptions}
selectedValue={team?.value ?? defaultTeam?.id ?? ''}
selectedValue={selectedTeam}
/>
</div>
<CreateTeam setTeam={setTeam} />

View File

@@ -19,7 +19,10 @@ const TeamRevamp: FC = () => {
const dispatch = useDispatch<AppDispatch>();
const teamData: TeamState = useSelector(selectSearchTeamData);
const { data } = teamData;
const { teamDetails } = teamData;
const teamId = teamDetails?.id || '';
const { selectedTeam } = teamData;
const selectedTeamId = selectedTeam?.id || '';
useEffect(() => {
dispatch(fetchTeams());
}, []);
@@ -32,6 +35,11 @@ const TeamRevamp: FC = () => {
dispatch(setSelectedTeam(data[0]));
}
}, []);
useEffect(() => {
if (selectedTeam) {
dispatch(fetchTeamDetails(selectedTeamId.toString()));
}
}, [selectedTeam]);
return (
<ErrorBoundary>
<div className={styles['team-wrapper']}>

View File

@@ -7,14 +7,11 @@ import { toast } from '@navi/web-ui/lib/primitives/Toast';
import { AppDispatch, RootState } from '@src/store';
import { ApiService } from '@src/services/api';
import {
TeamState,
fetchTeamDetails,
fetchTeams,
selectSearchTeamData,
setModalOpen,
setNewTeam,
setSelectedTeam,
setTeamData,
} from '@src/slices/team1Slice';
import { CREATE_TEAM } from '@src/Pages/TeamRevamp/partials/constants';
@@ -33,45 +30,40 @@ interface CreateTeamProps {
const CreateTeam: React.FC<CreateTeamProps> = ({ setTeam }) => {
const dispatch = useDispatch<AppDispatch>();
const teamData: TeamState = useSelector(selectSearchTeamData);
const { modalOpen } = useSelector(selectSearchTeamData);
const [teamName, setTeamName] = useState<string>('');
const [teamNameError, setTeamNameError] = useState<string>('');
const [emailError, setEmailError] = useState<string>('');
const [email, setEmail] = useState<string>('');
// const [createdTeam, setCreatedTeam] = useState<string>('');
const [teamName, setTeamName] = useState('');
const [teamNameError, setTeamNameError] = useState('');
const [emailError, setEmailError] = useState('');
const [email, setEmail] = useState('');
const newTeam = useSelector((state: RootState) => state.team1.teams.newTeam);
const validateTeamName = (value: string): void => {
value = value.trim();
if (!regularExpression.test(value)) {
setTeamNameError(
'Min. 3 characters required. Use spaces, _, or - only',
);
} else {
setTeamNameError('');
}
setTeamNameError(
!regularExpression.test(value)
? 'Min. 3 characters required. Use spaces, _, or - only'
: '',
);
};
const validateEmail = (value: string): void => {
const validEmail = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (validEmail.test(value)) {
if (!emailRegularExpression.test(value)) {
setEmailError('Please enter a Navi email ID');
} else {
setEmailError('');
}
}
setEmailError(
validEmail.test(value)
? !emailRegularExpression.test(value)
? 'Please enter a Navi email ID'
: ''
: '',
);
};
const selectedTeam = useSelector(
(state: RootState) => state.team1.teams.selectedTeam,
);
const handleTeamNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const handleTeamNameChange = (
e: React.ChangeEvent<HTMLInputElement>,
): void => {
const inputValue = e.target.value;
setTeamName(inputValue);
validateTeamName(inputValue);
};
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
const inputValue = e.target.value;
setEmail(inputValue);
validateEmail(inputValue);
@@ -79,16 +71,14 @@ const CreateTeam: React.FC<CreateTeamProps> = ({ setTeam }) => {
const addTeamHandler = (): void => {
ApiService.post(CREATE_TEAM, { name: teamName, manager_email: email })
.then((response: any) => {
.then(response => {
const toastMessage = `${response?.data?.data?.message}`;
const createdTeamId = response?.data?.data?.id;
const teamName = response?.data?.data?.name;
const createdTeamName = response?.data?.data?.name;
toast.success(toastMessage);
clearErrors();
dispatch(fetchTeamDetails(createdTeamId.toString()));
setTeam({ label: teamName, value: createdTeamId });
dispatch(setSelectedTeam(createdTeamId));
// setCreatedTeam(createdTeamId);
setTeam({ label: createdTeamName, value: createdTeamId });
dispatch(setNewTeam(createdTeamId));
dispatch(fetchTeams());
})
@@ -102,13 +92,7 @@ const CreateTeam: React.FC<CreateTeamProps> = ({ setTeam }) => {
});
};
useEffect(() => {
if (selectedTeam) {
dispatch(fetchTeamDetails(selectedTeam?.id));
}
}, [selectedTeam]);
const clearErrors = () => {
const clearErrors = (): void => {
dispatch(setModalOpen(false));
setTeamNameError('');
setEmailError('');

View File

@@ -41,12 +41,11 @@ type MemberType = {
const TeamMemberDetails = () => {
const teamData: TeamState = useSelector(selectSearchTeamData);
const dispatch = useDispatch<AppDispatch>();
const { teamDetails } = teamData;
const { isPending } = teamData;
const teamId = teamDetails?.id?.toString() || '';
const id = teamDetails?.id;
const userData = JSON.parse(localStorage.getItem('user-data') || '{}');
const Role = useAuthData();
const { teamDetails } = teamData;
const { isPending } = teamData;
const teamId = teamDetails?.id;
const userEmail = userData?.emailId || '';
const managerEmail = teamDetails?.participants?.find(
(item: any) => item.id === teamDetails?.managerId,
@@ -93,12 +92,12 @@ const TeamMemberDetails = () => {
? emailIds.split(',').map(item => item?.trim())
: [emailIds];
ApiService.post(endPoint, {
id: id,
id: teamId,
workEmailIds: finalSlackData,
})
.then(response => {
toast.success(response?.data?.data);
dispatch(fetchTeamDetails(teamId));
dispatch(fetchTeamDetails(teamId?.toString() || ''));
dispatch(fetchTeams());
})
.catch(error => {
@@ -112,12 +111,12 @@ const TeamMemberDetails = () => {
};
const handleMakeManager = (memberID: string) => {
const endpoint = MAKE_MANAGER(teamId, memberID);
const endpoint = MAKE_MANAGER(teamId?.toString() || '', memberID);
ApiService.patch(endpoint, {})
.then(response => {
if (response.status === 200) {
toast.success(response.data.data);
dispatch(fetchTeamDetails(teamId));
dispatch(fetchTeamDetails(teamId?.toString() || ''));
} else {
toast.error(response.data.error.message);
}
@@ -142,14 +141,14 @@ const TeamMemberDetails = () => {
setOpenDialog(false);
};
const removeMemberHandler = (memberID: string): void => {
const endpoint = REMOVE_TEAM_MEMBER(teamId, memberID);
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));
dispatch(fetchTeamDetails(teamId?.toString() || ''));
dispatch(fetchTeams());
} else {
toast.error(response.data.error.message);

View File

@@ -0,0 +1,10 @@
import { useRef } from 'react';
const useIsFirstRender = (): boolean => {
const isFirst = useRef(true);
if (isFirst.current) {
isFirst.current = false;
return true;
}
return isFirst.current;
};
export default useIsFirstRender;

View File

@@ -1,5 +1,5 @@
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import TeamDetails from '@src/Pages/TeamRevamp/TeamDetails';
import {
FETCH_SINGLE_TEAM_DATA,
FETCH_TEAM_DATA,
@@ -15,7 +15,7 @@ export interface TeamState {
modalOpen: boolean;
teamDetails: TeamsDetail;
error: string;
selectedTeam: number | string | any;
selectedTeam: TeamsDetail;
newTeam: number | string;
}
@@ -30,7 +30,7 @@ const initialState: TeamInitialState = {
isPending: false,
modalOpen: false,
teamDetails: {},
selectedTeam: [],
selectedTeam: {},
newTeam: '',
},
};
@@ -47,13 +47,6 @@ export const fetchTeamDetails = createAsyncThunk(
return await ApiService.get(endPoint);
},
);
// export const fetchDefaultTeamDetails = createAsyncThunk(
// 'team/fetchDefaultTeamDetails',
// async (id: string) => {
// const endPoint = FETCH_SINGLE_TEAM_DATA(id);
// return await ApiService.get(endPoint);
// },
// );
const team1Slice = createSlice({
name: 'team1',
@@ -89,13 +82,13 @@ const team1Slice = createSlice({
state.teams.error = action.error.message ?? 'Something went wrong';
state.teams.data = {};
state.teams.isPending = false;
state.teams.selectedTeam = [];
state.teams.selectedTeam = {};
});
builder.addCase(fetchTeams.pending, (state, action) => {
state.teams.isPending = true;
state.teams.data = {};
state.teams.error = '';
state.teams.selectedTeam = [];
state.teams.selectedTeam = {};
});
builder.addCase(fetchTeamDetails.fulfilled, (state, action) => {
state.teams.teamDetails = action.payload.data.data;
@@ -112,11 +105,6 @@ const team1Slice = createSlice({
state.teams.teamDetails = {};
state.teams.error = '';
});
// builder.addCase(fetchDefaultTeamDetails.fulfilled, (state, action) => {
// state.teams.teamDetails = action.payload.data.data;
// state.teams.isPending = false;
// state.teams.error = '';
// });
},
});