- Notifications
You must be signed in to change notification settings - Fork928
refactor: Improve the load state for the list pages#1428
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
4cef295
c0824d5
e1e6d9d
40afc47
c76d68c
6b37868
b3bdc36
8524f1f
036d18f
f52bb68
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import Box from "@material-ui/core/Box" | ||
import CircularProgress from "@material-ui/core/CircularProgress" | ||
import { makeStyles } from "@material-ui/core/styles" | ||
import TableCell from "@material-ui/core/TableCell" | ||
import TableRow from "@material-ui/core/TableRow" | ||
import React from "react" | ||
export const TableLoader: React.FC = () => { | ||
const styles = useStyles() | ||
return ( | ||
<TableRow> | ||
<TableCell colSpan={999} className={styles.cell}> | ||
<Box p={4}> | ||
<CircularProgress size={26} /> | ||
</Box> | ||
</TableCell> | ||
</TableRow> | ||
) | ||
} | ||
const useStyles = makeStyles((theme) => ({ | ||
cell: { | ||
textAlign: "center", | ||
height: theme.spacing(20), | ||
}, | ||
})) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { ComponentMeta, Story } from "@storybook/react" | ||
import React from "react" | ||
import { MockOrganization, MockTemplate } from "../../testHelpers/entities" | ||
import { TemplatesTable, TemplatesTableProps } from "./TemplatesTable" | ||
export default { | ||
title: "components/TemplatesTable", | ||
component: TemplatesTable, | ||
} as ComponentMeta<typeof TemplatesTable> | ||
const Template: Story<TemplatesTableProps> = (args) => <TemplatesTable {...args} /> | ||
export const Example = Template.bind({}) | ||
Example.args = { | ||
templates: [MockTemplate], | ||
organizations: [MockOrganization], | ||
} | ||
export const Empty = Template.bind({}) | ||
Empty.args = { | ||
templates: [], | ||
organizations: [], | ||
} | ||
export const Loading = Template.bind({}) | ||
Loading.args = { | ||
templates: undefined, | ||
organizations: [], | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import Box from "@material-ui/core/Box" | ||
import Table from "@material-ui/core/Table" | ||
import TableBody from "@material-ui/core/TableBody" | ||
import TableCell from "@material-ui/core/TableCell" | ||
import TableHead from "@material-ui/core/TableHead" | ||
import TableRow from "@material-ui/core/TableRow" | ||
import React from "react" | ||
import { Link } from "react-router-dom" | ||
import * as TypesGen from "../../api/typesGenerated" | ||
import { CodeExample } from "../../components/CodeExample/CodeExample" | ||
import { EmptyState } from "../../components/EmptyState/EmptyState" | ||
import { TableHeaderRow } from "../../components/TableHeaders/TableHeaders" | ||
import { TableLoader } from "../../components/TableLoader/TableLoader" | ||
import { TableTitle } from "../../components/TableTitle/TableTitle" | ||
export const Language = { | ||
title: "Templates", | ||
tableTitle: "All templates", | ||
nameLabel: "Name", | ||
emptyMessage: "No templates have been created yet", | ||
emptyDescription: "Run the following command to get started:", | ||
totalLabel: "total", | ||
} | ||
export interface TemplatesTableProps { | ||
templates?: TypesGen.Template[] | ||
organizations?: TypesGen.Organization[] | ||
} | ||
export const TemplatesTable: React.FC<TemplatesTableProps> = ({ templates, organizations }) => { | ||
const isLoading = !templates || !organizations | ||
// Create a dictionary of organization ID -> organization Name | ||
// Needed to properly construct links to dive into a template | ||
const orgDictionary = | ||
organizations && | ||
organizations.reduce((acc: Record<string, string>, curr: TypesGen.Organization) => { | ||
return { | ||
...acc, | ||
[curr.id]: curr.name, | ||
} | ||
}, {}) | ||
return ( | ||
<Table> | ||
<TableHead> | ||
<TableTitle title={Language.tableTitle} /> | ||
<TableHeaderRow> | ||
<TableCell size="small">{Language.nameLabel}</TableCell> | ||
</TableHeaderRow> | ||
</TableHead> | ||
<TableBody> | ||
{isLoading && <TableLoader />} | ||
{templates && | ||
organizations && | ||
orgDictionary && | ||
templates.map((t) => ( | ||
<TableRow key={t.id}> | ||
<TableCell> | ||
<Link to={`/templates/${orgDictionary[t.organization_id]}/${t.name}`}>{t.name}</Link> | ||
</TableCell> | ||
</TableRow> | ||
))} | ||
{templates && templates.length === 0 && ( | ||
<TableRow> | ||
<TableCell colSpan={999}> | ||
<Box p={4}> | ||
<EmptyState | ||
message={Language.emptyMessage} | ||
description={Language.emptyDescription} | ||
cta={<CodeExample code="coder templates create" />} | ||
/> | ||
</Box> | ||
</TableCell> | ||
</TableRow> | ||
)} | ||
</TableBody> | ||
</Table> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -9,6 +9,7 @@ import * as TypesGen from "../../api/typesGenerated" | ||
import { EmptyState } from "../EmptyState/EmptyState" | ||
import { RoleSelect } from "../RoleSelect/RoleSelect" | ||
import { TableHeaderRow } from "../TableHeaders/TableHeaders" | ||
import { TableLoader } from "../TableLoader/TableLoader" | ||
import { TableRowMenu } from "../TableRowMenu/TableRowMenu" | ||
import { TableTitle } from "../TableTitle/TableTitle" | ||
import { UserCell } from "../UserCell/UserCell" | ||
@@ -24,12 +25,12 @@ export const Language = { | ||
} | ||
export interface UsersTableProps { | ||
users?: TypesGen.User[] | ||
roles?: TypesGen.Role[] | ||
isUpdatingUserRoles?: boolean | ||
onSuspendUser: (user: TypesGen.User) => void | ||
onResetUserPassword: (user: TypesGen.User) => void | ||
onUpdateUserRoles: (user: TypesGen.User, roles: TypesGen.Role["name"][]) => void | ||
} | ||
export const UsersTable: React.FC<UsersTableProps> = ({ | ||
@@ -40,6 +41,8 @@ export const UsersTable: React.FC<UsersTableProps> = ({ | ||
onUpdateUserRoles, | ||
isUpdatingUserRoles, | ||
}) => { | ||
const isLoading = !users || !roles | ||
return ( | ||
<Table> | ||
<TableHead> | ||
@@ -52,38 +55,41 @@ export const UsersTable: React.FC<UsersTableProps> = ({ | ||
</TableHeaderRow> | ||
</TableHead> | ||
<TableBody> | ||
{isLoading && <TableLoader />} | ||
{users && | ||
roles && | ||
users.map((u) => ( | ||
<TableRow key={u.id}> | ||
<TableCell> | ||
<UserCell Avatar={{ username: u.username }} primaryText={u.username} caption={u.email} />{" "} | ||
</TableCell> | ||
<TableCell> | ||
<RoleSelect | ||
roles={roles} | ||
selectedRoles={u.roles} | ||
loading={isUpdatingUserRoles} | ||
onChange={(roles) => onUpdateUserRoles(u, roles)} | ||
/> | ||
</TableCell> | ||
<TableCell> | ||
<TableRowMenu | ||
data={u} | ||
menuItems={[ | ||
{ | ||
label: Language.suspendMenuItem, | ||
onClick: onSuspendUser, | ||
}, | ||
{ | ||
label: Language.resetPasswordMenuItem, | ||
onClick: onResetUserPassword, | ||
}, | ||
]} | ||
/> | ||
</TableCell> | ||
</TableRow> | ||
))} | ||
{users && users.length === 0 && ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Maybe next time we're in here, we can break this file apart a bit. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Sure thing. | ||
<TableRow> | ||
<TableCell colSpan={999}> | ||
<Box p={4}> | ||
Uh oh!
There was an error while loading.Please reload this page.