Merge pull request #109 from navi-sa/TP-51166
TP-51166 | Team-Page revamp
This commit is contained in:
@@ -12,7 +12,6 @@
|
||||
"build": "yarn run clear-build && tsc && vite build && yarn run copy-config",
|
||||
"lint": "eslint --fix \"**/?*.{ts,tsx,js,jsx}\"",
|
||||
"eslint-check": "eslint \"**/?*.{ts,tsx,js,jsx}\"",
|
||||
"prepare": "husky install",
|
||||
"pretty": "prettier --write \"./**/*.{js,jsx,ts,tsx,css,scss,md}\"",
|
||||
"prettier-check": "prettier --check \"./**/*.{js,jsx,ts,tsx,css,scss,md}\"",
|
||||
"eslint-check-quiet": "yarn eslint-check --quiet"
|
||||
@@ -33,7 +32,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@navi/dark-knight": "^1.0.13",
|
||||
"@navi/web-ui": "^1.59.2",
|
||||
"@navi/web-ui": "^1.59.4",
|
||||
"@reduxjs/toolkit": "^1.9.7",
|
||||
"@stoddabr/react-tableau-embed-live": "^0.3.26",
|
||||
"antd": "^5.9.4",
|
||||
|
||||
@@ -184,6 +184,9 @@
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.list-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
.hint-text {
|
||||
margin-top: 24px;
|
||||
font-size: 12px;
|
||||
|
||||
7
src/Pages/TeamRevamp/Team.module.scss
Normal file
7
src/Pages/TeamRevamp/Team.module.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
.team-wrapper {
|
||||
margin: 24px;
|
||||
}
|
||||
|
||||
.wrapper-class {
|
||||
display: flex;
|
||||
}
|
||||
7
src/Pages/TeamRevamp/TeamDetails/TeamDetails.module.scss
Normal file
7
src/Pages/TeamRevamp/TeamDetails/TeamDetails.module.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
.team-details-wrapper {
|
||||
margin: 20px 0px 0px 79px;
|
||||
}
|
||||
.fallback-component {
|
||||
height: 100px;
|
||||
margin-left: 450px;
|
||||
}
|
||||
26
src/Pages/TeamRevamp/TeamDetails/index.tsx
Normal file
26
src/Pages/TeamRevamp/TeamDetails/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { selectSearchTeamData } from '@src/slices/team1Slice';
|
||||
import FallbackComponent from '@src/components/Fallback';
|
||||
import TeamMemberDetails from '../partials/TeamMemberDetails';
|
||||
import SlackDetails from '../partials/SlackDetails';
|
||||
import styles from './TeamDetails.module.scss';
|
||||
|
||||
const TeamDetails: React.FC = () => {
|
||||
const { isPending } = useSelector(selectSearchTeamData);
|
||||
|
||||
if (isPending) {
|
||||
return (
|
||||
<div className={styles['fallback-component']}>
|
||||
<FallbackComponent />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={styles['team-details-wrapper']}>
|
||||
<SlackDetails />
|
||||
<TeamMemberDetails />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamDetails;
|
||||
26
src/Pages/TeamRevamp/TeamList/SearchInput.tsx
Normal file
26
src/Pages/TeamRevamp/TeamList/SearchInput.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { BorderedInput } from '@navi/web-ui/lib/primitives';
|
||||
import { SearchIcon } from '@navi/web-ui/lib/icons';
|
||||
import { SearchInputComponentProps } from '../types';
|
||||
import styles from './TeamList.module.scss';
|
||||
|
||||
const SearchInput: React.FC<SearchInputComponentProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
isAdmin,
|
||||
}) => {
|
||||
const containerClass = isAdmin
|
||||
? styles['search-bar']
|
||||
: styles['search-input'];
|
||||
return (
|
||||
<BorderedInput
|
||||
LeftInputAdornment={<SearchIcon />}
|
||||
onChange={onChange}
|
||||
containerClassName={containerClass}
|
||||
value={value}
|
||||
placeholder="Search by team "
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchInput;
|
||||
66
src/Pages/TeamRevamp/TeamList/TeamList.module.scss
Normal file
66
src/Pages/TeamRevamp/TeamList/TeamList.module.scss
Normal file
@@ -0,0 +1,66 @@
|
||||
.team-div {
|
||||
margin-top: 36px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--navi-color-gray-border);
|
||||
background: var(--navi-color-gray-bg-primary);
|
||||
box-shadow: 0px 6px 10px 0px rgba(0, 0, 0, 0.08),
|
||||
0px 1px 18px 0px rgba(0, 0, 0, 0.06), 0px 3px 5px 0px rgba(0, 0, 0, 0.1);
|
||||
position: sticky;
|
||||
}
|
||||
.search-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
.create-team-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
padding: 8px;
|
||||
margin-top: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
@mixin search {
|
||||
height: 36px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
.search-bar {
|
||||
@include search;
|
||||
width: 234px;
|
||||
}
|
||||
.search-input {
|
||||
@include search;
|
||||
width: 272px;
|
||||
}
|
||||
.select-picker-wrapper {
|
||||
min-width: 300px;
|
||||
min-height: calc(100vh - 228px) !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
:hover {
|
||||
background-color: var(--navi-select-picker-bg-item-selected);
|
||||
}
|
||||
}
|
||||
|
||||
.select-picker-wrapper::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.custom-options {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: small;
|
||||
color: var(--navi-color-gray-c2);
|
||||
height: 29px;
|
||||
padding-top: 15px;
|
||||
}
|
||||
.search-results {
|
||||
margin-left: 15px;
|
||||
margin-top: 12px;
|
||||
color: var(--navi-color-gray-c3);
|
||||
}
|
||||
.add-icon {
|
||||
margin-left: 3px;
|
||||
}
|
||||
192
src/Pages/TeamRevamp/TeamList/index.tsx
Normal file
192
src/Pages/TeamRevamp/TeamList/index.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
import { useEffect, useState, useRef, useReducer } from 'react';
|
||||
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 { 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 { AppDispatch, RootState } from '@src/store';
|
||||
import { useAuthData } from '@src/services/hooks/useAuth';
|
||||
import CreateTeam from '@src/Pages/TeamRevamp/partials/CreateTeam/CreateTeam';
|
||||
import SearchInput from './SearchInput';
|
||||
import styles from './TeamList.module.scss';
|
||||
import SelectedPersonIcon from '@src/assets/SelectedPersonIcon';
|
||||
|
||||
type Options = {
|
||||
label: string;
|
||||
value: number;
|
||||
additionalData: {
|
||||
count: number;
|
||||
isSelected: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
const CustomTeamOptions = (option: Options): JSX.Element => {
|
||||
return (
|
||||
<div className={styles['custom-options']} id={option.value?.toString()}>
|
||||
<section>{option.label}</section>
|
||||
<section className={styles['icon-wrapper']}>
|
||||
<div>{option.additionalData.count}</div>
|
||||
<div>
|
||||
{option.additionalData.isSelected ? (
|
||||
<SelectedPersonIcon />
|
||||
) : (
|
||||
<PersonIcon />
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TeamList: React.FC = () => {
|
||||
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 [team, setTeam] = useState<SelectPickerOptionProps>();
|
||||
const newTeam = useSelector((state: RootState) => state.team1.teams.newTeam);
|
||||
const [input, setInput] = useState('');
|
||||
const scroll = useRef<boolean>(false);
|
||||
|
||||
const scrollintoView = (event: string): void => {
|
||||
if (!event) return;
|
||||
const element = document.getElementById(event);
|
||||
if (element) {
|
||||
setTimeout(() => {
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
const options = Array.isArray(teamList)
|
||||
? teamList.map(item => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
additionalData: {
|
||||
count: item.slackUserIds?.length,
|
||||
isSelected:
|
||||
item.id === team?.value ||
|
||||
(team?.value ? false : defaultTeam?.id === item.id),
|
||||
},
|
||||
}))
|
||||
: [];
|
||||
|
||||
useEffect(() => {
|
||||
if (!scroll.current && team?.value) {
|
||||
scroll.current = true;
|
||||
if (input.length) {
|
||||
setInput('');
|
||||
scrollintoView(team?.value?.toString() ?? '');
|
||||
}
|
||||
return;
|
||||
}
|
||||
}, [team?.value, options]);
|
||||
|
||||
useEffect(() => {
|
||||
scroll.current = false;
|
||||
if (!scroll.current && newTeam?.toString()?.length) {
|
||||
scroll.current = true;
|
||||
if (Array.isArray(teamList) && teamList.length) {
|
||||
teamList?.forEach(item => {
|
||||
if (item.id === newTeam) {
|
||||
setTimeout(() => {
|
||||
scrollintoView(newTeam?.toString() ?? '');
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}, [teamList]);
|
||||
|
||||
const handleInputChange = (
|
||||
event: React.ChangeEvent<HTMLInputElement>,
|
||||
): void => {
|
||||
setInput(event.target.value);
|
||||
};
|
||||
const createHandler = (): void => {
|
||||
dispatch(setModalOpen(true));
|
||||
};
|
||||
|
||||
const handleSelectionChange = (
|
||||
event: SelectPickerOptionProps | SelectPickerOptionProps[],
|
||||
): void => {
|
||||
if (!Array.isArray(event)) {
|
||||
setTeam(event);
|
||||
dispatch(fetchTeamDetails(event.value.toString()));
|
||||
scroll.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
const returnIsFilteredResultEmpty = (): boolean => {
|
||||
const filteredResult = options.filter(item =>
|
||||
item.label.toLowerCase().includes(input.toLowerCase()),
|
||||
);
|
||||
return !filteredResult.length;
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles['team-div']}>
|
||||
<div className={styles['search-wrapper']}>
|
||||
<SearchInput
|
||||
value={input}
|
||||
onChange={handleInputChange}
|
||||
isAdmin={Role.includes('Admin')}
|
||||
/>
|
||||
{Role.includes('Admin') && (
|
||||
<Button
|
||||
startAdornment={
|
||||
<AddIcon
|
||||
className={styles['add-icon']}
|
||||
color="var(--navi-color-gray-bg-primary)"
|
||||
size="xl"
|
||||
/>
|
||||
}
|
||||
onClick={createHandler}
|
||||
variant="primary"
|
||||
className={styles['create-team-btn']}
|
||||
size="small"
|
||||
>
|
||||
{''}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{input && (
|
||||
<div className={styles['team-name']}>
|
||||
{' '}
|
||||
<Typography variant="h5" className={styles['search-results']}>
|
||||
{' '}
|
||||
SEARCH RESULTS{' '}
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
{returnIsFilteredResultEmpty() ? (
|
||||
<div className={styles['team-name']}>
|
||||
{' '}
|
||||
<Typography variant="h5" className={styles['search-results']}>
|
||||
{' '}
|
||||
No results found
|
||||
</Typography>
|
||||
</div>
|
||||
) : null}
|
||||
<SelectPicker
|
||||
options={options}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
multiSelect={false}
|
||||
updateSearch={input || ''}
|
||||
wrapperClasses={styles['select-picker-wrapper']}
|
||||
customOptionTemplate={CustomTeamOptions as any}
|
||||
selectedValue={team?.value ?? defaultTeam?.id ?? ''}
|
||||
/>
|
||||
</div>
|
||||
<CreateTeam setTeam={setTeam} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamList;
|
||||
182
src/Pages/TeamRevamp/constants.ts
Normal file
182
src/Pages/TeamRevamp/constants.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import { createURL } from '@src/services/globalUtils';
|
||||
import { ActionType, AppState, Participant } from './types';
|
||||
|
||||
const URL_PREFIX = createURL('/houston');
|
||||
|
||||
export const FETCH_TEAM_DATA = `${URL_PREFIX}/teams`;
|
||||
export const FETCH_ALL_BOTS_DATA = `${URL_PREFIX}/bots`;
|
||||
export const CREATE_TEAM = `${URL_PREFIX}/teams/add`;
|
||||
|
||||
export const FETCH_SINGLE_TEAM_DATA = (payload: string): string => {
|
||||
return `${URL_PREFIX}/teams/${payload}`;
|
||||
};
|
||||
|
||||
export const UPDATE_TEAM_DATA = (): string => {
|
||||
return `${URL_PREFIX}/teams`;
|
||||
};
|
||||
|
||||
export const REMOVE_TEAM_MEMBER = (teamId: string, userId: string): string => {
|
||||
return `${URL_PREFIX}/teams/${teamId}/members/${userId}`;
|
||||
};
|
||||
|
||||
export const MAKE_MANAGER = (teamId: string, userId: string): string => {
|
||||
return `${URL_PREFIX}/teams/${teamId}/manager/${userId}`;
|
||||
};
|
||||
|
||||
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 const actionTypes = {
|
||||
SET_OPEN_DIALOG: 'SET_OPEN_DIALOG',
|
||||
SET_OPEN_MODAL: 'SET_OPEN_MODAL',
|
||||
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',
|
||||
SET_TEAM_NAME: 'SET_TEAM_NAME',
|
||||
SET_TEAM_NAME_ERROR: 'SET_TEAM_NAME_ERROR',
|
||||
SET_EMAIL_ERROR: 'SET_EMAIL_ERROR',
|
||||
SET_EMAIL: 'SET_EMAIL',
|
||||
SET_CLEAR_MODAL: 'SET_CLEAR_MODAL',
|
||||
SET_ADD_MEMBER_ERROR: 'SET_ADD_MEMBER_ERROR',
|
||||
};
|
||||
|
||||
export const initialState: AppState = {
|
||||
openDialog: false,
|
||||
openModal: false,
|
||||
emailIds: '',
|
||||
selectedMember: undefined,
|
||||
hovered: {
|
||||
ishovered: false,
|
||||
id: '',
|
||||
},
|
||||
slackChannelId: '',
|
||||
oncall: {
|
||||
label: '',
|
||||
value: '',
|
||||
},
|
||||
psecOncall: {
|
||||
label: '',
|
||||
value: '',
|
||||
},
|
||||
input: '',
|
||||
psecInput: '',
|
||||
openOnCall: false,
|
||||
openPsecOnCall: false,
|
||||
teamName: '',
|
||||
teamNameError: '',
|
||||
emailError: '',
|
||||
email: '',
|
||||
clearModal: false,
|
||||
addMemberError: '',
|
||||
};
|
||||
|
||||
export const reducer = (state: AppState, action: ActionType): AppState => {
|
||||
switch (action.type) {
|
||||
case actionTypes.SET_OPEN_DIALOG:
|
||||
return {
|
||||
...state,
|
||||
openDialog: action.payload,
|
||||
};
|
||||
case actionTypes.SET_OPEN_MODAL:
|
||||
return {
|
||||
...state,
|
||||
openModal: action.payload,
|
||||
};
|
||||
case actionTypes.SET_EMAIL_IDS:
|
||||
return {
|
||||
...state,
|
||||
emailIds: action.payload,
|
||||
};
|
||||
case actionTypes.SET_SELECTED_MEMBER:
|
||||
return {
|
||||
...state,
|
||||
selectedMember: action.payload,
|
||||
};
|
||||
case actionTypes.SET_HOVERED:
|
||||
return {
|
||||
...state,
|
||||
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,
|
||||
};
|
||||
case actionTypes.SET_TEAM_NAME:
|
||||
return {
|
||||
...state,
|
||||
teamName: action.payload,
|
||||
};
|
||||
case actionTypes.SET_TEAM_NAME_ERROR:
|
||||
return {
|
||||
...state,
|
||||
teamNameError: action.payload,
|
||||
};
|
||||
case actionTypes.SET_EMAIL_ERROR:
|
||||
return {
|
||||
...state,
|
||||
emailError: action.payload,
|
||||
};
|
||||
case actionTypes.SET_EMAIL:
|
||||
return {
|
||||
...state,
|
||||
email: action.payload,
|
||||
};
|
||||
case actionTypes.SET_CLEAR_MODAL:
|
||||
return {
|
||||
...state,
|
||||
teamName: '',
|
||||
email: '',
|
||||
teamNameError: '',
|
||||
emailError: '',
|
||||
};
|
||||
case actionTypes.SET_ADD_MEMBER_ERROR:
|
||||
return {
|
||||
...state,
|
||||
addMemberError: action.payload,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
62
src/Pages/TeamRevamp/index.tsx
Normal file
62
src/Pages/TeamRevamp/index.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { FC, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import Typography from '@navi/web-ui/lib/primitives/Typography';
|
||||
import { AppDispatch } from '@src/store';
|
||||
import ErrorBoundary from '@src/components/ErrorBoundary/ErrorBoundary';
|
||||
import {
|
||||
TeamState,
|
||||
fetchTeamDetails,
|
||||
fetchTeams,
|
||||
selectSearchTeamData,
|
||||
setSelectedTeam,
|
||||
} from '@src/slices/team1Slice';
|
||||
import TeamDetails from './TeamDetails';
|
||||
import { fetchAllBots } from './partials/Bots';
|
||||
import TeamList from './TeamList';
|
||||
import styles from './Team.module.scss';
|
||||
import useClickStream from '@src/services/clickStream';
|
||||
import { CLICK_STREAM_EVENT_FACTORY } from '@src/services/clickStream/constants/values';
|
||||
|
||||
const TeamRevamp: FC = () => {
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const teamData: TeamState = useSelector(selectSearchTeamData);
|
||||
const { data, selectedTeam } = teamData;
|
||||
const selectedTeamId = selectedTeam?.id || '';
|
||||
const { fireEvent } = useClickStream();
|
||||
const { EVENT_NAME, SCREEN_NAME } = CLICK_STREAM_EVENT_FACTORY;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchTeams());
|
||||
fetchAllBots();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
dispatch(setSelectedTeam(data[0]));
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedTeam && selectedTeamId) {
|
||||
dispatch(fetchTeamDetails(selectedTeamId.toString()));
|
||||
}
|
||||
}, [selectedTeam]);
|
||||
useEffect(() => {
|
||||
fireEvent(EVENT_NAME.Houston_Team_Land, {
|
||||
screen_name: SCREEN_NAME.TEAM_PAGE,
|
||||
});
|
||||
}, []);
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<div className={styles['team-wrapper']}>
|
||||
<Typography variant="h3">Teams</Typography>
|
||||
<div className={styles['wrapper-class']}>
|
||||
<TeamList />
|
||||
<TeamDetails />
|
||||
</div>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamRevamp;
|
||||
24
src/Pages/TeamRevamp/partials/Bots.tsx
Normal file
24
src/Pages/TeamRevamp/partials/Bots.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ApiService } from '@src/services/api';
|
||||
import { FETCH_ALL_BOTS_DATA } from '../constants';
|
||||
import { PickerOptionProps } from '../types';
|
||||
import { toast } from '@navi/web-ui/lib/primitives/Toast';
|
||||
|
||||
let bots: Array<PickerOptionProps> = [];
|
||||
|
||||
export const getBots = (): Array<PickerOptionProps> => bots;
|
||||
|
||||
export const fetchAllBots = (): void => {
|
||||
ApiService.get(FETCH_ALL_BOTS_DATA)
|
||||
.then(response => {
|
||||
bots = [];
|
||||
response?.data?.data.map(data => {
|
||||
bots.push({
|
||||
label: '@' + data.name,
|
||||
value: data.id,
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
toast.error(error?.message);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
.team-wrapper {
|
||||
padding: 0px 24px 0px 24px;
|
||||
}
|
||||
@mixin team-form-wrapper {
|
||||
height: medium;
|
||||
margin-bottom: 20px;
|
||||
gap: 0 8px;
|
||||
}
|
||||
.email-wrapper {
|
||||
@include team-form-wrapper;
|
||||
}
|
||||
.input-wrapper {
|
||||
@include team-form-wrapper;
|
||||
}
|
||||
172
src/Pages/TeamRevamp/partials/CreateTeam/CreateTeam.tsx
Normal file
172
src/Pages/TeamRevamp/partials/CreateTeam/CreateTeam.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
import { useReducer } from 'react';
|
||||
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 { toast } from '@navi/web-ui/lib/primitives/Toast';
|
||||
import { AppDispatch } from '@src/store';
|
||||
import { ApiService } from '@src/services/api';
|
||||
import {
|
||||
fetchTeamDetails,
|
||||
fetchTeams,
|
||||
selectSearchTeamData,
|
||||
setModalOpen,
|
||||
setNewTeam,
|
||||
} from '@src/slices/team1Slice';
|
||||
import {
|
||||
CREATE_TEAM,
|
||||
actionTypes,
|
||||
initialState,
|
||||
reducer,
|
||||
} from '@src/Pages/TeamRevamp/constants';
|
||||
import {
|
||||
regularExpression,
|
||||
emailRegularExpression,
|
||||
validEmail,
|
||||
} from '@src/Pages/TeamRevamp/constants';
|
||||
import { CreateTeamProps } from '@src/Pages/TeamRevamp/types';
|
||||
import styles from './CreateTeam.module.scss';
|
||||
import useClickStream from '@src/services/clickStream';
|
||||
import { CLICK_STREAM_EVENT_FACTORY } from '@src/services/clickStream/constants/values';
|
||||
|
||||
const CreateTeam: React.FC<CreateTeamProps> = ({ setTeam }) => {
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const [state, dispatchData] = useReducer(reducer, initialState);
|
||||
const { fireEvent } = useClickStream();
|
||||
const { EVENT_NAME, SCREEN_NAME } = CLICK_STREAM_EVENT_FACTORY;
|
||||
const { modalOpen } = useSelector(selectSearchTeamData);
|
||||
const validateTeamName = (value: string): void => {
|
||||
value = value.trim();
|
||||
dispatchData({
|
||||
type: actionTypes.SET_TEAM_NAME_ERROR,
|
||||
payload: !regularExpression.test(value)
|
||||
? 'Min. 3 characters required. Use spaces, ‘_’, or ‘-’ only'
|
||||
: '',
|
||||
});
|
||||
};
|
||||
|
||||
const validateEmail = (value: string): void => {
|
||||
dispatchData({
|
||||
type: actionTypes.SET_EMAIL_ERROR,
|
||||
payload: validEmail.test(value)
|
||||
? !emailRegularExpression.test(value)
|
||||
? 'Please enter a Navi email ID'
|
||||
: ''
|
||||
: '',
|
||||
});
|
||||
};
|
||||
const handleTeamNameChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
): void => {
|
||||
const inputValue = e.target.value;
|
||||
dispatchData({
|
||||
type: actionTypes.SET_TEAM_NAME,
|
||||
payload: inputValue,
|
||||
});
|
||||
validateTeamName(inputValue);
|
||||
};
|
||||
|
||||
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
const inputValue = e.target.value;
|
||||
dispatchData({
|
||||
type: actionTypes.SET_EMAIL,
|
||||
payload: inputValue,
|
||||
});
|
||||
validateEmail(inputValue);
|
||||
};
|
||||
|
||||
const addTeamHandler = (): void => {
|
||||
fireEvent(EVENT_NAME.Houston_Create_Team, {
|
||||
screen_name: SCREEN_NAME.TEAM_PAGE,
|
||||
});
|
||||
ApiService.post(CREATE_TEAM, {
|
||||
name: state.teamName,
|
||||
manager_email: state.email,
|
||||
})
|
||||
.then(response => {
|
||||
const toastMessage = `${response?.data?.data?.message}`;
|
||||
const createdTeamId = response?.data?.data?.id;
|
||||
const createdTeamName = response?.data?.data?.name;
|
||||
toast.success(toastMessage);
|
||||
clearErrors();
|
||||
dispatch(fetchTeamDetails(createdTeamId.toString()));
|
||||
setTeam({ label: createdTeamName, value: createdTeamId });
|
||||
dispatch(setNewTeam(createdTeamId));
|
||||
dispatch(fetchTeams());
|
||||
})
|
||||
.catch(error => {
|
||||
const toastMessage = `${
|
||||
error?.response?.data?.error?.message
|
||||
? `${error?.response?.data?.error?.message}`
|
||||
: ''
|
||||
}`;
|
||||
toast.error(toastMessage);
|
||||
});
|
||||
};
|
||||
|
||||
const clearErrors = (): void => {
|
||||
dispatch(setModalOpen(false));
|
||||
dispatchData({
|
||||
type: actionTypes.SET_CLEAR_MODAL,
|
||||
payload: '',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles['team-wrapper']}>
|
||||
{modalOpen && (
|
||||
<ModalDialog
|
||||
open={modalOpen}
|
||||
footerButtons={[
|
||||
{
|
||||
label: 'Cancel',
|
||||
onClick: clearErrors,
|
||||
},
|
||||
{
|
||||
label: 'Submit',
|
||||
onClick: addTeamHandler,
|
||||
disabled:
|
||||
!!state.teamNameError ||
|
||||
!!state.emailError ||
|
||||
!state.teamName ||
|
||||
!state.email,
|
||||
},
|
||||
]}
|
||||
header="Set up new team "
|
||||
onClose={clearErrors}
|
||||
>
|
||||
<div className={styles['input-wrapper']}>
|
||||
<Typography variant="p4">
|
||||
<BorderedInput
|
||||
inputLabel="Team name"
|
||||
inputSize="full-width"
|
||||
type="text"
|
||||
value={state.teamName}
|
||||
onChange={handleTeamNameChange}
|
||||
error={state.teamNameError}
|
||||
Icon={<AlertOutlineIcon color="var(--navi-color-red-base)" />}
|
||||
placeholder="E.g. NaviPay_Operations"
|
||||
/>
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<div className={styles['email-wrapper']}>
|
||||
<Typography variant="p4" color="#F98600">
|
||||
<BorderedInput
|
||||
inputLabel="Manager e-mail ID"
|
||||
inputSize="full-width"
|
||||
type="text"
|
||||
value={state.email}
|
||||
onChange={handleEmailChange}
|
||||
placeholder="name@navi.com"
|
||||
error={state.emailError}
|
||||
Icon={<AlertOutlineIcon color="var(--navi-color-red-base)" />}
|
||||
/>
|
||||
</Typography>
|
||||
</div>
|
||||
</ModalDialog>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default CreateTeam;
|
||||
76
src/Pages/TeamRevamp/partials/SlackDetails/Slack.module.scss
Normal file
76
src/Pages/TeamRevamp/partials/SlackDetails/Slack.module.scss
Normal file
@@ -0,0 +1,76 @@
|
||||
.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;
|
||||
}
|
||||
@mixin slack-label {
|
||||
color: var(--navi-color-gray-c2);
|
||||
}
|
||||
.channel-label,
|
||||
.oncall-label,
|
||||
.psec-oncall-label {
|
||||
@include slack-label;
|
||||
}
|
||||
.hint-text {
|
||||
color: var(--navi-color-gray-c3);
|
||||
}
|
||||
.hint-text-disabled {
|
||||
opacity: 0;
|
||||
}
|
||||
285
src/Pages/TeamRevamp/partials/SlackDetails/index.tsx
Normal file
285
src/Pages/TeamRevamp/partials/SlackDetails/index.tsx
Normal file
@@ -0,0 +1,285 @@
|
||||
import { useEffect, 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 { AlertOutlineIcon } from '@navi/web-ui/lib/icons';
|
||||
import useOutsideClick from '@src/services/hooks/useOustideClick';
|
||||
import { useAuthData } from '@src/services/hooks/useAuth';
|
||||
import useTeamApis from '../../useTeamApis';
|
||||
import { actionTypes, initialState, reducer } from '../../constants';
|
||||
import { TeamsDetail } from '../../types';
|
||||
import { getBots } from '../Bots';
|
||||
import useGetTeamDetailsConstants from '../../util';
|
||||
import styles from './Slack.module.scss';
|
||||
|
||||
const TeamDetails: React.FC = () => {
|
||||
const [state, dispatchData] = useReducer(reducer, initialState);
|
||||
const Role = useAuthData();
|
||||
const { updateDetails } = useTeamApis();
|
||||
const {
|
||||
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,
|
||||
},
|
||||
});
|
||||
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,
|
||||
},
|
||||
});
|
||||
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 isUserParticipant = (teamDetails: TeamsDetail): boolean | undefined => {
|
||||
const participantEmails = teamDetails?.participants?.map(
|
||||
(participant: any) => participant.email,
|
||||
);
|
||||
return participantEmails?.includes(userEmail);
|
||||
};
|
||||
const isHintEnabled =
|
||||
isUserParticipant(teamDetails) ||
|
||||
Role.includes('Admin') ||
|
||||
userEmail === managerEmail;
|
||||
const hintClassName = isHintEnabled
|
||||
? styles['hint-text']
|
||||
: styles['hint-text-disabled'];
|
||||
|
||||
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" className={styles['channel-label']}>
|
||||
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}
|
||||
hintTextClasses={hintClassName}
|
||||
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" className={styles['oncall-label']}>
|
||||
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" className={styles['psec-oncall-label']}>
|
||||
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(
|
||||
state.slackChannelId,
|
||||
state.oncall.value,
|
||||
state.psecOncall.value,
|
||||
);
|
||||
}}
|
||||
disabled={
|
||||
!state.slackChannelId && !state.psecInput && !state.input
|
||||
}
|
||||
>
|
||||
Update details
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamDetails;
|
||||
@@ -0,0 +1,105 @@
|
||||
.member-details-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
.add-member {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.details-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
justify-content: space-around;
|
||||
gap: 12px;
|
||||
}
|
||||
.item-details {
|
||||
margin-top: 12px;
|
||||
margin-left: 10px;
|
||||
width: calc(100% - 48px);
|
||||
}
|
||||
.details-container {
|
||||
display: flex;
|
||||
width: 252px;
|
||||
height: 68px;
|
||||
}
|
||||
.icon-wrapper {
|
||||
margin-top: 12px;
|
||||
width: 32px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.items-wrapper {
|
||||
width: 252px;
|
||||
border-radius: 8px;
|
||||
height: 95px;
|
||||
}
|
||||
.team-member-view .make-manger-button,
|
||||
.delete-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.items-wrapper .make-manger-button,
|
||||
.items-wrapper .delete-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.items-wrapper:hover .make-manger-button {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.items-wrapper:hover .delete-icon {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
width: 16px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
.items-wrapper:hover {
|
||||
background-color: var(--navi-color-blue-bg);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.manager-wrapper {
|
||||
width: 253px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.remove-button {
|
||||
background-color: var(--navi-color-red-base);
|
||||
height: 36px;
|
||||
width: 77px;
|
||||
color: var(--navi-color-gray-bg-primary);
|
||||
}
|
||||
.cancel-button {
|
||||
background: var(--navi-color-gray-bg-primary);
|
||||
height: 36px;
|
||||
width: 77px;
|
||||
color: var(--navi-color-gray-c2);
|
||||
margin-right: 12px;
|
||||
border: 1px solid var(--navi-color-gray-border);
|
||||
border-radius: 6px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
.team-input-wrapper {
|
||||
width: 510px;
|
||||
height: 36px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.footer-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.input-wrapper-child {
|
||||
display: flex;
|
||||
}
|
||||
.horizontal-line {
|
||||
display: flex;
|
||||
}
|
||||
.hr-tag {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
margin-left: 5px;
|
||||
border: none;
|
||||
border-top: 2px solid var(--navi-color-gray-border);
|
||||
}
|
||||
.members-tag {
|
||||
color: var(--navi-color-gray-c3);
|
||||
}
|
||||
295
src/Pages/TeamRevamp/partials/TeamMemberDetails/index.tsx
Normal file
295
src/Pages/TeamRevamp/partials/TeamMemberDetails/index.tsx
Normal file
@@ -0,0 +1,295 @@
|
||||
import { useReducer } from 'react';
|
||||
import {
|
||||
BorderedInput,
|
||||
Button,
|
||||
Typography,
|
||||
ModalDialog,
|
||||
} from '@navi/web-ui/lib/primitives';
|
||||
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 { useAuthData } from '@src/services/hooks/useAuth';
|
||||
import { useGetTeamDetailsConstants } from '@src/Pages/TeamRevamp/util';
|
||||
import useTeamApis from '../../useTeamApis';
|
||||
import {
|
||||
actionTypes,
|
||||
emailRegularExpression,
|
||||
initialState,
|
||||
reducer,
|
||||
validEmail,
|
||||
} from '@src/Pages/TeamRevamp/constants';
|
||||
import { TeamsDetail } from '@src/Pages/TeamRevamp/types';
|
||||
import styles from './TeamMemberDetails.module.scss';
|
||||
|
||||
type MemberType = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
};
|
||||
const TeamMemberDetails: React.FC = () => {
|
||||
const [state, dispatchData] = useReducer(reducer, initialState);
|
||||
const Role = useAuthData();
|
||||
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,
|
||||
);
|
||||
return participantEmails?.includes(userEmail);
|
||||
};
|
||||
|
||||
const validateEmail = (value: string): void => {
|
||||
const emailAddresses = value.split(',').map(email => email.trim());
|
||||
const errorMessage = emailAddresses
|
||||
.map(email =>
|
||||
validEmail.test(email)
|
||||
? emailRegularExpression.test(email)
|
||||
? ''
|
||||
: 'Please enter a Navi email ID'
|
||||
: '',
|
||||
)
|
||||
.find(errorMessage => errorMessage !== '');
|
||||
dispatchData({
|
||||
type: actionTypes.SET_ADD_MEMBER_ERROR,
|
||||
payload: errorMessage || '',
|
||||
});
|
||||
};
|
||||
const handleAddMember = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
const inputValue = e.target.value;
|
||||
dispatchData({
|
||||
type: actionTypes.SET_EMAIL_IDS,
|
||||
payload: inputValue,
|
||||
});
|
||||
validateEmail(inputValue);
|
||||
};
|
||||
const onKeyPressClickHandler = (event: React.KeyboardEvent): void => {
|
||||
if (state.addMemberError) {
|
||||
return;
|
||||
}
|
||||
if (event.key === 'Enter') {
|
||||
addMemberHandler(state.emailIds);
|
||||
dispatchData({
|
||||
type: actionTypes.SET_EMAIL_IDS,
|
||||
payload: '',
|
||||
});
|
||||
}
|
||||
dispatchData({
|
||||
type: actionTypes.SET_EMAIL_IDS,
|
||||
payload: '',
|
||||
});
|
||||
};
|
||||
|
||||
const handleOpenModal = (member: MemberType): void => {
|
||||
dispatchData({
|
||||
type: actionTypes.SET_SELECTED_MEMBER,
|
||||
payload: member,
|
||||
});
|
||||
dispatchData({
|
||||
type: actionTypes.SET_OPEN_MODAL,
|
||||
payload: true,
|
||||
});
|
||||
};
|
||||
const handleResetModal = (): void => {
|
||||
dispatchData({
|
||||
type: actionTypes.SET_OPEN_MODAL,
|
||||
payload: false,
|
||||
});
|
||||
};
|
||||
const handleOpenDialog = (member: MemberType): void => {
|
||||
dispatchData({
|
||||
type: actionTypes.SET_SELECTED_MEMBER,
|
||||
payload: member,
|
||||
});
|
||||
dispatchData({
|
||||
type: actionTypes.SET_OPEN_DIALOG,
|
||||
payload: true,
|
||||
});
|
||||
};
|
||||
const handleResetDialog = (): void => {
|
||||
dispatchData({
|
||||
type: actionTypes.SET_OPEN_DIALOG,
|
||||
payload: false,
|
||||
});
|
||||
};
|
||||
|
||||
const handleHoverchange = (item: MemberType): void => {
|
||||
if (userEmail === managerEmail || Role.includes('Admin')) {
|
||||
dispatchData({
|
||||
type: actionTypes.SET_HOVERED,
|
||||
payload: {
|
||||
ishovered: false,
|
||||
id: item.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
const handleHoverIcon = (item: MemberType): void => {
|
||||
if (userEmail === managerEmail || Role.includes('Admin')) {
|
||||
dispatchData({
|
||||
type: actionTypes.SET_HOVERED,
|
||||
payload: {
|
||||
ishovered: true,
|
||||
id: item.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
const computedClassName = (item: MemberType): string => {
|
||||
return (
|
||||
(manager?.name === item.name && styles['manager-wrapper']) ||
|
||||
(userEmail !== managerEmail &&
|
||||
!Role.includes('Admin') &&
|
||||
styles['team-member-view']) ||
|
||||
styles['items-wrapper']
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<div className={styles['add-member']}>
|
||||
<div className={styles['horizontal-line']}>
|
||||
<Typography variant="h5" className={styles['members-tag']}>
|
||||
MEMBERS
|
||||
</Typography>
|
||||
<hr className={styles['hr-tag']}></hr>{' '}
|
||||
</div>
|
||||
{(isUserParticipant(teamDetails) ||
|
||||
Role.includes('Admin') ||
|
||||
userEmail === managerEmail) && (
|
||||
<BorderedInput
|
||||
placeholder={'Add comma separated e-mail ID(s)'}
|
||||
onChange={handleAddMember}
|
||||
hintMsg={state.addMemberError ? '' : 'Press enter to add e-mails'}
|
||||
onKeyPress={onKeyPressClickHandler}
|
||||
value={state.emailIds}
|
||||
containerClassName={styles['team-input-wrapper']}
|
||||
disabled={
|
||||
!isUserParticipant(teamDetails) && !Role.includes('Admin')
|
||||
}
|
||||
error={state.addMemberError}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div key={teamId} className={styles['member-details-wrapper']}>
|
||||
<div key={teamId} className={styles['details-wrapper']}>
|
||||
{teamMembers?.map(item => (
|
||||
<div
|
||||
className={computedClassName(item)}
|
||||
key={item.id}
|
||||
onMouseEnter={() => handleHoverIcon(item)}
|
||||
onMouseLeave={() => handleHoverchange(item)}
|
||||
>
|
||||
<div className={styles['details-container']}>
|
||||
<div className={styles['icon-wrapper']}>
|
||||
{manager?.name === item.name ? (
|
||||
<ManagerIconFill />
|
||||
) : (
|
||||
<div>
|
||||
{state.hovered.ishovered &&
|
||||
state.hovered.id === item.id ? (
|
||||
<MemberIcon />
|
||||
) : (
|
||||
<PersonIconFill />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div key={item.id} className={styles['item-details']}>
|
||||
{' '}
|
||||
<Typography variant="p3"> {item.name}</Typography>
|
||||
<Typography variant="p5"> {item.email}</Typography>
|
||||
<Typography variant="p5">
|
||||
{manager?.name === item.name ? 'Manager' : ''}
|
||||
</Typography>{' '}
|
||||
<div className={styles['make-manger-button']}>
|
||||
{manager?.name !== item.name && (
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={() => {
|
||||
handleOpenModal(item);
|
||||
}}
|
||||
>
|
||||
Make manager{' '}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{manager?.name !== item.name && (
|
||||
<div
|
||||
id={item.id}
|
||||
onClick={() => {
|
||||
handleOpenDialog(item);
|
||||
}}
|
||||
className={styles['delete-icon']}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<ModalDialog
|
||||
open={state.openModal}
|
||||
footerButtons={[
|
||||
{
|
||||
label: 'Cancel',
|
||||
onClick: handleResetModal,
|
||||
},
|
||||
{
|
||||
label: 'Update',
|
||||
onClick: () => {
|
||||
makeManager(state.selectedMember?.id.toString() || '');
|
||||
handleResetModal();
|
||||
},
|
||||
},
|
||||
]}
|
||||
header="Update manager? "
|
||||
onClose={handleResetModal}
|
||||
>
|
||||
<div className={styles['input-wrapper']}>
|
||||
<Typography variant="p4">
|
||||
Are you sure you want to make {`${state.selectedMember?.name}`}{' '}
|
||||
manager of this team?
|
||||
</Typography>
|
||||
</div>
|
||||
</ModalDialog>
|
||||
<ModalDialog
|
||||
open={state.openDialog}
|
||||
header="Remove member? "
|
||||
onClose={handleResetDialog}
|
||||
>
|
||||
<div className={styles['input-wrapper']}>
|
||||
<Typography variant="p4">
|
||||
Are you sure you want to remove {`${state.selectedMember?.name}`}{' '}
|
||||
from this team?
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<div className={styles['footer-wrapper']}>
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={handleResetDialog}
|
||||
className={styles['cancel-button']}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={() => {
|
||||
removeTeamMember(state.selectedMember?.id.toString() || '');
|
||||
handleResetDialog();
|
||||
}}
|
||||
className={styles['remove-button']}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
</div>
|
||||
</ModalDialog>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default TeamMemberDetails;
|
||||
125
src/Pages/TeamRevamp/types.ts
Normal file
125
src/Pages/TeamRevamp/types.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import { TeamState } from '@src/slices/team1Slice';
|
||||
import { SelectPickerOptionProps } from '@navi/web-ui/lib/components/SelectPicker/types';
|
||||
export interface SearchInputComponentProps {
|
||||
value: string;
|
||||
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||
isAdmin: boolean;
|
||||
}
|
||||
export interface useTeamApiProps {
|
||||
addMemberHandler: (emailIds: string) => void;
|
||||
removeTeamMember: (memberId: string) => void;
|
||||
makeManager: (memberId: string) => void;
|
||||
updateDetails: (
|
||||
slackChannelId: string,
|
||||
oncall: string,
|
||||
psecOncall: string,
|
||||
) => void;
|
||||
}
|
||||
export interface TeamsData {
|
||||
id?: string;
|
||||
name?: string;
|
||||
slackUserIds?: Array<string>;
|
||||
lastUpdatedAt?: string;
|
||||
expand?: boolean;
|
||||
}
|
||||
export interface CreateTeamProps {
|
||||
setTeam: React.Dispatch<
|
||||
React.SetStateAction<SelectPickerOptionProps | undefined>
|
||||
>;
|
||||
}
|
||||
export interface BotsData {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface TeamFormProps {
|
||||
teamId: string;
|
||||
isExpanded: boolean;
|
||||
setLastUpdatedAt: (value: string) => void;
|
||||
}
|
||||
|
||||
export const slackUserOptions = [
|
||||
{
|
||||
label: 'Email-id',
|
||||
value: 'WorkEmailIds',
|
||||
},
|
||||
{
|
||||
label: 'Slack user-id',
|
||||
value: 'SlackUserIds',
|
||||
},
|
||||
];
|
||||
|
||||
export const userInputPlaceholders = {
|
||||
'Email-id': 'email ids',
|
||||
'Slack user-id': 'slack user ids',
|
||||
};
|
||||
|
||||
export interface PickerOptionProps {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
export interface Details {
|
||||
teamId: number | undefined;
|
||||
userEmail: string | undefined;
|
||||
managerEmail: string | undefined;
|
||||
manager: Participant | undefined;
|
||||
teamMembers: any;
|
||||
teamDetails: TeamsDetail;
|
||||
teamData: TeamState;
|
||||
DEFAULT_TEAM_ONCALL: string;
|
||||
DEFAULT_TEAM_ONCALL_ID: string;
|
||||
DEFAULT_TEAM_PSEC_ID: string;
|
||||
DEFAULT_PSEC_ONCALL: string;
|
||||
webChannelId: string;
|
||||
}
|
||||
export interface Participant {
|
||||
name: string;
|
||||
email: string;
|
||||
id: string;
|
||||
}
|
||||
export interface TeamsDetail {
|
||||
id?: number;
|
||||
lastUpdatedAt?: string;
|
||||
managerId?: string;
|
||||
name?: string;
|
||||
oncall?: { id: string; name: string };
|
||||
participants?: Array<Participant>;
|
||||
pse_oncall?: { id: string; name: string };
|
||||
slackUserIds?: string[];
|
||||
webhookSlackChannelId?: string;
|
||||
webhookSlackChannelName?: string;
|
||||
}
|
||||
export 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;
|
||||
teamName: string;
|
||||
teamNameError: string;
|
||||
emailError: string;
|
||||
email: string;
|
||||
clearModal: boolean;
|
||||
addMemberError: string;
|
||||
}
|
||||
export interface ActionType {
|
||||
type: string;
|
||||
payload: any;
|
||||
}
|
||||
147
src/Pages/TeamRevamp/useTeamApis.tsx
Normal file
147
src/Pages/TeamRevamp/useTeamApis.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import { useReducer } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { toast } from '@navi/web-ui/lib/primitives/Toast';
|
||||
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,
|
||||
actionTypes,
|
||||
initialState,
|
||||
reducer,
|
||||
} from './constants';
|
||||
import { useGetTeamDetailsConstants } from './util';
|
||||
import { useTeamApiProps } from './types';
|
||||
import useClickStream from '@src/services/clickStream';
|
||||
import { CLICK_STREAM_EVENT_FACTORY } from '@src/services/clickStream/constants/values';
|
||||
|
||||
const useTeamApis = (): useTeamApiProps => {
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
const { teamId } = useGetTeamDetailsConstants();
|
||||
const [state, dispatchData] = useReducer(reducer, initialState);
|
||||
const { fireEvent } = useClickStream();
|
||||
const { EVENT_NAME, SCREEN_NAME } = CLICK_STREAM_EVENT_FACTORY;
|
||||
|
||||
const addMemberHandler = (emailIds: string): void => {
|
||||
fireEvent(EVENT_NAME.Houston_Add_Member, {
|
||||
screen_name: SCREEN_NAME.TEAM_PAGE,
|
||||
});
|
||||
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 => {
|
||||
fireEvent(EVENT_NAME.Houston_Remove_Member, {
|
||||
screen_name: SCREEN_NAME.TEAM_PAGE,
|
||||
});
|
||||
const endpoint = REMOVE_TEAM_MEMBER(teamId?.toString() || '', memberId);
|
||||
ApiService.delete(endpoint)
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
toast.success(response.data.data);
|
||||
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 => {
|
||||
fireEvent(EVENT_NAME.Houston_Make_Manager, {
|
||||
screen_name: SCREEN_NAME.TEAM_PAGE,
|
||||
});
|
||||
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}`);
|
||||
});
|
||||
};
|
||||
const updateDetails = (
|
||||
slackChannelId: string,
|
||||
oncall: string,
|
||||
psecOncall: string,
|
||||
): void => {
|
||||
if (oncall) {
|
||||
fireEvent(EVENT_NAME.Houston_Add_Oncall, {
|
||||
screen_name: SCREEN_NAME.TEAM_PAGE,
|
||||
});
|
||||
}
|
||||
|
||||
if (psecOncall) {
|
||||
fireEvent(EVENT_NAME.Houston_Add_Pseconcall, {
|
||||
screen_name: SCREEN_NAME.TEAM_PAGE,
|
||||
});
|
||||
}
|
||||
const endPoint = UPDATE_TEAM_DATA();
|
||||
ApiService.post(endPoint, {
|
||||
id: teamId,
|
||||
webhook_slack_channel: slackChannelId,
|
||||
on_call_handle: oncall,
|
||||
pse_on_call_id: psecOncall,
|
||||
})
|
||||
.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);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
addMemberHandler,
|
||||
removeTeamMember,
|
||||
makeManager,
|
||||
updateDetails,
|
||||
};
|
||||
};
|
||||
|
||||
export default useTeamApis;
|
||||
57
src/Pages/TeamRevamp/util.ts
Normal file
57
src/Pages/TeamRevamp/util.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { TeamState, selectSearchTeamData } from '@src/slices/team1Slice';
|
||||
import { Details } from './types';
|
||||
export const useTeamDetails = (): TeamState => {
|
||||
return useSelector(selectSearchTeamData);
|
||||
};
|
||||
|
||||
export const useGetTeamDetailsConstants = (): Details => {
|
||||
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;
|
||||
38
src/assets/AlertIcon.tsx
Normal file
38
src/assets/AlertIcon.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React, { FC, useState } from 'react';
|
||||
import { IconProps } from './types';
|
||||
|
||||
const AlertIcon: FC<IconProps> = ({
|
||||
width = '24',
|
||||
height = '24',
|
||||
onClick,
|
||||
...restProps
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
>
|
||||
<mask
|
||||
id="mask0_3268_65747"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
>
|
||||
<rect width="24" height="24" fill="#D9D9D9" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_3268_65747)">
|
||||
<path
|
||||
d="M12 17C12.2833 17 12.521 16.904 12.713 16.712C12.9043 16.5207 13 16.2833 13 16C13 15.7167 12.9043 15.479 12.713 15.287C12.521 15.0957 12.2833 15 12 15C11.7167 15 11.4793 15.0957 11.288 15.287C11.096 15.479 11 15.7167 11 16C11 16.2833 11.096 16.5207 11.288 16.712C11.4793 16.904 11.7167 17 12 17ZM11 12C11 12.5523 11.4477 13 12 13C12.5523 13 13 12.5523 13 12V8C13 7.44772 12.5523 7 12 7C11.4477 7 11 7.44772 11 8V12ZM12 22C10.6167 22 9.31667 21.7373 8.1 21.212C6.88333 20.6873 5.825 19.975 4.925 19.075C4.025 18.175 3.31267 17.1167 2.788 15.9C2.26267 14.6833 2 13.3833 2 12C2 10.6167 2.26267 9.31667 2.788 8.1C3.31267 6.88333 4.025 5.825 4.925 4.925C5.825 4.025 6.88333 3.31233 8.1 2.787C9.31667 2.26233 10.6167 2 12 2C13.3833 2 14.6833 2.26233 15.9 2.787C17.1167 3.31233 18.175 4.025 19.075 4.925C19.975 5.825 20.6873 6.88333 21.212 8.1C21.7373 9.31667 22 10.6167 22 12C22 13.3833 21.7373 14.6833 21.212 15.9C20.6873 17.1167 19.975 18.175 19.075 19.075C18.175 19.975 17.1167 20.6873 15.9 21.212C14.6833 21.7373 13.3833 22 12 22Z"
|
||||
fill="#F98600"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default AlertIcon;
|
||||
26
src/assets/DeleteIcon.tsx
Normal file
26
src/assets/DeleteIcon.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { FC } from 'react';
|
||||
import { IconProps } from './types';
|
||||
|
||||
const DeleteIcon: FC<IconProps> = ({
|
||||
width = '24',
|
||||
height = '24',
|
||||
onClick,
|
||||
...restProps
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
height="16"
|
||||
viewBox="0 0 14 16"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M2.83333 15.5C2.37499 15.5 1.98277 15.3369 1.65666 15.0108C1.32999 14.6842 1.16666 14.2917 1.16666 13.8333V3C0.93055 3 0.732495 2.92028 0.572495 2.76083C0.413051 2.60083 0.333328 2.40278 0.333328 2.16667C0.333328 1.93056 0.413051 1.7325 0.572495 1.5725C0.732495 1.41306 0.93055 1.33333 1.16666 1.33333H4.49999C4.49999 1.09722 4.57999 0.899167 4.74 0.739167C4.89944 0.579722 5.09722 0.5 5.33333 0.5H8.66666C8.90277 0.5 9.10083 0.579722 9.26083 0.739167C9.42027 0.899167 9.49999 1.09722 9.49999 1.33333H12.8333C13.0694 1.33333 13.2672 1.41306 13.4267 1.5725C13.5867 1.7325 13.6667 1.93056 13.6667 2.16667C13.6667 2.40278 13.5867 2.60083 13.4267 2.76083C13.2672 2.92028 13.0694 3 12.8333 3V13.8333C12.8333 14.2917 12.6703 14.6842 12.3442 15.0108C12.0175 15.3369 11.625 15.5 11.1667 15.5H2.83333ZM2.83333 3V13.8333H11.1667V3H2.83333ZM4.49999 11.3333C4.49999 11.5694 4.57999 11.7672 4.74 11.9267C4.89944 12.0867 5.09722 12.1667 5.33333 12.1667C5.56944 12.1667 5.7675 12.0867 5.9275 11.9267C6.08694 11.7672 6.16666 11.5694 6.16666 11.3333V5.5C6.16666 5.26389 6.08694 5.06583 5.9275 4.90583C5.7675 4.74639 5.56944 4.66667 5.33333 4.66667C5.09722 4.66667 4.89944 4.74639 4.74 4.90583C4.57999 5.06583 4.49999 5.26389 4.49999 5.5V11.3333ZM7.83333 11.3333C7.83333 11.5694 7.91333 11.7672 8.07333 11.9267C8.23277 12.0867 8.43055 12.1667 8.66666 12.1667C8.90277 12.1667 9.10083 12.0867 9.26083 11.9267C9.42027 11.7672 9.49999 11.5694 9.49999 11.3333V5.5C9.49999 5.26389 9.42027 5.06583 9.26083 4.90583C9.10083 4.74639 8.90277 4.66667 8.66666 4.66667C8.43055 4.66667 8.23277 4.74639 8.07333 4.90583C7.91333 5.06583 7.83333 5.26389 7.83333 5.5V11.3333Z"
|
||||
fill="#E92C2C"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteIcon;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { FC } from 'react';
|
||||
import { FC } from 'react';
|
||||
|
||||
import { IconProps } from './types';
|
||||
|
||||
|
||||
28
src/assets/ManagerIconFill.tsx
Normal file
28
src/assets/ManagerIconFill.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React, { FC } from 'react';
|
||||
import { IconProps } from './types';
|
||||
|
||||
const ManagerIconFill: FC<IconProps> = ({
|
||||
width = '32',
|
||||
height = '32',
|
||||
...restProps
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
>
|
||||
<rect width="32" height="32" rx="16" fill="#E6F1FF" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M16 7C18.2133 7 20 8.78667 20 11C20 13.2133 18.2133 15 16 15C13.7867 15 12 13.2133 12 11C12 8.78667 13.7867 7 16 7ZM16 25.9333C12.6667 25.9333 9.72 24.2267 8 21.64C8.04 18.9867 13.3333 17.5333 16 17.5333C18.6533 17.5333 23.96 18.9867 24 21.64C22.28 24.2267 19.3333 25.9333 16 25.9333Z"
|
||||
fill="#0276FE"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManagerIconFill;
|
||||
28
src/assets/MemberIcon.tsx
Normal file
28
src/assets/MemberIcon.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React, { FC } from 'react';
|
||||
import { IconProps } from './types';
|
||||
|
||||
const MemberIcon: FC<IconProps> = ({
|
||||
width = '32',
|
||||
height = '32',
|
||||
...restProps
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
>
|
||||
<rect width="32" height="32" rx="16" fill="#CCE4FF" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M16 7C18.2133 7 20 8.78667 20 11C20 13.2133 18.2133 15 16 15C13.7867 15 12 13.2133 12 11C12 8.78667 13.7867 7 16 7ZM16 25.9333C12.6667 25.9333 9.72 24.2267 8 21.64C8.04 18.9867 13.3333 17.5333 16 17.5333C18.6533 17.5333 23.96 18.9867 24 21.64C22.28 24.2267 19.3333 25.9333 16 25.9333Z"
|
||||
fill="#3591FE"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default MemberIcon;
|
||||
@@ -7,8 +7,6 @@ const PersonIcon: FC<IconProps> = ({
|
||||
onClick,
|
||||
...restProps
|
||||
}) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
@@ -17,13 +15,11 @@ const PersonIcon: FC<IconProps> = ({
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
onClick={onClick as MouseEventHandler<SVGSVGElement>}
|
||||
onMouseEnter={() => setIsHovered(true)} // Set isHovered to true on hover
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<path
|
||||
d="M8 8C6.9 8 5.95833 7.60833 5.175 6.825C4.39167 6.04167 4 5.1 4 4C4 2.9 4.39167 1.95833 5.175 1.175C5.95833 0.391667 6.9 0 8 0C9.1 0 10.0417 0.391667 10.825 1.175C11.6083 1.95833 12 2.9 12 4C12 5.1 11.6083 6.04167 10.825 6.825C10.0417 7.60833 9.1 8 8 8ZM2 16C1.45 16 0.979333 15.8043 0.588 15.413C0.196 15.021 0 14.55 0 14V13.2C0 12.6333 0.146 12.1123 0.438 11.637C0.729334 11.1623 1.11667 10.8 1.6 10.55C2.63333 10.0333 3.68333 9.64567 4.75 9.387C5.81667 9.129 6.9 9 8 9C9.1 9 10.1833 9.129 11.25 9.387C12.3167 9.64567 13.3667 10.0333 14.4 10.55C14.8833 10.8 15.2707 11.1623 15.562 11.637C15.854 12.1123 16 12.6333 16 13.2V14C16 14.55 15.8043 15.021 15.413 15.413C15.021 15.8043 14.55 16 14 16H2Z"
|
||||
fill={isHovered ? '#0276FE' : '#969696'}
|
||||
fill={'#969696'}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
33
src/assets/PersonIconFill.tsx
Normal file
33
src/assets/PersonIconFill.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React, { FC, MouseEventHandler, useState } from 'react';
|
||||
import { IconProps } from './types';
|
||||
|
||||
const PersonIconFill: FC<IconProps> = ({
|
||||
width = '32',
|
||||
height = '32',
|
||||
onClick,
|
||||
...restProps
|
||||
}) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
onClick={onClick as MouseEventHandler<SVGSVGElement>}
|
||||
onMouseEnter={() => setIsHovered(true)} // Set isHovered to true on hover
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<rect width="32" height="32" rx="16" fill="#E8E8E8" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M16 7C18.2133 7 20 8.78667 20 11C20 13.2133 18.2133 15 16 15C13.7867 15 12 13.2133 12 11C12 8.78667 13.7867 7 16 7ZM16 25.9333C12.6667 25.9333 9.72 24.2267 8 21.64C8.04 18.9867 13.3333 17.5333 16 17.5333C18.6533 17.5333 23.96 18.9867 24 21.64C22.28 24.2267 19.3333 25.9333 16 25.9333Z"
|
||||
fill="#585757"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default PersonIconFill;
|
||||
28
src/assets/SelectedPersonIcon.tsx
Normal file
28
src/assets/SelectedPersonIcon.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { FC, MouseEventHandler, useState } from 'react';
|
||||
import { IconProps } from './types';
|
||||
|
||||
const PersonIcon: FC<IconProps> = ({
|
||||
width = '24',
|
||||
height = '24',
|
||||
onClick,
|
||||
...restProps
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
onClick={onClick as MouseEventHandler<SVGSVGElement>}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<path
|
||||
d="M8 8C6.9 8 5.95833 7.60833 5.175 6.825C4.39167 6.04167 4 5.1 4 4C4 2.9 4.39167 1.95833 5.175 1.175C5.95833 0.391667 6.9 0 8 0C9.1 0 10.0417 0.391667 10.825 1.175C11.6083 1.95833 12 2.9 12 4C12 5.1 11.6083 6.04167 10.825 6.825C10.0417 7.60833 9.1 8 8 8ZM2 16C1.45 16 0.979333 15.8043 0.588 15.413C0.196 15.021 0 14.55 0 14V13.2C0 12.6333 0.146 12.1123 0.438 11.637C0.729334 11.1623 1.11667 10.8 1.6 10.55C2.63333 10.0333 3.68333 9.64567 4.75 9.387C5.81667 9.129 6.9 9 8 9C9.1 9 10.1833 9.129 11.25 9.387C12.3167 9.64567 13.3667 10.0333 14.4 10.55C14.8833 10.8 15.2707 11.1623 15.562 11.637C15.854 12.1123 16 12.6333 16 13.2V14C16 14.55 15.8043 15.021 15.413 15.413C15.021 15.8043 14.55 16 14 16H2Z"
|
||||
fill="#0276FE"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default PersonIcon;
|
||||
33
src/assets/TeamIcon.tsx
Normal file
33
src/assets/TeamIcon.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { IconProps } from '@navi/web-ui/lib/icons/types';
|
||||
import { FC } from 'react';
|
||||
|
||||
const TeamIcon: FC<IconProps> = () => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
>
|
||||
<mask
|
||||
id="mask0_3268_31763"
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
y="0"
|
||||
width="24"
|
||||
height="24"
|
||||
>
|
||||
<rect width="24" height="24" fill="#D9D9D9" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_3268_31763)">
|
||||
<path
|
||||
d="M4 13.525L6.525 11L4 8.475L1.475 11L4 13.525ZM17.5 13L20 9L22.5 13H17.5ZM0 18V16.425C0 15.6917 0.371 15.104 1.113 14.662C1.85433 14.2207 2.81667 14 4 14C4.21667 14 4.425 14.004 4.625 14.012C4.825 14.0207 5.01667 14.0417 5.2 14.075C4.96667 14.4083 4.79167 14.7667 4.675 15.15C4.55833 15.5333 4.5 15.9417 4.5 16.375V18H0ZM6 18V16.375C6 15.2917 6.55433 14.4167 7.663 13.75C8.771 13.0833 10.2167 12.75 12 12.75C13.8 12.75 15.25 13.0833 16.35 13.75C17.45 14.4167 18 15.2917 18 16.375V18H6ZM19.5 18V16.375C19.5 15.9417 19.4457 15.5333 19.337 15.15C19.229 14.7667 19.0667 14.4083 18.85 14.075C19.0333 14.0417 19.221 14.0207 19.413 14.012C19.6043 14.004 19.8 14 20 14C21.2 14 22.1667 14.2207 22.9 14.662C23.6333 15.104 24 15.6917 24 16.425V18H19.5ZM12 12C11.1667 12 10.4583 11.7083 9.875 11.125C9.29167 10.5417 9 9.83333 9 9C9 8.15 9.29167 7.43733 9.875 6.862C10.4583 6.28733 11.1667 6 12 6C12.85 6 13.5623 6.28733 14.137 6.862C14.7123 7.43733 15 8.15 15 9C15 9.83333 14.7123 10.5417 14.137 11.125C13.5623 11.7083 12.85 12 12 12Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamIcon;
|
||||
@@ -10,6 +10,7 @@ import LeadIcon from '@navi/web-ui/lib/icons/LeadIcon';
|
||||
import { NavItemType } from '@navi/web-ui/lib/components/Navbar/types';
|
||||
import { Typography } from '@navi/web-ui/lib/primitives';
|
||||
import { toast } from '@navi/web-ui/lib/primitives/Toast';
|
||||
import TeamIcon from '../../assets/TeamIcon';
|
||||
import JiraDashboardIcon from '@src/assets/JiraDashboardIcon';
|
||||
import Dialog from '../Dialog';
|
||||
import styles from './LeftNav.module.scss';
|
||||
@@ -58,7 +59,7 @@ const LeftNav: React.FC<LeftNavProps> = ({ children }) => {
|
||||
itemType: 'simpleNavItem',
|
||||
label: 'Team',
|
||||
route: '/team',
|
||||
Icon: LeadIcon,
|
||||
Icon: TeamIcon,
|
||||
handleNavigation: () => navigate('/team'),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ const Incident = lazy(() => import('./Pages/Incidents/index'));
|
||||
const Team = lazy(() => import('./Pages/Team/index'));
|
||||
const Severity = lazy(() => import('./Pages/Severity/index'));
|
||||
const Tableau = lazy(() => import('./Pages/Tableau/index'));
|
||||
const TeamRevamp = lazy(() => import('./Pages/TeamRevamp/index'));
|
||||
const JiraDashboard = lazy(() => import('./Pages/JiraDashboard/index'));
|
||||
import { CustomRouteObject } from './types';
|
||||
|
||||
@@ -19,11 +20,6 @@ const routes: CustomRouteObject[] = [
|
||||
path: '/incident/:incidentId',
|
||||
element: <Incident />,
|
||||
},
|
||||
{
|
||||
id: 'TEAM',
|
||||
path: '/team',
|
||||
element: <Team />,
|
||||
},
|
||||
{
|
||||
id: 'SEVERITY',
|
||||
path: '/severity',
|
||||
@@ -34,6 +30,11 @@ const routes: CustomRouteObject[] = [
|
||||
path: '/metrics',
|
||||
element: <Tableau />,
|
||||
},
|
||||
{
|
||||
id: 'TEAM',
|
||||
path: '/team',
|
||||
element: <TeamRevamp />,
|
||||
},
|
||||
{
|
||||
id: 'JIRA_DASHBOARD',
|
||||
path: '/jiraDashboard',
|
||||
|
||||
10
src/services/hooks/isFirstRender.tsx
Normal file
10
src/services/hooks/isFirstRender.tsx
Normal 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;
|
||||
12
src/services/hooks/useAuth.tsx
Normal file
12
src/services/hooks/useAuth.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
const useAuthData = () => {
|
||||
const [userRole, setUserRole] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const userData = JSON.parse(localStorage.getItem('user-data') || '{}');
|
||||
setUserRole(userData?.roles || []);
|
||||
}, []);
|
||||
|
||||
return userRole;
|
||||
};
|
||||
export { useAuthData };
|
||||
119
src/slices/team1Slice.tsx
Normal file
119
src/slices/team1Slice.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import { PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
import {
|
||||
FETCH_SINGLE_TEAM_DATA,
|
||||
FETCH_TEAM_DATA,
|
||||
} from '@src/Pages/TeamRevamp/constants';
|
||||
import { TeamsData, TeamsDetail } from '@src/Pages/TeamRevamp/types';
|
||||
import { ApiService } from '@src/services/api';
|
||||
import { RootState } from '@src/store';
|
||||
|
||||
export interface TeamState {
|
||||
data: TeamsData;
|
||||
isPending: boolean;
|
||||
modalOpen: boolean;
|
||||
teamDetails: TeamsDetail;
|
||||
error: string;
|
||||
selectedTeam: TeamsDetail;
|
||||
newTeam: number | string;
|
||||
}
|
||||
|
||||
type TeamInitialState = {
|
||||
teams: TeamState;
|
||||
};
|
||||
|
||||
const initialState: TeamInitialState = {
|
||||
teams: {
|
||||
data: {},
|
||||
error: '',
|
||||
isPending: false,
|
||||
modalOpen: false,
|
||||
teamDetails: {},
|
||||
selectedTeam: {},
|
||||
newTeam: '',
|
||||
},
|
||||
};
|
||||
|
||||
export const fetchTeams = createAsyncThunk('team/fetchTeams', async () => {
|
||||
const endPoint = FETCH_TEAM_DATA;
|
||||
return await ApiService.get(endPoint);
|
||||
});
|
||||
|
||||
export const fetchTeamDetails = createAsyncThunk(
|
||||
'team/fetchTeamDetails',
|
||||
async (teamId: string) => {
|
||||
const endPoint = FETCH_SINGLE_TEAM_DATA(teamId);
|
||||
return await ApiService.get(endPoint);
|
||||
},
|
||||
);
|
||||
|
||||
const team1Slice = createSlice({
|
||||
name: 'team1',
|
||||
initialState,
|
||||
reducers: {
|
||||
setTeamData: (state, action: PayloadAction<TeamsData>) => {
|
||||
state.teams.data = action.payload;
|
||||
},
|
||||
setModalOpen: (state, action: PayloadAction<boolean>) => {
|
||||
state.teams.modalOpen = action.payload;
|
||||
},
|
||||
setTeamDetails: (state, action: PayloadAction<TeamsDetail>) => {
|
||||
state.teams.teamDetails = action.payload;
|
||||
},
|
||||
setNewTeam: (state, action: PayloadAction<string>) => {
|
||||
state.teams.newTeam = action.payload;
|
||||
},
|
||||
setSelectedTeam: (state, action: PayloadAction<TeamsDetail>) => {
|
||||
state.teams.selectedTeam = action.payload;
|
||||
},
|
||||
},
|
||||
|
||||
extraReducers: builder => {
|
||||
builder.addCase(fetchTeams.fulfilled, (state, action) => {
|
||||
state.teams.data = action.payload.data.data;
|
||||
state.teams.isPending = false;
|
||||
state.teams.error = '';
|
||||
if (!state.teams.selectedTeam) {
|
||||
state.teams.selectedTeam = action.payload.data.data[0];
|
||||
}
|
||||
});
|
||||
builder.addCase(fetchTeams.rejected, (state, action) => {
|
||||
state.teams.error = action.error.message ?? 'Something went wrong';
|
||||
state.teams.data = {};
|
||||
state.teams.isPending = false;
|
||||
state.teams.selectedTeam = {};
|
||||
});
|
||||
builder.addCase(fetchTeams.pending, (state, action) => {
|
||||
state.teams.isPending = true;
|
||||
state.teams.data = {};
|
||||
state.teams.error = '';
|
||||
state.teams.selectedTeam = {};
|
||||
});
|
||||
builder.addCase(fetchTeamDetails.fulfilled, (state, action) => {
|
||||
state.teams.teamDetails = action.payload.data.data;
|
||||
state.teams.isPending = false;
|
||||
state.teams.error = '';
|
||||
});
|
||||
builder.addCase(fetchTeamDetails.rejected, (state, action) => {
|
||||
state.teams.error = action.error.message ?? 'Something went wrong';
|
||||
state.teams.teamDetails = {};
|
||||
state.teams.isPending = false;
|
||||
});
|
||||
builder.addCase(fetchTeamDetails.pending, (state, action) => {
|
||||
state.teams.isPending = true;
|
||||
state.teams.teamDetails = {};
|
||||
state.teams.error = '';
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const selectSearchTeamData = (state: RootState) => state.team1.teams;
|
||||
|
||||
export const {
|
||||
setTeamData,
|
||||
setModalOpen,
|
||||
setTeamDetails,
|
||||
setSelectedTeam,
|
||||
setNewTeam,
|
||||
} = team1Slice.actions;
|
||||
export default team1Slice.reducer;
|
||||
@@ -2,12 +2,17 @@ import { configureStore } from '@reduxjs/toolkit';
|
||||
import teamReducer from '../slices/teamSlice';
|
||||
import severityReducer from '../slices/sevSlice';
|
||||
import dashboardReducer from '../slices/dashboardSlice';
|
||||
|
||||
import teamReducer1 from '../slices/team1Slice';
|
||||
|
||||
import jiraDashboardReducer from '../slices/jiraDashboardSlice';
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
team: teamReducer,
|
||||
severity: severityReducer,
|
||||
dashboard: dashboardReducer,
|
||||
team1: teamReducer1,
|
||||
jiraDashboard: jiraDashboardReducer,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user