8000 feat: add "on this page" to empty table message when you're past page 1 by presleyp · Pull Request #4886 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

feat: add "on this page" to empty table message when you're past page 1 #4886

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Nov 8, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
UsersPage
  • Loading branch information
presleyp committed Nov 3, 2022
commit 9f1bd8fae2338ad09d198a4339dd0b7beb0c91db
2 changes: 1 addition & 1 deletion site/src/components/PaginationWidget/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export const createPaginationRef = (
return spawn(paginationMachine.withContext(context))
}

export const isNonInitialPage = (searchParams: URLSearchParams): boolean => {
export const nonInitialPage = (searchParams: URLSearchParams): boolean => {
const page = searchParams.get("page")
const numberPage = page ? Number(page) : 1
return numberPage > 1
Expand Down
3 changes: 3 additions & 0 deletions site/src/components/UsersTable/UsersTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface UsersTableProps {
user: TypesGen.User,
roles: TypesGen.Role["name"][],
) => void
isNonInitialPage: boolean
}

export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
Expand All @@ -46,6 +47,7 @@ export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
isUpdatingUserRoles,
canEditUsers,
isLoading,
isNonInitialPage,
}) => {
return (
<TableContainer>
Expand Down Expand Up @@ -78,6 +80,7 @@ export const UsersTable: FC<React.PropsWithChildren<UsersTableProps>> = ({
onResetUserPassword={onResetUserPassword}
onSuspendUser={onSuspendUser}
onUpdateUserRoles={onUpdateUserRoles}
isNonInitialPage={isNonInitialPage}
/>
</TableBody>
</Table>
Expand Down
253 changes: 135 additions & 118 deletions site/src/components/UsersTable/UsersTableBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import Box from "@material-ui/core/Box"
import { makeStyles } from "@material-ui/core/styles"
import TableCell from "@material-ui/core/TableCell"
import TableRow from "@material-ui/core/TableRow"
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
import { LastUsed } from "components/LastUsed/LastUsed"
import { FC } from "react"
import { useTranslation } from "react-i18next"
import * as TypesGen from "../../api/typesGenerated"
import { combineClasses } from "../../util/combineClasses"
import { AvatarData } from "../AvatarData/AvatarData"
Expand All @@ -12,15 +14,6 @@ import { RoleSelect } from "../RoleSelect/RoleSelect"
import { TableLoader } from "../TableLoader/TableLoader"
import { TableRowMenu } from "../TableRowMenu/TableRowMenu"

export const Language = {
emptyMessage: "No users found",
suspendMenuItem: "Suspend",
deleteMenuItem: "Delete",
listWorkspacesMenuItem: "View workspaces",
activateMenuItem: "Activate",
resetPasswordMenuItem: "Reset password",
}

interface UsersTableBodyProps {
users?: TypesGen.User[]
roles?: TypesGen.AssignableRoles[]
Expand All @@ -36,6 +29,7 @@ interface UsersTableBodyProps {
user: TypesGen.User,
roles: TypesGen.Role["name"][],
) => void
isNonInitialPage: boolean
}

export const UsersTableBody: FC<
Expand All @@ -52,121 +46,144 @@ export const UsersTableBody: FC<
isUpdatingUserRoles,
canEditUsers,
isLoading,
isNonInitialPage,
}) => {
const styles = useStyles()

if (isLoading) {
return <TableLoader />
}

if (!users || users.length === 0) {
return (
<TableRow>
<TableCell colSpan={999}>
<Box p={4}>
<EmptyState message={Language.emptyMessage} />
</Box>
</TableCell>
</TableRow>
)
}
const { t } = useTranslation("usersPage")

return (
<>
{users.map((user) => {
// When the user has no role we want to show they are a Member
const fallbackRole: TypesGen.Role = {
name: "member",
display_name: "Member",
}
const userRoles = user.roles.length === 0 ? [fallbackRole] : user.roles
<ChooseOne>
<Cond condition={Boolean(isLoading)}>
<TableLoader />
</Cond>
<Cond condition={!users || users.length === 0}>
<ChooseOne>
<Cond condition={isNonInitialPage}>
<TableRow>
<TableCell colSpan={999}>
<Box p={4}>
<EmptyState message={t("emptyPageMessage")} />
</Box>
</TableCell>
</TableRow>
</Cond>
<Cond>
<TableRow>
<TableCell colSpan={999}>
<Box p={4}>
<EmptyState message={t("emptyMessage")} />
</Box>
</TableCell>
</TableRow>
</Cond>
</ChooseOne>
</Cond>
<Cond>
<>
{users &&
users.map((user) => {
// When the user has no role we want to show they are a Member
const fallbackRole: TypesGen.Role = {
name: "member",
display_name: "Member",
}
const userRoles =
user.roles.length === 0 ? [fallbackRole] : user.roles

return (
<TableRow key={user.id}>
<TableCell>
<AvatarData
title={user.username}
subtitle={user.email}
highlightTitle
avatar={
user.avatar_url ? (
<img
className={styles.avatar}
alt={`${user.username}'s Avatar`}
src={user.avatar_url}
return (
<TableRow key={user.id}>
<TableCell>
<AvatarData
title={user.username}
subtitle={user.email}
highlightTitle
avatar={
user.avatar_url ? (
<img
className={styles.avatar}
alt={`${user.username}'s Avatar`}
src={user.avatar_url}
/>
) : null
}
/>
) : null
}
/>
</TableCell>
<TableCell
className={combineClasses([
styles.status,
user.status === "suspended" ? styles.suspended : undefined,
])}
>
{user.status}
</TableCell>
<TableCell>
<LastUsed lastUsedAt={user.last_seen_at} />
</TableCell>
<TableCell>
{canEditUsers ? (
<RoleSelect
roles={roles ?? []}
selectedRoles={userRoles}
loading={isUpdatingUserRoles}
onChange={(roles) => {
// Remove the fallback role because it is only for the UI
roles = roles.filter((role) => role !== fallbackRole.name)
onUpdateUserRoles(user, roles)
}}
/>
) : (
<>{userRoles.map((role) => role.display_name).join(", ")}</>
)}
</TableCell>
{canEditUsers && (
<TableCell>
<TableRowMenu
data={user}
menuItems={
// Return either suspend or activate depending on status
(user.status === "active"
? [
{
label: Language.suspendMenuItem,
onClick: onSuspendUser,
},
]
: [
{
label: Language.activateMenuItem,
onClick: onActivateUser,
},
]
).concat(
{
label: Language.deleteMenuItem,
onClick: onDeleteUser,
},
{
label: Language.listWorkspacesMenuItem,
onClick: onListWorkspaces,
},
{
label: Language.resetPasswordMenuItem,
onClick: onResetUserPassword,
},
)
}
/>
</TableCell>
)}
</TableRow>
)
})}
</>
</TableCell>
<TableCell
className={combineClasses([
styles.status,
user.status === "suspended"
? styles.suspended
: undefined,
])}
>
{user.status}
</TableCell>
<TableCell>
<LastUsed lastUsedAt={user.last_seen_at} />
</TableCell>
<TableCell>
{canEditUsers ? (
<RoleSelect
roles={roles ?? []}
selectedRoles={userRoles}
loading={isUpdatingUserRoles}
onChange={(roles) => {
// Remove the fallback role because it is only for the UI
roles = roles.filter(
(role) => role !== fallbackRole.name,
)
onUpdateUserRoles(user, roles)
}}
/>
) : (
<>
{userRoles.map((role) => role.display_name).join(", ")}
</>
)}
</TableCell>
{canEditUsers && (
<TableCell>
<TableRowMenu
data={user}
menuItems={
// Return either suspend or activate depending on status
(user.status === "active"
? [
{
label: t("suspendMenuItem"),
onClick: onSuspendUser,
},
]
: [
{
label: t("activateMenuItem"),
onClick: onActivateUser,
},
]
).concat(
{
label: t("deleteMenuItem"),
onClick: onDeleteUser,
},
{
label: t("listWorkspacesMenuItem"),
onClick: onListWorkspaces,
},
{
label: t("resetPasswordMenuItem"),
onClick: onResetUserPassword,
},
)
}
/>
</TableCell>
)}
</TableRow>
)
})}
</>
</Cond>
</ChooseOne>
)
}

Expand Down
2 changes: 2 additions & 0 deletions site/src/i18n/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import workspacePage from "./workspacePage.json"
import agent from "./agent.json"
import buildPage from "./buildPage.json"
import workspacesPage from "./workspacesPage.json"
import usersPage from "./usersPage.json"

export const en = {
common,
Expand All @@ -18,4 +19,5 @@ export const en = {
agent,
buildPage,
workspacesPage,
usersPage,
}
9 changes: 9 additions & 0 deletions site/src/i18n/en/usersPage.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"emptyMessage": "No users found",
"emptyPageMessage": "No users found on this page",
"suspendMenuItem": "Suspend",
"deleteMenuItem": "Delete",
"listWorkspacesMenuItem": "View workspaces",
"activateMenuItem": "Activate",
"resetPasswordMenuItem": "Reset password"
}
4 changes: 2 additions & 2 deletions site/src/pages/AuditPage/AuditPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useMachine } from "@xstate/react"
import {
getPaginationContext,
isNonInitialPage,
nonInitialPage,
} from "components/PaginationWidget/utils"
import { FC } from "react"
import { Helmet } from "react-helmet-async"
Expand Down Expand Up @@ -41,7 +41,7 @@ const AuditPage: FC = () => {
auditSend("FILTER", { filter })
}}
paginationRef={paginationRef}
isNonInitialPage={isNonInitialPage(searchParams)}
isNonInitialPage={nonInitialPage(searchParams)}
/>
</>
)
Expand Down
6 changes: 5 additions & 1 deletion site/src/pages/UsersPage/UsersPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { useActor, useMachine } from "@xstate/react"
import { User } from "api/typesGenerated"
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog"
import { getPaginationContext } from "components/PaginationWidget/utils"
import {
getPaginationContext,
nonInitialPage,
} from "components/PaginationWidget/utils"
import { usePermissions } from "hooks/usePermissions"
import { FC, ReactNode, useContext, useEffect } from "react"
import { Helmet } from "react-helmet-async"
Expand Down Expand Up @@ -127,6 +130,7 @@ export const UsersPage: FC<{ children?: ReactNode }> = () => {
usersSend({ type: "UPDATE_FILTER", query })
}}
paginationRef={paginationRef}
isNonInitialPage={nonInitialPage(searchParams)}
/>

<DeleteDialog
Expand Down
Loading
0