Skip to content

Commit

Permalink
added all teams page (#561)
Browse files Browse the repository at this point in the history
  • Loading branch information
EduardZaydler authored Nov 25, 2024
1 parent 3b9574c commit 5dcf704
Show file tree
Hide file tree
Showing 17 changed files with 351 additions and 16 deletions.
1 change: 1 addition & 0 deletions src/Components/Header/Components/AdminMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const AdminMenu: FC = () => {
</>
}
>
<LinkMenuItem link={getPageLink("allTeams")}>All Teams</LinkMenuItem>
<LinkMenuItem link={getPageLink("patterns")}>Patterns</LinkMenuItem>
<LinkMenuItem link={getPageLink("contacts")}>Contacts</LinkMenuItem>
<LinkMenuItem link={getPageLink("notifications")}>Notifier</LinkMenuItem>
Expand Down
4 changes: 3 additions & 1 deletion src/Components/ModalError/ModalError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ type FooterErrorProps = {
message?: string | null;
maxWidth?: string;
margin?: string | number;
padding?: string | number;
};

export default function ModalError({
message,
maxWidth,
margin,
padding,
}: FooterErrorProps): React.ReactElement | null {
return message ? (
<div className={cn("root")} style={{ margin }}>
<div className={cn("root")} style={{ margin, padding }}>
<div style={{ maxWidth }}>
<ErrorIcon /> {message}
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Teams/Confirm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function Confirm({
active = true,
}: ConfirmProps): ReactElement {
const [opened, setOpened] = useState(false);
const [error, setError] = useState<string>("");
const [error, setError] = useState("");

const handleConfirm = async () => {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Hovered, HoveredShow } from "../Hovered/Hovered";
import { Team } from "../../../Domain/Team";
import { Confirm } from "../Confirm";
import DeleteIcon from "@skbkontur/react-icons/Delete";
import { useFyllyDeleteTeam } from "../../../hooks/useFullyDeleteTeam";
import { useFullyDeleteTeam } from "../../../hooks/useFullyDeleteTeam";
import { fullyDeleteTeamConfirmText } from "../../../helpers/teamOperationsConfirmMessages";

interface IConfirmFullTeamDeleteionProps {
Expand All @@ -25,7 +25,7 @@ export const ConfirmFullTeamDeleteion: FC<IConfirmFullTeamDeleteionProps> = ({
isDeletingSubscriptions,
isDeletingUsers,
isDeletingTeam,
} = useFyllyDeleteTeam(teamId, !isConfirmOpened);
} = useFullyDeleteTeam(teamId, !isConfirmOpened);

const confirmMessage = fullyDeleteTeamConfirmText(
isFetchingData,
Expand Down
42 changes: 42 additions & 0 deletions src/Components/Teams/TeamCard/TeamCard.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
@import "~styles/variables.less";
@import "~styles/mixins.less";

.team-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
text-align: start;
color: #333;
position: relative;

.team-name {
overflow: hidden;
max-width: 100%;
font-size: 18px;
line-height: 24px;
text-overflow: ellipsis;
white-space: nowrap;
max-width: calc(100% - 12px);
}

.team-description {
overflow: hidden;
text-overflow: ellipsis;
max-height: 120px;
}

.team-id {
font-size: 14px;
color: #999;
}

.team-card-kebab {
position: absolute;
right: 4px;
}

.team-users {
margin-left: -4px;
}
}
126 changes: 126 additions & 0 deletions src/Components/Teams/TeamCard/TeamCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React, { FC, useState } from "react";
import TrashIcon from "@skbkontur/react-icons/Trash";
import EditIcon from "@skbkontur/react-icons/Edit";
import { Button, Kebab } from "@skbkontur/react-ui";
import { MenuItem } from "@skbkontur/react-ui/components/MenuItem";
import { useFullyDeleteTeam } from "../../../hooks/useFullyDeleteTeam";
import WarningIcon from "@skbkontur/react-icons/Warning";
import { fullyDeleteTeamConfirmText } from "../../../helpers/teamOperationsConfirmMessages";
import ModalError from "../../../Components/ModalError/ModalError";
import { Users } from "../../../Components/Teams/Users";
import { useModal } from "../../../hooks/useModal";
import { TeamEditor } from "../../../Components/Teams/TeamEditor/TeamEditor";
import RouterLink from "../../../Components/RouterLink/RouterLink";
import { getPageLink } from "../../../Domain/Global";
import { Markdown } from "../../../Components/Markdown/Markdown";
import { Team } from "../../../Domain/Team";
import { Flexbox } from "../../Flexbox/FlexBox";
import classNames from "classnames/bind";

import styles from "./TeamCard.less";

const cn = classNames.bind(styles);

interface ITeamCardProps {
team: Team;
isDeleting: boolean;
onOpenDelete: () => void;
onCloseDelete: () => void;
}

export const TeamCard: FC<ITeamCardProps> = ({ team, isDeleting, onOpenDelete, onCloseDelete }) => {
const { name, description, id } = team;
const [error, setError] = useState("");
const { isModalOpen, openModal, closeModal } = useModal();

const handleConfirm = async () => {
try {
await handleFullyDeleteTeam();
onCloseDelete();
} catch (error) {
setError(error);
}
};

const {
handleFullyDeleteTeam,
isFetchingData,
isDeletingContacts,
isDeletingSubscriptions,
isDeletingUsers,
isDeletingTeam,
} = useFullyDeleteTeam(id, !isDeleting);

const confirmMessage = fullyDeleteTeamConfirmText(
isFetchingData,
isDeletingContacts,
isDeletingSubscriptions,
isDeletingUsers,
isDeletingTeam,
name
);

const isLoading =
isFetchingData ||
isDeletingContacts ||
isDeletingSubscriptions ||
isDeletingUsers ||
isDeletingTeam;

return (
<>
<div className={cn("team-card")}>
<Flexbox gap={5}>
<Kebab className={cn("team-card-kebab")} size="large">
<MenuItem icon={<EditIcon />} onClick={openModal}>
Edit
</MenuItem>
<MenuItem icon={<TrashIcon />} onClick={onOpenDelete}>
Delete
</MenuItem>
</Kebab>
{isDeleting ? (
<>
<Flexbox gap={8} align="center">
<WarningIcon size={40} />
{confirmMessage +
" If you are not a member, add yourself before deleting."}
<ModalError padding={"10px 16px"} margin={0} message={error} />
<Flexbox direction="row" gap={8}>
<Button
loading={isLoading}
onClick={handleConfirm}
use={"primary"}
width={100}
>
Confirm
</Button>
<Button disabled={isLoading} onClick={onCloseDelete}>
Cancel
</Button>
</Flexbox>
</Flexbox>
</>
) : (
<>
<div className={cn("team-name")}>{name}</div>
<div className={cn("team-id")}>{`id: ${id}`}</div>
<div className={cn("team-description")}>
{description && <Markdown markdown={description} />}
</div>
<Flexbox justify="space-between" direction="row">
<div className={cn("team-users")}>
<Users team={team} />
</div>
<RouterLink to={getPageLink("teamSettings", team.id)}>
Team settings
</RouterLink>
</Flexbox>
</>
)}
</Flexbox>
</div>
{isModalOpen && <TeamEditor team={team} onClose={closeModal} />}
</>
);
};
6 changes: 3 additions & 3 deletions src/Components/Teams/TeamEditor/TeamEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import styles from "./TeamEditor.less";
const cn = classNames.bind(styles);

interface ITeamEditorProps {
team?: Team;
team?: Team | null;
onClose: () => void;
}

Expand All @@ -30,11 +30,11 @@ export const TeamEditor: FC<ITeamEditorProps> = ({ team, onClose }: ITeamEditorP
const [error, setError] = useState<string | null>(null);
const { handleUpdateTeam, isUpdatingTeam } = useUpdateTeam(
validationRef,
team,
name,
description,
setError,
onClose
onClose,
team
);
const { handleAddTeam, isAddingTeam } = useAddTeam(
validationRef,
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Teams/Users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function Users({ team }: UsersProps): ReactElement {
onCollapse={handleCollapse}
>
{users?.length ? (
<Grid columns="20px 240px" gap="8px" margin="8px 0 0 8px">
<Grid columns="20px 110px" gap="8px" margin="8px 0 0 8px">
{users.map((userName) => (
<Fragment key={userName}>
<Confirm
Expand Down
8 changes: 8 additions & 0 deletions src/Containers/AllTeamsContainer/AllTeamsContainer.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@import "~styles/variables.less";
@import "~styles/mixins.less";

.teams-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 16px;
}
108 changes: 108 additions & 0 deletions src/Containers/AllTeamsContainer/AllTeamsContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React, { useEffect, FC, useState } from "react";
import { Layout, LayoutContent, LayoutTitle } from "../../Components/Layout/Layout";
import { setDocumentTitle } from "../../helpers/setDocumentTitle";
import { useAppDispatch, useAppSelector } from "../../store/hooks";
import { UIState } from "../../store/selectors";
import { useGetAllTeamsQuery } from "../../services/TeamsApi";
import classNames from "classnames/bind";
import { Team } from "../../Domain/Team";
import { Flexbox } from "../../Components/Flexbox/FlexBox";
import { SearchInput } from "../../Components/TriggerInfo/Components/SearchInput/SearchInput";
import { useDebounce } from "../../hooks/useDebounce";
import { Paging } from "@skbkontur/react-ui/components/Paging";
import transformPageFromHumanToProgrammer from "../../logic/transformPageFromHumanToProgrammer";
import { Select } from "@skbkontur/react-ui/components/Select";
import { EmptyListText } from "../../Components/TriggerInfo/Components/EmptyListMessage/EmptyListText";
import { TeamCard } from "../../Components/Teams/TeamCard/TeamCard";

import styles from "./AllTeamsContainer.less";
import { setError } from "../../store/Reducers/UIReducer.slice";

const cn = classNames.bind(styles);

type SortDirection = "asc" | "desc";

const SORT_OPTIONS: Array<[SortDirection, string]> = [
["asc", "Name: A-Z"],
["desc", "Name: Z-A"],
];

const AllTeamsContainer: FC = () => {
const { error, isLoading } = useAppSelector(UIState);
const [deletingTeam, setDeletingTeam] = useState<Team | null>(null);
const [searchValue, setSearchValue] = useState("");
const [activePage, setActivePage] = useState(1);
const [sortDirection, setSortDirection] = useState<SortDirection>("asc");
const debouncedSearchMetric = useDebounce(searchValue, 500);
const dispatch = useAppDispatch();

const { data: teams } = useGetAllTeamsQuery({
page: transformPageFromHumanToProgrammer(activePage),
searchText: debouncedSearchMetric,
sort: sortDirection,
});

const pageCount = Math.ceil((teams?.total ?? 0) / (teams?.size ?? 1));

const handleSetSearchValue = (value: string) => {
dispatch(setError(null));
setSearchValue(value);
};

useEffect(() => {
setDocumentTitle("All Teams");
}, []);

return (
<Layout loading={isLoading} error={error}>
<LayoutContent>
<LayoutTitle>Teams</LayoutTitle>
<Flexbox gap={24}>
<Flexbox gap={16} direction="row">
<SearchInput
value={searchValue}
width={"100%"}
placeholder="Filter by team name or team id, regExp is supported"
onValueChange={handleSetSearchValue}
onClear={() => setSearchValue("")}
/>
<Select<SortDirection, string>
placeholder="Sort"
value={sortDirection}
renderItem={(_v, item) => item}
renderValue={(_v, item) => item}
onValueChange={setSortDirection}
items={SORT_OPTIONS}
/>
</Flexbox>
{teams?.list.length ? (
<div className={cn("teams-container")}>
{teams?.list.map((team) => (
<TeamCard
key={team.id}
team={team}
isDeleting={deletingTeam?.id === team.id}
onOpenDelete={() => setDeletingTeam(team)}
onCloseDelete={() => setDeletingTeam(null)}
/>
))}
</div>
) : (
<EmptyListText text={"There are no teams"} />
)}

<Paging
shouldBeVisibleWithLessThanTwoPages={false}
caption="Next page"
activePage={activePage}
pagesCount={pageCount}
onPageChange={setActivePage}
withoutNavigationHint
/>
</Flexbox>
</LayoutContent>
</Layout>
);
};

export default AllTeamsContainer;
2 changes: 2 additions & 0 deletions src/Domain/Global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const PagesPaths = {
teams: "/teams",
team: "/teams/:teamId?",
contacts: "/contacts",
allTeams: "/teams/all",
};

export const PagesLinks = {
Expand All @@ -29,6 +30,7 @@ export const PagesLinks = {
team: "/teams/%id%",
docs: "//moira.readthedocs.org/",
contacts: "/contacts",
allTeams: "/teams/all",
};

export type PagePath = keyof typeof PagesPaths;
Expand Down
Loading

0 comments on commit 5dcf704

Please sign in to comment.