summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-ui/src/components
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2022-09-15 15:38:06 +0200
committerFabian Mastenbroek <mail.fabianm@gmail.com>2022-09-20 16:07:06 +0200
commit7199e2c15838d78fedd3c6127beddf1656dbeae2 (patch)
tree8ded3dcd92a8d8c571c0f5c80e635ae4e527d1af /opendc-web/opendc-web-ui/src/components
parent24b857ae580fcbea441e7cb91bc7aba681fc6c8b (diff)
feat(web/ui): Redesign projects page
This change updates the design of the projects page to use a gallery overview.
Diffstat (limited to 'opendc-web/opendc-web-ui/src/components')
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/NewProject.js39
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/NewProject.module.scss26
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/ProjectCollection.js137
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js73
-rw-r--r--opendc-web/opendc-web-ui/src/components/util/NavItemLink.js12
6 files changed, 145 insertions, 144 deletions
diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js
index 285217e9..7c6d129c 100644
--- a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js
+++ b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js
@@ -6,7 +6,7 @@ import { filterPanel } from './FilterPanel.module.scss'
export const FILTERS = { SHOW_ALL: 'All Projects', SHOW_OWN: 'My Projects', SHOW_SHARED: 'Shared with me' }
const FilterPanel = ({ onSelect, activeFilter = 'SHOW_ALL' }) => (
- <ToggleGroup className={`${filterPanel} mb-2`}>
+ <ToggleGroup className={`${filterPanel} pf-u-mb-sm`}>
{Object.keys(FILTERS).map((filter) => (
<ToggleGroupItem
key={filter}
diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewProject.js b/opendc-web/opendc-web-ui/src/components/projects/NewProject.js
deleted file mode 100644
index bfa7c01a..00000000
--- a/opendc-web/opendc-web-ui/src/components/projects/NewProject.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import React, { useState } from 'react'
-import { Button } from '@patternfly/react-core'
-import { PlusIcon } from '@patternfly/react-icons'
-import { useNewProject } from '../../data/project'
-import { buttonContainer } from './NewProject.module.scss'
-import TextInputModal from '../util/modals/TextInputModal'
-
-/**
- * A container for creating a new project.
- */
-const NewProject = () => {
- const [isVisible, setVisible] = useState(false)
- const { mutate: addProject } = useNewProject()
-
- const onSubmit = (name) => {
- if (name) {
- addProject({ name })
- }
- setVisible(false)
- }
-
- return (
- <>
- <div className={buttonContainer}>
- <Button
- icon={<PlusIcon />}
- color="primary"
- className="pf-u-float-right"
- onClick={() => setVisible(true)}
- >
- New Project
- </Button>
- </div>
- <TextInputModal title="New Project" label="Project name" isOpen={isVisible} callback={onSubmit} />
- </>
- )
-}
-
-export default NewProject
diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewProject.module.scss b/opendc-web/opendc-web-ui/src/components/projects/NewProject.module.scss
deleted file mode 100644
index 5a0e74fc..00000000
--- a/opendc-web/opendc-web-ui/src/components/projects/NewProject.module.scss
+++ /dev/null
@@ -1,26 +0,0 @@
-/*!
- * Copyright (c) 2021 AtLarge Research
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-.buttonContainer {
- flex: 0 1 auto;
- padding: 20px 0;
-}
diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectCollection.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectCollection.js
new file mode 100644
index 00000000..70f02812
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectCollection.js
@@ -0,0 +1,137 @@
+import {
+ Gallery,
+ Bullseye,
+ EmptyState,
+ EmptyStateIcon,
+ Card,
+ CardTitle,
+ CardActions,
+ DropdownItem,
+ CardHeader,
+ Dropdown,
+ KebabToggle,
+ CardBody,
+ CardHeaderMain,
+ TextVariants,
+ Text,
+ TextContent,
+ Tooltip,
+ Button,
+ Label,
+} from '@patternfly/react-core'
+import { PlusIcon, FolderIcon, TrashIcon } from '@patternfly/react-icons'
+import PropTypes from 'prop-types'
+import React, { useReducer, useMemo } from 'react'
+import { Project, Status } from '../../shapes'
+import { parseAndFormatDateTime } from '../../util/date-time'
+import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP, AUTH_NAME_MAP } from '../../util/authorizations'
+import NavItemLink from '../util/NavItemLink'
+import TableEmptyState from '../util/TableEmptyState'
+
+function ProjectCard({ project, onDelete }) {
+ const [isKebabOpen, toggleKebab] = useReducer((t) => !t, false)
+ const { id, role, name, updatedAt } = project
+ const Icon = AUTH_ICON_MAP[role]
+
+ return (
+ <Card
+ isCompact
+ isRounded
+ isFlat
+ className="pf-u-min-height"
+ style={{ '--pf-u-min-height--MinHeight': '175px' }}
+ >
+ <CardHeader className="pf-u-flex-grow-1">
+ <CardHeaderMain className="pf-u-align-self-flex-start">
+ <FolderIcon />
+ </CardHeaderMain>
+ <CardActions>
+ <Tooltip content={AUTH_DESCRIPTION_MAP[role]}>
+ <Label icon={<Icon />}>{AUTH_NAME_MAP[role]}</Label>
+ </Tooltip>
+ <Dropdown
+ isPlain
+ position="right"
+ toggle={<KebabToggle className="pf-u-px-0" onToggle={toggleKebab} />}
+ isOpen={isKebabOpen}
+ dropdownItems={[
+ <DropdownItem
+ key="trash"
+ onClick={() => {
+ onDelete()
+ toggleKebab()
+ }}
+ position="right"
+ icon={<TrashIcon />}
+ >
+ Delete
+ </DropdownItem>,
+ ]}
+ />
+ </CardActions>
+ </CardHeader>
+ <CardTitle component={NavItemLink} className="pf-u-pb-0" href={`/projects/${id}`}>
+ {name}
+ </CardTitle>
+ <CardBody isFilled={false}>
+ <TextContent>
+ <Text component={TextVariants.small}>Last modified {parseAndFormatDateTime(updatedAt)}</Text>
+ </TextContent>
+ </CardBody>
+ </Card>
+ )
+}
+
+function ProjectCollection({ status, projects, onDelete, onCreate, isFiltering }) {
+ const sortedProjects = useMemo(() => {
+ const res = [...projects]
+ res.sort((a, b) => (new Date(a.updatedAt) < new Date(b.updatedAt) ? 1 : -1))
+ return res
+ }, [projects])
+
+ if (sortedProjects.length === 0) {
+ return (
+ <TableEmptyState
+ status={status}
+ isFiltering={isFiltering}
+ loadingTitle="Loading Projects"
+ emptyTitle="No projects"
+ emptyText="You have not created any projects yet. Create a new project to get started quickly."
+ emptyAction={
+ <Button icon={<PlusIcon />} onClick={onCreate}>
+ Create Project
+ </Button>
+ }
+ />
+ )
+ }
+
+ return (
+ <Gallery hasGutter aria-label="Available projects">
+ {sortedProjects.map((project) => (
+ <ProjectCard key={project.id} project={project} onDelete={() => onDelete(project)} />
+ ))}
+ <Card isCompact isFlat isRounded style={{ borderStyle: 'dotted' }}>
+ <Bullseye>
+ <EmptyState>
+ <Button isBlock variant="link" onClick={onCreate}>
+ <EmptyStateIcon icon={PlusIcon} />
+ <br />
+ Create Project
+ </Button>
+ </EmptyState>
+ </Bullseye>
+ </Card>
+ </Gallery>
+ )
+}
+
+ProjectCollection.propTypes = {
+ status: Status.isRequired,
+ isFiltering: PropTypes.bool,
+ projects: PropTypes.arrayOf(Project).isRequired,
+ onDelete: PropTypes.func,
+ onCreate: PropTypes.func,
+}
+
+export default ProjectCollection
diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js
deleted file mode 100644
index 6921578c..00000000
--- a/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import PropTypes from 'prop-types'
-import React from 'react'
-import Link from 'next/link'
-import { Project, Status } from '../../shapes'
-import { Table, TableBody, TableHeader } from '@patternfly/react-table'
-import { parseAndFormatDateTime } from '../../util/date-time'
-import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP } from '../../util/authorizations'
-import TableEmptyState from '../util/TableEmptyState'
-
-const ProjectTable = ({ status, projects, onDelete, isFiltering }) => {
- const columns = ['Project name', 'Last edited', 'Access Rights']
- const rows =
- projects.length > 0
- ? projects.map((project) => {
- const Icon = AUTH_ICON_MAP[project.role]
- return [
- {
- title: <Link href={`/projects/${project.id}`}>{project.name}</Link>,
- },
- parseAndFormatDateTime(project.updatedAt),
- {
- title: (
- <>
- <Icon className="pf-u-mr-md" key="auth" /> {AUTH_DESCRIPTION_MAP[project.role]}
- </>
- ),
- },
- ]
- })
- : [
- {
- heightAuto: true,
- cells: [
- {
- props: { colSpan: 3 },
- title: (
- <TableEmptyState
- status={status}
- loadingTitle="Loading Projects"
- isFiltering={isFiltering}
- />
- ),
- },
- ],
- },
- ]
-
- const actions =
- projects.length > 0
- ? [
- {
- title: 'Delete Project',
- onClick: (_, rowId) => onDelete(projects[rowId]),
- },
- ]
- : []
-
- return (
- <Table aria-label="Project List" variant="compact" cells={columns} rows={rows} actions={actions}>
- <TableHeader />
- <TableBody />
- </Table>
- )
-}
-
-ProjectTable.propTypes = {
- status: Status.isRequired,
- isFiltering: PropTypes.bool,
- projects: PropTypes.arrayOf(Project).isRequired,
- onDelete: PropTypes.func,
-}
-
-export default ProjectTable
diff --git a/opendc-web/opendc-web-ui/src/components/util/NavItemLink.js b/opendc-web/opendc-web-ui/src/components/util/NavItemLink.js
index c0d109bd..83301361 100644
--- a/opendc-web/opendc-web-ui/src/components/util/NavItemLink.js
+++ b/opendc-web/opendc-web-ui/src/components/util/NavItemLink.js
@@ -23,11 +23,13 @@
import Link from 'next/link'
import PropTypes from 'prop-types'
-const NavItemLink = ({ children, href, ...props }) => (
- <Link href={href}>
- <a {...props}>{children}</a>
- </Link>
-)
+function NavItemLink({ children, href, ...props }) {
+ return (
+ <Link href={href}>
+ <a {...props}>{children}</a>
+ </Link>
+ )
+}
NavItemLink.propTypes = {
children: PropTypes.node,