All checks were successful
Publish Docker Image / Publish Docker Image (push) Successful in 39s
173 lines
6.0 KiB
TypeScript
Executable File
173 lines
6.0 KiB
TypeScript
Executable File
import { mdiDotsVertical } from '@mdi/js'
|
|
import Icon from '@mdi/react'
|
|
import { isAfter } from 'date-fns'
|
|
import parseHtml from 'react-html-parser'
|
|
import type {
|
|
DeleteProjectMutation,
|
|
DeleteProjectMutationVariables,
|
|
FindProjects,
|
|
} from 'types/graphql'
|
|
|
|
import { Link, routes } from '@redwoodjs/router'
|
|
import { useMutation } from '@redwoodjs/web'
|
|
import type { TypedDocumentNode } from '@redwoodjs/web'
|
|
import { toast } from '@redwoodjs/web/toast'
|
|
|
|
import { QUERY } from 'src/components/Project/ProjectsCell'
|
|
import { calculateLuminance } from 'src/lib/color'
|
|
import { timeTag, truncate } from 'src/lib/formatters'
|
|
import { batchDelete } from 'src/lib/tus'
|
|
|
|
const DELETE_PROJECT_MUTATION: TypedDocumentNode<
|
|
DeleteProjectMutation,
|
|
DeleteProjectMutationVariables
|
|
> = gql`
|
|
mutation DeleteProjectMutation($id: Int!) {
|
|
deleteProject(id: $id) {
|
|
id
|
|
}
|
|
}
|
|
`
|
|
|
|
const ProjectsList = ({ projects }: FindProjects) => {
|
|
const [deleteProject] = useMutation(DELETE_PROJECT_MUTATION, {
|
|
onCompleted: () => toast.success('Project deleted'),
|
|
onError: (error) => toast.error(error.message),
|
|
refetchQueries: [{ query: QUERY }],
|
|
awaitRefetchQueries: true,
|
|
})
|
|
|
|
const onDeleteClick = (id: DeleteProjectMutationVariables['id']) => {
|
|
if (confirm('Are you sure you want to delete project ' + id + '?')) {
|
|
batchDelete(projects.find((project) => project.id === id).images)
|
|
deleteProject({ variables: { id } })
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="w-full overflow-hidden overflow-x-auto rounded-xl bg-base-100">
|
|
<table className="table">
|
|
<thead className="bg-base-200 font-syne">
|
|
<tr>
|
|
<th>Title</th>
|
|
<th>Description</th>
|
|
<th>Date</th>
|
|
<th>Images</th>
|
|
<th>Tags</th>
|
|
<th>Links</th>
|
|
<th className="w-0"> </th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{projects
|
|
.slice()
|
|
.sort((a, b) =>
|
|
isAfter(new Date(b.date), new Date(a.date)) ? 1 : -1
|
|
)
|
|
.map((project) => {
|
|
const actionButtons = (
|
|
<>
|
|
<Link
|
|
to={routes.adminProject({ id: project.id })}
|
|
title={'Show project ' + project.id + ' detail'}
|
|
className="btn btn-xs uppercase"
|
|
>
|
|
Show
|
|
</Link>
|
|
<Link
|
|
to={routes.editProject({ id: project.id })}
|
|
title={'Edit project ' + project.id}
|
|
className="btn btn-primary btn-xs uppercase"
|
|
>
|
|
Edit
|
|
</Link>
|
|
<button
|
|
type="button"
|
|
title={'Delete projectt ' + project.id}
|
|
className="btn btn-error btn-xs uppercase"
|
|
onClick={() => onDeleteClick(project.id)}
|
|
>
|
|
Delete
|
|
</button>
|
|
</>
|
|
)
|
|
|
|
return (
|
|
<tr key={project.id}>
|
|
<td>{truncate(project.title)}</td>
|
|
<td className="max-w-72">
|
|
<article className="prose text-sm line-clamp-3">
|
|
{parseHtml(project.description)}
|
|
</article>
|
|
</td>
|
|
<td className="max-w-36">{timeTag(project.date)}</td>
|
|
<td>{`${project.images.length} ${project.images.length === 1 ? 'image' : 'images'}`}</td>
|
|
<td>
|
|
<div className="flex flex-wrap gap-2">
|
|
{project.tags.map((tag, i) => (
|
|
<div
|
|
key={i}
|
|
className="badge whitespace-nowrap"
|
|
style={{
|
|
backgroundColor: tag.color,
|
|
color:
|
|
calculateLuminance(tag.color) > 0.5
|
|
? 'black'
|
|
: 'white',
|
|
}}
|
|
>
|
|
{tag.tag}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div className="flex flex-wrap gap-2">
|
|
{project.links.map((link, i) => (
|
|
<a
|
|
href={link}
|
|
target="_blank"
|
|
className="badge badge-ghost text-nowrap"
|
|
key={i}
|
|
rel="noreferrer"
|
|
>
|
|
{link}
|
|
</a>
|
|
))}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<nav className="hidden justify-end space-x-2 md:flex">
|
|
{actionButtons}
|
|
</nav>
|
|
<div className="dropdown dropdown-end flex justify-end md:hidden">
|
|
<div
|
|
tabIndex={0}
|
|
role="button"
|
|
className="btn btn-square btn-ghost btn-sm lg:hidden"
|
|
>
|
|
<Icon
|
|
path={mdiDotsVertical}
|
|
className="text-base-content-100 size-6"
|
|
/>
|
|
</div>
|
|
<div
|
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
tabIndex={0}
|
|
className="menu dropdown-content z-10 -mt-1 mr-10 inline rounded-box bg-base-100 shadow-xl"
|
|
>
|
|
<nav className="w-46 space-x-2">{actionButtons}</nav>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
)
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default ProjectsList
|