@@ -7,6 +7,7 @@ export const schema = gql`
|
||||
mainImage: String
|
||||
createdAt: DateTime!
|
||||
updatedAt: DateTime!
|
||||
deleted: Boolean!
|
||||
user: User!
|
||||
userId: String!
|
||||
Comment: [Comment]!
|
||||
|
||||
@@ -50,7 +50,8 @@ const Routes = () => {
|
||||
<Route path="/u/{userName}/{partTitle}/ide" page={IdePartPage} name="ide" />
|
||||
|
||||
<Private unauthenticated="home" role="admin">
|
||||
<Route path="/users" page={UsersPage} name="users" />
|
||||
<Route path="/admin/users" page={UsersPage} name="users" />
|
||||
<Route path="/admin/parts" page={AdminPartsPage} name="parts" />
|
||||
</Private>
|
||||
</Router>
|
||||
)
|
||||
|
||||
128
web/src/components/AdminParts/AdminParts.js
Normal file
128
web/src/components/AdminParts/AdminParts.js
Normal file
@@ -0,0 +1,128 @@
|
||||
import { useMutation, useFlash } from '@redwoodjs/web'
|
||||
import { Link, routes } from '@redwoodjs/router'
|
||||
|
||||
import { QUERY } from 'src/components/AdminPartsCell'
|
||||
|
||||
const DELETE_PART_MUTATION = gql`
|
||||
mutation DeletePartMutation($id: String!) {
|
||||
deletePart(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const MAX_STRING_LENGTH = 150
|
||||
|
||||
const truncate = (text) => {
|
||||
let output = text
|
||||
if (text && text.length > MAX_STRING_LENGTH) {
|
||||
output = output.substring(0, MAX_STRING_LENGTH) + '...'
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
const jsonTruncate = (obj) => {
|
||||
return truncate(JSON.stringify(obj, null, 2))
|
||||
}
|
||||
|
||||
const timeTag = (datetime) => {
|
||||
return (
|
||||
<time dateTime={datetime} title={datetime}>
|
||||
{new Date(datetime).toUTCString()}
|
||||
</time>
|
||||
)
|
||||
}
|
||||
|
||||
const checkboxInputTag = (checked) => {
|
||||
return <input type="checkbox" checked={checked} disabled />
|
||||
}
|
||||
|
||||
const AdminParts = ({ parts }) => {
|
||||
const { addMessage } = useFlash()
|
||||
const [deletePart] = useMutation(DELETE_PART_MUTATION, {
|
||||
onCompleted: () => {
|
||||
addMessage('Part deleted.', { classes: 'rw-flash-success' })
|
||||
},
|
||||
// This refetches the query on the list page. Read more about other ways to
|
||||
// update the cache over here:
|
||||
// https://www.apollographql.com/docs/react/data/mutations/#making-all-other-cache-updates
|
||||
refetchQueries: [{ query: QUERY }],
|
||||
awaitRefetchQueries: true,
|
||||
})
|
||||
|
||||
const onDeleteClick = (id) => {
|
||||
if (confirm('Are you sure you want to delete part ' + id + '?')) {
|
||||
deletePart({ variables: { id } })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rw-segment rw-table-wrapper-responsive">
|
||||
<table className="rw-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Title</th>
|
||||
<th>Description</th>
|
||||
<th>Code</th>
|
||||
<th>Main image</th>
|
||||
<th>Created at</th>
|
||||
<th>Updated at</th>
|
||||
<th>User id</th>
|
||||
<th>Deleted</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{parts.map((part) => (
|
||||
<tr key={part.id}>
|
||||
<td>{truncate(part.id)}</td>
|
||||
<td>{truncate(part.title)}</td>
|
||||
<td>{truncate(part.description)}</td>
|
||||
<td>{truncate(part.code)}</td>
|
||||
<td>{truncate(part.mainImage)}</td>
|
||||
<td>{timeTag(part.createdAt)}</td>
|
||||
<td>{timeTag(part.updatedAt)}</td>
|
||||
<td>{truncate(part.userId)}</td>
|
||||
<td>{checkboxInputTag(part.deleted)}</td>
|
||||
<td>
|
||||
<nav className="rw-table-actions">
|
||||
<Link
|
||||
to={routes.part({
|
||||
userName: part?.user?.userName,
|
||||
partTitle: part?.title,
|
||||
})}
|
||||
title={'Show part ' + part.id + ' detail'}
|
||||
className="rw-button rw-button-small"
|
||||
>
|
||||
Show
|
||||
</Link>
|
||||
<Link
|
||||
to={routes.editPart({
|
||||
userName: part?.user?.userName,
|
||||
partTitle: part?.title,
|
||||
})}
|
||||
title={'Edit part ' + part.id}
|
||||
className="rw-button rw-button-small rw-button-blue"
|
||||
>
|
||||
Edit
|
||||
</Link>
|
||||
<a
|
||||
href="#"
|
||||
title={'Delete part ' + part.id}
|
||||
className="rw-button rw-button-small rw-button-red"
|
||||
onClick={() => onDeleteClick(part.id)}
|
||||
>
|
||||
Delete
|
||||
</a>
|
||||
</nav>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AdminParts
|
||||
39
web/src/components/AdminPartsCell/AdminPartsCell.js
Normal file
39
web/src/components/AdminPartsCell/AdminPartsCell.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Link, routes } from '@redwoodjs/router'
|
||||
|
||||
import AdminParts from 'src/components/AdminParts'
|
||||
|
||||
export const QUERY = gql`
|
||||
query PARTS {
|
||||
parts {
|
||||
id
|
||||
title
|
||||
description
|
||||
code
|
||||
mainImage
|
||||
createdAt
|
||||
updatedAt
|
||||
userId
|
||||
deleted
|
||||
user {
|
||||
userName
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const Loading = () => <div>Loading...</div>
|
||||
|
||||
export const Empty = () => {
|
||||
return (
|
||||
<div className="rw-text-center">
|
||||
{'No parts yet. '}
|
||||
<Link to={routes.newPart()} className="rw-link">
|
||||
{'Create one?'}
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Success = ({ parts }) => {
|
||||
return <AdminParts parts={parts} />
|
||||
}
|
||||
12
web/src/pages/AdminPartsPage/AdminPartsPage.js
Normal file
12
web/src/pages/AdminPartsPage/AdminPartsPage.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import MainLayout from 'src/layouts/MainLayout'
|
||||
import AdminPartsCell from 'src/components/AdminPartsCell'
|
||||
|
||||
const PartsPage = () => {
|
||||
return (
|
||||
<MainLayout>
|
||||
<AdminPartsCell />
|
||||
</MainLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default PartsPage
|
||||
Reference in New Issue
Block a user