diff --git a/app/api/tags/[id]/listings/route.ts b/app/api/tags/[id]/listings/route.ts new file mode 100644 index 00000000..b2ae52ad --- /dev/null +++ b/app/api/tags/[id]/listings/route.ts @@ -0,0 +1,28 @@ +import prisma from '../../../../../prisma/client' + +export async function PUT(request: Request, { params }) { + const id = params.id + const listingIds = await request.json() + + const listingIdsToConnect = listingIds.map((id) => ({ id })) + + try { + const tag = await prisma.tag.update({ + where: { + id: Number(id), + }, + data: { + listings: { + connect: listingIdsToConnect, + }, + }, + }) + + return Response.json({ data: tag }) + } catch (e) { + console.error(`[RW] Unable to add tag to listings - ${e}`) + return new Response(`Unable to add tag to listings - ${e}`, { + status: 500, + }) + } +} diff --git a/components/admin/sidebar/Sidebar.tsx b/components/admin/sidebar/Sidebar.tsx index ffccee71..4bb050c9 100644 --- a/components/admin/sidebar/Sidebar.tsx +++ b/components/admin/sidebar/Sidebar.tsx @@ -186,7 +186,7 @@ const SidebarContent = ({ onClose, ...rest }: SidebarProps) => { Like what you see? - + Consider making a donation to help us host and develop the Resilience Web platform 🙏🏼 diff --git a/components/admin/tags/header/Header.tsx b/components/admin/tags/header/Header.tsx index 89dea714..69b14d4e 100644 --- a/components/admin/tags/header/Header.tsx +++ b/components/admin/tags/header/Header.tsx @@ -9,7 +9,6 @@ import { NewTagDialog } from './tag-dialog' const Header = () => { const { isOpen, onOpen, onClose } = useDisclosure() const { mutate: createTag } = useCreateTag() - const { selectedWebId } = useAppContext() const handleSubmit = useCallback( diff --git a/components/admin/tags/list/List.tsx b/components/admin/tags/list/List.tsx index 2ac7fc93..965654dd 100644 --- a/components/admin/tags/list/List.tsx +++ b/components/admin/tags/list/List.tsx @@ -14,7 +14,10 @@ import { memo, useCallback, useState } from 'react' import useUpdateTag from '@hooks/tags/useUpdateTag' import useDeleteTag from '@hooks/tags/useDeleteTag' +import useAddTagToListings from '@hooks/tags/useAddTagToListings' +import useListings from '@hooks/listings/useListings' import { UpdateTagDialog } from '../header/tag-dialog' +import AddTagToListingsDialog from './add-to-listings-dialog' const columns = [ { @@ -28,33 +31,54 @@ const columns = [ ] const List = ({ tags }) => { - const { isOpen, onOpen, onClose } = useDisclosure() + const { + isOpen: isUpdateTagDialogOpen, + onOpen: onUpdateTagDialogOpen, + onClose: onUpdateTagDialogClose, + } = useDisclosure() + const { + isOpen: isAddToListingsDialogOpen, + onOpen: onAddToListingsDialogOpen, + onClose: onAddToListingsDialogClose, + } = useDisclosure() + const { listings, isPending: isLoadingListings } = useListings() const { mutate: updateTag } = useUpdateTag() const { mutate: deleteTag } = useDeleteTag() + const { mutate: addTagToListings } = useAddTagToListings() const [selectedTagId, setSelectedTagId] = useState(null) const selectedTag = tags.find((tag) => tag.id === selectedTagId) - const handleOpen = (tagId) => { + const handleOpenUpdateTagDialog = (tagId) => { setSelectedTagId(tagId) - onOpen() + onUpdateTagDialogOpen() } const handleSubmit = useCallback( (data) => { - onClose() + onUpdateTagDialogClose() updateTag({ ...data, id: selectedTagId, }) }, - [onClose, updateTag, selectedTagId], + [onUpdateTagDialogClose, updateTag, selectedTagId], ) const handleDelete = useCallback(() => { - onClose() + onUpdateTagDialogClose() deleteTag({ id: selectedTagId }) - }, [deleteTag, onClose, selectedTagId]) + }, [deleteTag, onUpdateTagDialogClose, selectedTagId]) + + const handleOpenAddTagToListingsDialog = (tagId) => { + setSelectedTagId(tagId) + onAddToListingsDialogOpen() + } + + const handleAddTagToListingsSubmit = (data) => { + onAddToListingsDialogClose() + addTagToListings({ tagId: selectedTagId, listingIds: data }) + } if (!tags) { return null @@ -64,7 +88,7 @@ const List = ({ tags }) => { <> - + {columns.map((column, index) => ( ) } @@ -98,7 +132,7 @@ const List = ({ tags }) => { + + + + ) +} diff --git a/components/admin/tags/list/add-to-listings-dialog/index.ts b/components/admin/tags/list/add-to-listings-dialog/index.ts new file mode 100644 index 00000000..8f10f278 --- /dev/null +++ b/components/admin/tags/list/add-to-listings-dialog/index.ts @@ -0,0 +1 @@ +export { default } from './AddTagToListingsDialog' diff --git a/hooks/listings/useUpdateListing.tsx b/hooks/listings/useUpdateListing.tsx index dc85d17a..de15bdaa 100644 --- a/hooks/listings/useUpdateListing.tsx +++ b/hooks/listings/useUpdateListing.tsx @@ -31,6 +31,12 @@ export default function useUpdateListing() { queryClient.invalidateQueries({ queryKey: ['listings', 'list', { webSlug }], }) + queryClient.invalidateQueries({ + queryKey: ['categories', { webSlug }], + }) + queryClient.invalidateQueries({ + queryKey: ['tags', { webSlug }], + }) }, }) } diff --git a/hooks/tags/useAddTagToListings.tsx b/hooks/tags/useAddTagToListings.tsx new file mode 100644 index 00000000..2bcb2e9f --- /dev/null +++ b/hooks/tags/useAddTagToListings.tsx @@ -0,0 +1,30 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { useAppContext } from '@store/hooks' + +async function addTagToListingsRequest({ tagId, listingIds }) { + const response = await fetch(`/api/tags/${tagId}/listings`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json;charset=utf-8', + }, + body: JSON.stringify(listingIds), + }) + + const data = await response.json() + const { tag } = data + return tag +} + +export default function useAddTagToListings() { + const queryClient = useQueryClient() + const { selectedWebSlug: webSlug } = useAppContext() + + return useMutation({ + mutationFn: addTagToListingsRequest, + onSettled: () => { + void queryClient.invalidateQueries({ + queryKey: ['tags', { webSlug }], + }) + }, + }) +}
@@ -84,6 +108,16 @@ const List = ({ tags }) => { return ( {cell.length} +