Skip to content

Commit

Permalink
(FE) Add Retrieve Workspace List (#74)
Browse files Browse the repository at this point in the history
* Add retrieve the workspace list

* Add move to workspace handler

* Change font size
  • Loading branch information
devleejb authored Jan 19, 2024
1 parent 03c4fa5 commit 4922605
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 5 deletions.
22 changes: 22 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"randomcolor": "^0.6.2",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0",
"react-infinite-scroller": "^1.2.6",
"react-redux": "^9.0.4",
"react-resizable-layout": "^0.7.2",
"react-router-dom": "^6.21.1",
Expand All @@ -49,6 +50,7 @@
"@types/randomcolor": "^0.5.9",
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@types/react-infinite-scroller": "^1.2.5",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@vitejs/plugin-react": "^4.2.1",
Expand Down
30 changes: 27 additions & 3 deletions frontend/src/components/drawers/WorkspaceDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import MoreVertIcon from "@mui/icons-material/MoreVert";
import { useSelector } from "react-redux";
import { selectUser } from "../../store/userSlice";
import { MouseEventHandler, useState } from "react";
import ProfilePopover from "../common/ProfilePopover";
import ProfilePopover from "../popovers/ProfilePopover";
import { useParams } from "react-router-dom";
import { useGetWorkspaceQuery } from "../../hooks/api/workspace";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
import WorkspaceListPopover from "../popovers/WorkspaceListPopover";

const DRAWER_WIDTH = 240;

Expand All @@ -26,6 +28,9 @@ function WorkspaceDrawer() {
const userStore = useSelector(selectUser);
const { data: workspace } = useGetWorkspaceQuery(params.workspaceId);
const [profileAnchorEl, setProfileAnchorEl] = useState<(EventTarget & Element) | null>(null);
const [workspaceListAnchorEl, setWorkspaceListAnchorEl] = useState<
(EventTarget & Element) | null
>(null);

const handleOpenProfilePopover: MouseEventHandler = (event) => {
setProfileAnchorEl(event.currentTarget);
Expand All @@ -35,6 +40,14 @@ function WorkspaceDrawer() {
setProfileAnchorEl(null);
};

const handleOpenWorkspacePopover: MouseEventHandler = (event) => {
setWorkspaceListAnchorEl(event.currentTarget);
};

const handleCloseWorkspacePopover = () => {
setWorkspaceListAnchorEl(null);
};

return (
<Drawer
sx={{
Expand All @@ -50,7 +63,7 @@ function WorkspaceDrawer() {
open
>
<ListItem disablePadding>
<ListItemButton>
<ListItemButton onClick={handleOpenWorkspacePopover}>
<ListItemText
primary={workspace?.title}
primaryTypographyProps={{
Expand All @@ -60,11 +73,22 @@ function WorkspaceDrawer() {
/>
<ListItemSecondaryAction>
<IconButton>
<KeyboardArrowDownIcon />
{workspaceListAnchorEl ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
</IconButton>
</ListItemSecondaryAction>
</ListItemButton>
<WorkspaceListPopover
open={Boolean(workspaceListAnchorEl)}
anchorEl={workspaceListAnchorEl}
onClose={handleCloseWorkspacePopover}
width={DRAWER_WIDTH - 32}
/>
</ListItem>
<Divider />
<Box sx={{ mt: "auto" }}>
<Divider />
<ListItem disablePadding>
Expand Down
96 changes: 96 additions & 0 deletions frontend/src/components/popovers/WorkspaceListPopover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import {
Box,
CircularProgress,
ListItemSecondaryAction,
ListItemText,
MenuItem,
MenuList,
Popover,
PopoverProps,
} from "@mui/material";
import { useGetWorkspaceListQuery } from "../../hooks/api/workspace";
import InfiniteScroll from "react-infinite-scroller";
import { useMemo } from "react";
import { Workspace } from "../../hooks/api/types/workspace";
import { useNavigate, useParams } from "react-router-dom";
import CheckIcon from "@mui/icons-material/Check";

interface WorkspaceListPopoverProps extends PopoverProps {
width?: number;
}

function WorkspaceListPopover(props: WorkspaceListPopoverProps) {
const { width, ...popoverProps } = props;
const navigate = useNavigate();
const params = useParams();
const { data: workspacePageList, hasNextPage, fetchNextPage } = useGetWorkspaceListQuery();
const workspaceList = useMemo(() => {
return workspacePageList?.pages.reduce((prev: Array<Workspace>, page) => {
return prev.concat(page.workspaces);
}, [] as Array<Workspace>);
}, [workspacePageList?.pages]);

const handleMoveToSelectedWorkspace = (workspaceId: string) => {
if (params.workspaceId === workspaceId) return;

navigate(`/workspace/${workspaceId}`);
};

return (
<Popover
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
}}
{...popoverProps}
>
<Box
style={{
height: 300,
overflow: "auto",
}}
>
<InfiniteScroll
pageStart={0}
loadMore={() => fetchNextPage()}
hasMore={hasNextPage}
loader={
<Box className="loader" key={0}>
<CircularProgress size="sm" />
</Box>
}
useWindow={false}
>
<MenuList sx={{ width }}>
{workspaceList?.map((workspace) => (
<MenuItem
key={workspace.id}
onClick={() => handleMoveToSelectedWorkspace(workspace.id)}
>
<ListItemText
primaryTypographyProps={{
noWrap: true,
variant: "body2",
}}
>
{workspace.title}
</ListItemText>
{params.workspaceId === workspace.id && (
<ListItemSecondaryAction>
<CheckIcon fontSize="small" />
</ListItemSecondaryAction>
)}
</MenuItem>
))}
</MenuList>
</InfiniteScroll>
</Box>
</Popover>
);
}

export default WorkspaceListPopover;
5 changes: 5 additions & 0 deletions frontend/src/hooks/api/types/workspace.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ export interface Workspace {
}

export class GetWorkspaceResponse extends Workspace {}

export class GetWorkspaceListResponse {
cursor: string | null;
workspaces: Array<Workspace>;
}
27 changes: 25 additions & 2 deletions frontend/src/hooks/api/workspace.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { useQuery } from "@tanstack/react-query";
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
import axios from "axios";
import { GetWorkspaceResponse } from "./types/workspace";
import { GetWorkspaceListResponse, GetWorkspaceResponse } from "./types/workspace";

export const generateGetWorkspaceQueryKey = (workspaceId: string) => {
return ["workspaces", workspaceId];
};

export const generateGetWorkspaceListQueryKey = () => {
return ["workspaces"];
};

export const useGetWorkspaceQuery = (workspaceId?: string) => {
const query = useQuery({
queryKey: generateGetWorkspaceQueryKey(workspaceId || ""),
Expand All @@ -21,3 +25,22 @@ export const useGetWorkspaceQuery = (workspaceId?: string) => {

return query;
};

export const useGetWorkspaceListQuery = () => {
const query = useInfiniteQuery<GetWorkspaceListResponse>({
queryKey: generateGetWorkspaceListQueryKey(),
queryFn: async ({ pageParam }) => {
const res = await axios.get<GetWorkspaceListResponse>("/workspaces", {
params: {
cursor: pageParam,
},
});
return res.data;
},
initialPageParam: undefined,
getPreviousPageParam: (firstPage) => firstPage.cursor ?? undefined,
getNextPageParam: (lastPage) => lastPage.cursor ?? undefined,
});

return query;
};

0 comments on commit 4922605

Please sign in to comment.