diff --git a/web-server/pages/teams/index.tsx b/web-server/pages/teams/index.tsx
index 70d1a922c..5a8e86e2d 100644
--- a/web-server/pages/teams/index.tsx
+++ b/web-server/pages/teams/index.tsx
@@ -9,12 +9,13 @@ import { Integration } from '@/constants/integrations';
import { PageWrapper } from '@/content/PullRequests/PageWrapper';
import { useAuth } from '@/hooks/useAuth';
import { fetchTeams } from '@/slices/team';
-import { useDispatch } from '@/store';
+import { useDispatch, useSelector } from '@/store';
import { PageLayout } from '@/types/resources';
function Page() {
const dispatch = useDispatch();
- const { role, orgId } = useAuth();
+ const { orgId } = useAuth();
+ const teamsList = useSelector((state) => state.team.teams);
useEffect(() => {
if (!orgId) return;
@@ -37,8 +38,9 @@ function Page() {
showEvenIfNoTeamSelected
hideAllSelectors
>
-
-
+
+ {teamsList.length ? : }
+
);
}
diff --git a/web-server/src/components/Teams/CreateTeams.tsx b/web-server/src/components/Teams/CreateTeams.tsx
index ae6d94b6b..8be999dfc 100644
--- a/web-server/src/components/Teams/CreateTeams.tsx
+++ b/web-server/src/components/Teams/CreateTeams.tsx
@@ -19,23 +19,25 @@ import { BaseRepo } from '@/types/resources';
import { FlexBox } from '../FlexBox';
import { Line } from '../Text';
-export const CreateTeams = () => {
+type CRUDCallBacks = {
+ onSave?: AnyFunction;
+ onDiscard?: AnyFunction;
+};
+
+export const CreateTeams: FC = ({ onSave, onDiscard }) => {
return (
-
+
);
};
-export const TeamsCRUD = () => {
+export const TeamsCRUD: FC = ({ onSave, onDiscard }) => {
const { isPageLoading } = useTeamCRUD();
return (
<>
{isPageLoading ? (
-
-
- Loading...
-
+
) : (
{
p={2}
maxWidth={'900px'}
>
-
-
- Create a Team
-
- Create a team to generate metric insights
-
+
-
+
)}
>
);
};
+const Loader = () => {
+ return (
+
+
+ Loading...
+
+ );
+};
+
+const Heading = () => {
+ return (
+
+
+ Create a Team
+
+ Create a team to generate metric insights
+
+ );
+};
+
const TeamName = () => {
const {
teamName,
@@ -102,7 +119,7 @@ const TeamRepos = () => {
} = useTeamCRUD();
return (
-
+
Add Repositories
@@ -154,12 +171,16 @@ const TeamRepos = () => {
);
};
-const ActionTray = () => {
- const { onSave, isSaveLoading, teamName, selectedRepos } = useTeamCRUD();
+const ActionTray: FC = ({
+ onSave: onSaveCallBack,
+ onDiscard: onDiscardCallBack
+}) => {
+ const { onSave, isSaveLoading, teamName, selectedRepos, onDiscard } =
+ useTeamCRUD();
const { enqueueSnackbar } = useSnackbar();
return (
-
+
{
if (!teamName) {
@@ -179,7 +200,7 @@ const ActionTray = () => {
+
);
};
@@ -197,7 +227,7 @@ const ActionTray = () => {
const DisplayRepos = () => {
const { selectedRepos } = useTeamCRUD();
return (
-
+
{!!selectedRepos.length && }
{selectedRepos.map((repo) => (
diff --git a/web-server/src/components/Teams/useTeamsConfig.tsx b/web-server/src/components/Teams/useTeamsConfig.tsx
index 3266c2940..854a0e8bb 100644
--- a/web-server/src/components/Teams/useTeamsConfig.tsx
+++ b/web-server/src/components/Teams/useTeamsConfig.tsx
@@ -1,11 +1,5 @@
import { useSnackbar } from 'notistack';
-import {
- createContext,
- useContext,
- useEffect,
- SyntheticEvent,
- useCallback
-} from 'react';
+import { createContext, useContext, SyntheticEvent, useCallback } from 'react';
import { Integration } from '@/constants/integrations';
import { FetchState } from '@/constants/ui-states';
@@ -38,6 +32,7 @@ interface TeamsCRUDContextType {
isSaveLoading: boolean;
unselectRepo: (id: BaseRepo['id']) => void;
isPageLoading: boolean;
+ onDiscard: (callBack?: AnyFunction) => void;
}
const TeamsCRUDContext = createContext(
@@ -65,8 +60,8 @@ export const TeamsCRUDProvider: React.FC = ({ children }) => {
const isPageLoading = useSelector(
(s) => s.team.requests?.teams === FetchState.REQUEST
);
- useEffect(() => {
- dispatch(
+ const fetchTeamsAndRepos = useCallback(() => {
+ return dispatch(
fetchTeams({
org_id: orgId,
provider: Integration.GITHUB
@@ -160,12 +155,7 @@ export const TeamsCRUDProvider: React.FC = ({ children }) => {
variant: 'success',
autoHideDuration: 2000
});
- dispatch(
- fetchTeams({
- org_id: orgId,
- provider: Integration.GITHUB
- })
- );
+ fetchTeamsAndRepos();
callBack?.(res);
})
.finally(isSaveLoading.false);
@@ -173,6 +163,7 @@ export const TeamsCRUDProvider: React.FC = ({ children }) => {
[
dispatch,
enqueueSnackbar,
+ fetchTeamsAndRepos,
isSaveLoading.false,
isSaveLoading.true,
org.name,
@@ -182,6 +173,17 @@ export const TeamsCRUDProvider: React.FC = ({ children }) => {
]
);
+ const onDiscard = useCallback(
+ (callBack?: AnyFunction) => {
+ depFn(teamName.set, '');
+ depFn(selections.set, []);
+ depFn(teamRepoError.false);
+ depFn(teamNameError.false);
+ callBack?.();
+ },
+ [selections.set, teamName.set, teamRepoError.false, teamNameError.false]
+ );
+
const contextValue: TeamsCRUDContextType = {
teamName: teamName.value,
showTeamNameError: teamNameError.value,
@@ -198,7 +200,8 @@ export const TeamsCRUDProvider: React.FC = ({ children }) => {
onSave,
isSaveLoading: isSaveLoading.value,
unselectRepo,
- isPageLoading
+ isPageLoading,
+ onDiscard
};
return (
diff --git a/web-server/src/components/TeamsList.tsx b/web-server/src/components/TeamsList.tsx
index 2c5334cfb..a3747d493 100644
--- a/web-server/src/components/TeamsList.tsx
+++ b/web-server/src/components/TeamsList.tsx
@@ -1,14 +1,28 @@
-import { Delete, MoreVert } from '@mui/icons-material';
-import Edit from '@mui/icons-material/Edit';
-import { Button, Card, Divider, Menu, MenuItem } from '@mui/material';
+import { Add, Delete, Edit, MoreVert } from '@mui/icons-material';
+import {
+ Button,
+ Card,
+ Divider,
+ Menu,
+ MenuItem,
+ TextField
+} from '@mui/material';
+import { useSnackbar } from 'notistack';
import pluralize from 'pluralize';
-import { MouseEventHandler } from 'react';
+import { ascend } from 'ramda';
+import { FC, MouseEventHandler, useCallback, useMemo } from 'react';
+import { Integration } from '@/constants/integrations';
+import { ROUTES } from '@/constants/routes';
+import { useAuth } from '@/hooks/useAuth';
import { useBoolState, useEasyState } from '@/hooks/useEasyState';
-import { useSelector } from '@/store';
+import { deleteTeam, fetchTeams } from '@/slices/team';
+import { useDispatch, useSelector } from '@/store';
import { Team } from '@/types/api/teams';
+import { depFn } from '@/utils/fn';
import { FlexBox } from './FlexBox';
+import { CreateTeams } from './Teams/CreateTeams';
import { Line } from './Text';
type TeamCardProps = {
@@ -19,12 +33,84 @@ const VISIBLE_REPOS_COUNT = 3;
export const TeamsList = () => {
const teamsArray = useSelector((state) => state.team.teams);
+ const searchQuery = useEasyState('');
+ const teamsArrayFiltered = useMemo(() => {
+ if (!searchQuery.value) {
+ return teamsArray;
+ }
+ return teamsArray.filter((team) =>
+ team.name.toLowerCase().includes(searchQuery.value.toLowerCase())
+ );
+ }, [searchQuery.value, teamsArray]);
+
+ return (
+ <>
+
+ {!teamsArrayFiltered.length && teamsArray.length ? (
+
+ No teams found
+
+ ) : null}
+
+ {teamsArrayFiltered.map((team, index) => (
+
+ ))}
+
+ >
+ );
+};
+
+const SearchFilter: FC<{
+ searchQuery: string;
+ onChange: (value: string) => void;
+}> = ({ searchQuery, onChange }) => {
+ const showCreate = useBoolState(false);
+ const showCreateTeam = useCallback(() => {
+ depFn(showCreate.toggle);
+ }, [showCreate.toggle]);
return (
-
- {teamsArray.map((team, index) => (
-
- ))}
+
+
+
+ {
+ onChange(e.target.value);
+ }}
+ fullWidth
+ placeholder="Search for teams"
+ />
+
+
+
+
+
+
+
+
+
+
+ {showCreate.value && }
);
};
@@ -32,64 +118,86 @@ export const TeamsList = () => {
const TeamCard: React.FC = ({ team }) => {
const { name: teamName, id: teamId } = team;
const teamReposMap = useSelector((state) => state.team.teamReposMaps);
- const assignedReposToTeam = teamReposMap[teamId] ?? [];
+ const assignedReposToTeam = useMemo(
+ () => [...(teamReposMap[teamId] ?? [])]?.sort(ascend((item) => item.name)),
+ [teamId, teamReposMap]
+ );
const visibleReposName = assignedReposToTeam.slice(0, VISIBLE_REPOS_COUNT);
- return (
-
-
-
-
- {teamName}
-
-
-
-
-
+ const tooltipRepos = useMemo(
+ () =>
+ assignedReposToTeam
+ .slice(VISIBLE_REPOS_COUNT)
+ .map((item) => item.name)
+ .join(', '),
+ [assignedReposToTeam]
+ );
-
-
-
- {assignedReposToTeam.length}{' '}
- {pluralize('Repo', assignedReposToTeam.length)} Added
+ return (
+
+
+
+
+
+ {teamName}
- {Boolean(assignedReposToTeam.length) && (
- <>
-
-
-
- {visibleReposName.map(
- (r, idx) =>
- `${r.name} ${
- idx === visibleReposName.length - 1 ? '' : ', '
- }`
- )}
- {assignedReposToTeam.length > VISIBLE_REPOS_COUNT && (
-
- +{assignedReposToTeam.length - VISIBLE_REPOS_COUNT} more
-
- )}
-
-
- {
- // TODO: IMPLEMENT EDIT TEAM
- // disableCreation();
- // addPage({
- // page: {
- // title: `Editing team: ${team.name}`,
- // ui: 'team_edit',
- // props: { teamId: team.id }
- // }
- // });
- }}
- />
+
+
+
+
+ {assignedReposToTeam.length}{' '}
+ {pluralize('Repo', assignedReposToTeam.length)} Added
+
+
+ {Boolean(assignedReposToTeam.length) && (
+ <>
+
+
+
+ {visibleReposName.map((r) => r.name).join(', ')}{' '}
+ {assignedReposToTeam.length > VISIBLE_REPOS_COUNT && (
+ {tooltipRepos}
+ }
+ >
+
+ +{assignedReposToTeam.length - VISIBLE_REPOS_COUNT}{' '}
+ more
+
+
+ )}
+
-
- >
- )}
+ >
+ )}
+
+
+
+
+
+ {
+ // TODO: IMPLEMENT EDIT TEAM
+ // disableCreation();
+ // addPage({
+ // page: {
+ // title: `Editing team: ${team.name}`,
+ // ui: 'team_edit',
+ // props: { teamId: team.id }
+ // }
+ // });
+ }}
+ />
+
+
+
@@ -97,17 +205,59 @@ const TeamCard: React.FC = ({ team }) => {
};
const MoreOptions = ({ teamId }: { teamId: ID }) => {
+ const dispatch = useDispatch();
+ const { enqueueSnackbar } = useSnackbar();
+ const { orgId } = useAuth();
const anchorEl = useEasyState();
+ const loading = useBoolState(false);
+ const cancelMenu = useBoolState(false);
const handleOpenMenu: MouseEventHandler = (event) => {
anchorEl.set(event.currentTarget);
};
- const handleCloseMenu = () => {
- anchorEl.set(null);
- };
+ const handleCloseMenu = useCallback(() => {
+ depFn(anchorEl.set, null);
+ }, [anchorEl.set]);
- const cancelMenu = useBoolState(false);
+ const handleTeamDeletion = useCallback(
+ (teamId: ID) => {
+ depFn(loading.true);
+ dispatch(
+ deleteTeam({
+ org_id: orgId,
+ team_id: teamId
+ })
+ )
+ .then((res) => {
+ if (res.meta.requestStatus === 'fulfilled') {
+ enqueueSnackbar('Team deleted successfully', {
+ variant: 'success',
+ autoHideDuration: 2000
+ });
+ dispatch(
+ fetchTeams({ org_id: orgId, provider: Integration.GITHUB })
+ );
+ handleCloseMenu();
+ } else {
+ enqueueSnackbar('Failed to delete team', {
+ variant: 'error',
+ autoHideDuration: 2000
+ });
+ console.error('Failed to delete team', res.meta);
+ }
+ })
+ .finally(loading.false);
+ },
+ [
+ dispatch,
+ enqueueSnackbar,
+ handleCloseMenu,
+ loading.false,
+ loading.true,
+ orgId
+ ]
+ );
return (
<>
@@ -167,8 +317,7 @@ const MoreOptions = ({ teamId }: { teamId: ID }) => {
size="small"
variant="contained"
onClick={() => {
- // TODO: IMPLEMENT DELETE TEAM
- // handleTeamDeletion(teamId);
+ handleTeamDeletion(teamId);
}}
>
Delete
diff --git a/web-server/src/slices/team.ts b/web-server/src/slices/team.ts
index c15fcbc43..ef515320e 100644
--- a/web-server/src/slices/team.ts
+++ b/web-server/src/slices/team.ts
@@ -252,7 +252,10 @@ export const deleteTeam = createAsyncThunk(
return await handleApi(
`/resources/orgs/${params.org_id}/teams/v2`,
{
- method: 'DELETE'
+ method: 'DELETE',
+ data: {
+ id: params.team_id
+ }
}
);
}