From 06573d90443963db509b64c40991672923628552 Mon Sep 17 00:00:00 2001 From: Yehoshua Sandler Date: Mon, 21 Apr 2025 12:39:56 -0500 Subject: [PATCH] feat: requestHold feature --- .../(frontend)/books/[bookId]/page.client.tsx | 60 ++++++++++-- src/app/(frontend)/page.tsx | 8 +- src/app/(frontend)/serverCalls/requestHold.ts | 24 +++++ src/collections/Checkouts/Checkouts.ts | 34 +++++++ src/collections/Checkouts/HoldRequests.ts | 79 +++++++++++++++ src/collections/Copies/Copies.ts | 20 ++++ src/collections/Repositories/Repositories.ts | 23 ++++- src/payload-types.ts | 95 +++++++++++++++++++ src/payload.config.ts | 4 +- 9 files changed, 336 insertions(+), 11 deletions(-) create mode 100644 src/app/(frontend)/serverCalls/requestHold.ts create mode 100644 src/collections/Checkouts/Checkouts.ts create mode 100644 src/collections/Checkouts/HoldRequests.ts diff --git a/src/app/(frontend)/books/[bookId]/page.client.tsx b/src/app/(frontend)/books/[bookId]/page.client.tsx index 5b1f4a1..8327ec0 100644 --- a/src/app/(frontend)/books/[bookId]/page.client.tsx +++ b/src/app/(frontend)/books/[bookId]/page.client.tsx @@ -4,18 +4,31 @@ import { Book, Genre, Repository } from '@/payload-types' import { Combobox, ComboboxLabel, ComboboxOption, ComboboxDescription } from '@/components/combobox' import { Field, Label } from '@/components/fieldset' -import { PaginatedDocs } from 'payload' +import type { PaginatedDocs } from 'payload' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Loader2 } from 'lucide-react' import { RichText } from '@/components/RichText' +import { useMemo, useState } from 'react' +import requestHold from '../../serverCalls/requestHold' type DropDownProps = { currentRepository: Repository repositories: Repository[] + isDisabled: boolean + isRequesting: boolean + onClickRequest: () => void + onChange: (repo: Repository | null) => void } -function RepoDropdown({ currentRepository, repositories }: DropDownProps) { +function RepoDropdown({ + currentRepository, + repositories, + isRequesting, + isDisabled, + onClickRequest, + onChange, +}: DropDownProps) { return ( @@ -25,7 +38,8 @@ function RepoDropdown({ currentRepository, repositories }: DropDownProps) { options={repositories} displayValue={(repo) => repo?.name} defaultValue={currentRepository} - className="w" + className="" + onChange={(repo) => onChange(repo)} > {(repo) => ( @@ -34,8 +48,12 @@ function RepoDropdown({ currentRepository, repositories }: DropDownProps) { )} - @@ -53,6 +71,29 @@ export default function BookByIdPageClient(props: Props) { const repos = repositories.docs + const [isRequestingCopy, setIsRequestingCopy] = useState(false) + const [selectedRepository, setSelectedRepository] = useState( + repos.length ? repos[0] : null, + ) + + const isRequestDisabled = useMemo(() => { + return isRequestingCopy + }, [isRequestingCopy]) + + const onClickRequest = async () => { + if (isRequestingCopy || !selectedRepository || !book) return + + setIsRequestingCopy(true) + const response = await requestHold({ + repositoryId: selectedRepository.id, + bookId: book.id, + }) + + console.log(response) + + setIsRequestingCopy(false) + } + return (
{/* Book */} @@ -94,7 +135,14 @@ export default function BookByIdPageClient(props: Props) {

{book.summary}

- + setSelectedRepository(repo)} + />

Genres

diff --git a/src/app/(frontend)/page.tsx b/src/app/(frontend)/page.tsx index 0a6d62f..5f93747 100644 --- a/src/app/(frontend)/page.tsx +++ b/src/app/(frontend)/page.tsx @@ -38,12 +38,14 @@ export default async function HomePage() {
-

Get the help you need

+

+ Engage In Our Community Resources +

Welcome diff --git a/src/app/(frontend)/serverCalls/requestHold.ts b/src/app/(frontend)/serverCalls/requestHold.ts new file mode 100644 index 0000000..c8ce006 --- /dev/null +++ b/src/app/(frontend)/serverCalls/requestHold.ts @@ -0,0 +1,24 @@ +'use server' + +import { getPayload } from "payload" +import configPromise from '@payload-config' + +type Props = { + bookId: number, + repositoryId: number, +} +const requestHold = async (props: Props) => { + const payload = await getPayload({ config: configPromise }) + + const requestHoldResponse = await payload.create({ + collection: 'holdRequests', + data: { + repository: props.repositoryId, + book: props.bookId, + }, + }) + + return requestHoldResponse +} + +export default requestHold diff --git a/src/collections/Checkouts/Checkouts.ts b/src/collections/Checkouts/Checkouts.ts new file mode 100644 index 0000000..8e44fbd --- /dev/null +++ b/src/collections/Checkouts/Checkouts.ts @@ -0,0 +1,34 @@ +import { CollectionConfig } from "payload"; + +const Checkouts: CollectionConfig = { + slug: 'checkouts', + fields: [ + { + name: 'fromHold', + type: 'relationship', + relationTo: 'holdRequests', + hasMany: false, + }, + { + name: 'user', + type: 'relationship', + relationTo: 'users', + hasMany: false, + }, + { + name: 'copy', + type: 'relationship', + relationTo: 'copies', + hasMany: false, + }, + { + name: 'book', + type: 'join', + collection: 'copies', + on: 'book', + hasMany: false, + } + ] +} + +export default Checkouts diff --git a/src/collections/Checkouts/HoldRequests.ts b/src/collections/Checkouts/HoldRequests.ts new file mode 100644 index 0000000..3c2f56a --- /dev/null +++ b/src/collections/Checkouts/HoldRequests.ts @@ -0,0 +1,79 @@ +import { CollectionConfig } from "payload"; + +const HoldRequests: CollectionConfig = { + slug: 'holdRequests', + fields: [ + { + name: 'copy', + type: 'relationship', + relationTo: 'copies', + filterOptions: ({ data }) => { + return { + book: { + equals: data.book + }, + } + } + }, + { + name: 'book', + type: 'relationship', + relationTo: 'books', + required: true, + }, + { + name: 'repository', + type: 'relationship', + relationTo: 'repositories', + }, + { + name: 'userRequested', + type: 'relationship', + relationTo: 'users', + required: true, + }, + { + name: 'dateRequested', + type: 'date', + }, + { + name: 'isHolding', + type: 'checkbox', + hooks: { + beforeValidate: [({ data, originalDoc }) => { + if (data?.isHolding && !data.copy) return originalDoc + }] + } + }, + { + name: 'holdingUntilDate', + type: 'date', + }, + { + name: 'isCheckedOut', + type: 'checkbox', + hooks: + { + beforeValidate: [({ data, originalDoc }) => { + if (data?.isCheckedOut && !data.copy) return originalDoc + + if (originalDoc.isCheckedOut && !data?.isCheckedOut) return originalDoc + }], + afterChange: [({ value, data, req }) => { + if (value) { + req.payload.create({ + collection: 'checkouts', + data: { + user: data?.userRequested, + copy: data?.copy, + } + }) + } + }] + } + + } + ], +} + +export default HoldRequests diff --git a/src/collections/Copies/Copies.ts b/src/collections/Copies/Copies.ts index 8ad3e86..76e437f 100644 --- a/src/collections/Copies/Copies.ts +++ b/src/collections/Copies/Copies.ts @@ -73,6 +73,26 @@ export const Copies: CollectionConfig = { { name: 'notes', type: 'richText' + }, + { + name: 'holdRequests', + type: 'join', + collection: 'holdRequests', + on: 'copy', + where: { + or: [ + { + isCheckedOut: { + equals: false + } + }, + { + isCheckedOut: { + equals: null + } + } + ] + } } ], hooks: { diff --git a/src/collections/Repositories/Repositories.ts b/src/collections/Repositories/Repositories.ts index 1bccf02..48feaa0 100644 --- a/src/collections/Repositories/Repositories.ts +++ b/src/collections/Repositories/Repositories.ts @@ -30,6 +30,27 @@ export const Repositories: CollectionConfig = { { name: 'dateOpenToPublic', type: 'date' - } + }, + { + name: 'holdRequests', + type: 'join', + collection: 'holdRequests', + on: 'repository', + defaultSort: 'dateRequested', + where: { + or: [ + { + isCheckedOut: { + equals: false + } + }, + { + isCheckedOut: { + equals: null + } + } + ] + } + }, ] } diff --git a/src/payload-types.ts b/src/payload-types.ts index 69cf907..dff600a 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -73,6 +73,8 @@ export interface Config { authors: Author; repositories: Repository; copies: Copy; + holdRequests: HoldRequest; + checkouts: Checkout; genre: Genre; pages: Page; 'payload-locked-documents': PayloadLockedDocument; @@ -86,6 +88,15 @@ export interface Config { authors: { books: 'books'; }; + repositories: { + holdRequests: 'holdRequests'; + }; + copies: { + holdRequests: 'holdRequests'; + }; + checkouts: { + book: 'copies'; + }; }; collectionsSelect: { users: UsersSelect | UsersSelect; @@ -94,6 +105,8 @@ export interface Config { authors: AuthorsSelect | AuthorsSelect; repositories: RepositoriesSelect | RepositoriesSelect; copies: CopiesSelect | CopiesSelect; + holdRequests: HoldRequestsSelect | HoldRequestsSelect; + checkouts: CheckoutsSelect | CheckoutsSelect; genre: GenreSelect | GenreSelect; pages: PagesSelect | PagesSelect; 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; @@ -265,6 +278,11 @@ export interface Copy { }; [k: string]: unknown; } | null; + holdRequests?: { + docs?: (number | HoldRequest)[]; + hasNextPage?: boolean; + totalDocs?: number; + }; updatedAt: string; createdAt: string; } @@ -281,6 +299,45 @@ export interface Repository { abbreviation: string; owner: (number | User)[]; dateOpenToPublic?: string | null; + holdRequests?: { + docs?: (number | HoldRequest)[]; + hasNextPage?: boolean; + totalDocs?: number; + }; + updatedAt: string; + createdAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "holdRequests". + */ +export interface HoldRequest { + id: number; + copy?: (number | null) | Copy; + book: number | Book; + repository?: (number | null) | Repository; + userRequested: number | User; + dateRequested?: string | null; + isHolding?: boolean | null; + holdingUntilDate?: string | null; + isCheckedOut?: boolean | null; + updatedAt: string; + createdAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "checkouts". + */ +export interface Checkout { + id: number; + fromHold?: (number | null) | HoldRequest; + user?: (number | null) | User; + copy?: (number | null) | Copy; + book?: { + docs?: (number | Copy)[]; + hasNextPage?: boolean; + totalDocs?: number; + }; updatedAt: string; createdAt: string; } @@ -337,6 +394,14 @@ export interface PayloadLockedDocument { relationTo: 'copies'; value: number | Copy; } | null) + | ({ + relationTo: 'holdRequests'; + value: number | HoldRequest; + } | null) + | ({ + relationTo: 'checkouts'; + value: number | Checkout; + } | null) | ({ relationTo: 'genre'; value: number | Genre; @@ -463,6 +528,7 @@ export interface RepositoriesSelect { abbreviation?: T; owner?: T; dateOpenToPublic?: T; + holdRequests?: T; updatedAt?: T; createdAt?: T; } @@ -476,6 +542,35 @@ export interface CopiesSelect { book?: T; repository?: T; notes?: T; + holdRequests?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "holdRequests_select". + */ +export interface HoldRequestsSelect { + copy?: T; + book?: T; + repository?: T; + userRequested?: T; + dateRequested?: T; + isHolding?: T; + holdingUntilDate?: T; + isCheckedOut?: T; + updatedAt?: T; + createdAt?: T; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "checkouts_select". + */ +export interface CheckoutsSelect { + fromHold?: T; + user?: T; + copy?: T; + book?: T; updatedAt?: T; createdAt?: T; } diff --git a/src/payload.config.ts b/src/payload.config.ts index df5be9b..6af5ce5 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -15,6 +15,8 @@ import { Copies } from './collections/Copies/Copies' import { Genre } from './collections/Books/Genre' import { Header } from './globals/header/config' import { Pages } from './collections/Pages/Pages' +import HoldRequests from './collections/Checkouts/HoldRequests' +import Checkouts from './collections/Checkouts/Checkouts' const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) @@ -35,7 +37,7 @@ export default buildConfig({ }, }, globals: [Header], - collections: [Users, Media, Books, Authors, Repositories, Copies, Genre, Pages], + collections: [Users, Media, Books, Authors, Repositories, Copies, HoldRequests, Checkouts, Genre, Pages], editor: lexicalEditor(), secret: process.env.PAYLOAD_SECRET || '', typescript: {