Skip to content

Commit 6499d77

Browse files
committed
feat: sibling borrowings
1 parent f6eb80d commit 6499d77

File tree

10 files changed

+153
-16
lines changed

10 files changed

+153
-16
lines changed

app/(protected)/admin/borrows/[id]/page.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,27 @@ import { ButtonGroup, ButtonGroupSeparator } from '@/components/ui/button-group'
1111

1212
export default async function BorrowDetailsPage({
1313
params,
14+
searchParams,
1415
}: {
1516
params: Promise<{ id: string }>
17+
searchParams: Promise<{
18+
status?: 'lost' | 'active' | 'overdue' | 'returned'
19+
user_id?: string
20+
book_id?: string
21+
borrowed_at?: string
22+
due_at?: string
23+
returned_at?: string
24+
lost_at?: string
25+
}>
1626
}) {
1727
const { id } = await params
28+
const sp = await searchParams
1829

1930
const from = `/admin/borrows/${id}`
2031

2132
const headers = await Verify({ from })
2233

23-
const [borrowRes] = await Promise.all([getBorrow({ id }, { headers })])
34+
const [borrowRes] = await Promise.all([getBorrow({ id, ...sp }, { headers })])
2435

2536
if ('error' in borrowRes) {
2637
return <div>{JSON.stringify(borrowRes.message)}</div>

app/(protected)/admin/borrows/page.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,19 @@ export default async function Borrows({
186186
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
187187
{res.data.map((borrow, idx) => (
188188
<BorrowCardErrorBoundary key={borrow.id} idx={skip + idx + 1}>
189-
<ListCardBorrow borrow={borrow} idx={skip + idx + 1}>
189+
<ListCardBorrow
190+
borrow={borrow}
191+
idx={skip + idx + 1}
192+
searchParams={{
193+
book_id,
194+
borrowed_at,
195+
due_at,
196+
lost_at,
197+
returned_at,
198+
status,
199+
user_id,
200+
}}
201+
>
190202
<BtnReturnBook
191203
variant="outline"
192204
className="w-full"

app/(protected)/borrows/[id]/page.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,26 @@ import { DetailBorrow } from '@/components/borrows/DetailBorrow'
55

66
export default async function BorrowDetailsPage({
77
params,
8+
searchParams,
89
}: {
910
params: Promise<{ id: string }>
11+
searchParams: Promise<{
12+
status?: 'lost' | 'active' | 'overdue' | 'returned'
13+
book_id?: string
14+
borrowed_at?: string
15+
due_at?: string
16+
returned_at?: string
17+
lost_at?: string
18+
}>
1019
}) {
1120
const { id } = await params
21+
const sp = await searchParams
1222

1323
const from = `/borrows/${id}`
1424

1525
const headers = await Verify({ from })
1626

17-
const [borrowRes] = await Promise.all([getBorrow({ id }, { headers })])
27+
const [borrowRes] = await Promise.all([getBorrow({ id, ...sp }, { headers })])
1828

1929
if ('error' in borrowRes) {
2030
return <div>{JSON.stringify(borrowRes.message)}</div>

app/(protected)/borrows/page.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,14 @@ export default async function Borrows({
157157
key={borrow.id}
158158
borrow={borrow}
159159
idx={skip + idx + 1}
160+
searchParams={{
161+
book_id,
162+
borrowed_at,
163+
due_at,
164+
lost_at,
165+
returned_at,
166+
status,
167+
}}
160168
/>
161169
))}
162170
</div>

components/books/book-select-card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const SelectedBook = memo<{
2424
'shrink-0 relative left-0 transition-all not-first-of-type:-ml-12',
2525
'hover:transition-all hover:-translate-y-4 hover:transform-none',
2626
'peer peer-hover:left-12 peer-hover:transition-all',
27-
'[transform:perspective(800px)_rotateY(20deg)]',
27+
'transform-[perspective(800px)_rotateY(20deg)]',
2828
!disabled && 'hover:cursor-pointer',
2929
'group'
3030
)}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use client'
2+
3+
import { ChevronLeft, ChevronRight } from 'lucide-react'
4+
import { useSearchParams } from 'next/navigation'
5+
import { Button } from '../ui/button'
6+
import Link from 'next/link'
7+
import { Route } from 'next'
8+
9+
export const BtnBorrowSeq: React.FC<{ prevID?: string; nextID?: string }> = ({
10+
prevID,
11+
nextID,
12+
}) => {
13+
const currentFilters = useSearchParams()
14+
15+
const params = new URLSearchParams()
16+
currentFilters.forEach((value, key) => {
17+
params.append(key, value)
18+
})
19+
20+
return (
21+
<div className="space-x-2">
22+
{prevID && (
23+
<Link href={`./${prevID}?${params.toString()}` as Route<string>}>
24+
<Button variant="outline" size="sm">
25+
<ChevronLeft className="h-4 w-4 mr-1" />
26+
Previous
27+
</Button>
28+
</Link>
29+
)}
30+
{nextID && (
31+
<Link href={`./${nextID}?${params.toString()}` as Route<string>}>
32+
<Button variant="outline" size="sm">
33+
<ChevronRight className="h-4 w-4 mr-1" />
34+
Next
35+
</Button>
36+
</Link>
37+
)}
38+
</div>
39+
)
40+
}

components/borrows/DetailBorrow.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { ViewTransition } from 'react'
3131
import { Alert, AlertDescription, AlertTitle } from '../ui/alert'
3232
import { CardPrevBorrows } from './CardPrevBorrows'
3333
import { colorsToCssVars } from '@/lib/utils/color-utils'
34+
import { BtnBorrowSeq } from './BtnBorrowSeq'
3435

3536
export const DetailBorrow: React.FC<
3637
React.PropsWithChildren<{
@@ -45,7 +46,10 @@ export const DetailBorrow: React.FC<
4546
return (
4647
<>
4748
<div className="grid grid-cols-1 md:grid-cols-2 gap-4" style={cssVars}>
48-
<Card className="md:row-span-2 bg-[var(--color-light-vibrant)] dark:bg-[var(--color-dark-muted)]">
49+
<div className="place-self-center md:col-span-2 md:place-self-end">
50+
<BtnBorrowSeq prevID={borrow.prev_id} nextID={borrow.next_id} />
51+
</div>
52+
<Card className="md:row-span-2 bg-(--color-light-vibrant) dark:bg-(--color-dark-muted)">
4953
<CardHeader>
5054
<CardTitle>Book Information</CardTitle>
5155
</CardHeader>

components/borrows/ListCardBorrow.tsx

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,38 @@ import {
2020
import Link from 'next/link'
2121
import { Route } from 'next'
2222
import { ViewTransition } from 'react'
23+
import { GetBorrowQuery } from '@/lib/api/borrow'
2324

2425
export const ListCardBorrow: React.FC<
25-
React.PropsWithChildren<{ borrow: Borrow; idx: number }>
26-
> = ({ borrow, idx, children }) => {
26+
React.PropsWithChildren<{
27+
borrow: Borrow
28+
idx: number
29+
searchParams: Pick<
30+
GetBorrowQuery,
31+
| 'status'
32+
| 'user_id'
33+
| 'book_id'
34+
| 'borrowed_at'
35+
| 'due_at'
36+
| 'returned_at'
37+
| 'lost_at'
38+
>
39+
}>
40+
> = ({ borrow, idx, children, searchParams }) => {
2741
const status = getBorrowStatus(borrow)
2842
const isDue = isBorrowDue(borrow)
2943

44+
const href = (`./borrows/${borrow.id}?` +
45+
new URLSearchParams(
46+
Object.entries(searchParams).filter(([_, v]) => Boolean(v))
47+
).toString()) as Route<string>
48+
3049
return (
3150
<ViewTransition name={borrow.id} key={borrow.id}>
3251
<Card className={cn('relative', status === 'lost' && 'bg-destructive/5')}>
3352
<CardHeader>
3453
<Link
35-
// FIXME
36-
href={`./borrows/${borrow.id}` as Route}
54+
href={href}
3755
className="flex justify-between items-start min-h-20"
3856
>
3957
<div>

lib/api/borrow.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@ type GetListBorrowsQuery = QueryParams<
1111
library_id?: string
1212
status?: 'active' | 'overdue' | 'returned' | 'lost'
1313
user_id?: string
14+
returned_at?: string
15+
lost_at?: string
1416
}
1517
>
1618
type GetListBorrowsResponse = Promise<ResList<Borrow>>
1719

1820
export const getListBorrows = async (
19-
{
20-
status,
21-
...query
22-
}: GetListBorrowsQuery & { returned_at?: string; lost_at?: string },
21+
{ status, ...query }: GetListBorrowsQuery,
2322
init?: RequestInit
2423
): GetListBorrowsResponse => {
2524
const url = new URL(BORROW_URL)
@@ -50,19 +49,52 @@ export const getListBorrows = async (
5049
return response.json()
5150
}
5251

53-
type GetBorrowQuery = Pick<Borrow, 'id'>
52+
export type GetBorrowQuery = Pick<Borrow, 'id'> &
53+
Pick<
54+
GetListBorrowsQuery,
55+
| 'book_id'
56+
| 'subscription_id'
57+
| 'user_id'
58+
| 'library_id'
59+
| 'borrowed_at'
60+
| 'due_at'
61+
| 'returned_at'
62+
| 'lost_at'
63+
| 'is_active'
64+
| 'is_expired'
65+
| 'sort_by'
66+
| 'sort_in'
67+
| 'status'
68+
>
5469
type GetBorrowResponse = Promise<ResSingle<BorrowDetail>>
5570
export const getBorrow = async (
56-
query: GetBorrowQuery,
71+
{ id, status, ...query }: GetBorrowQuery,
5772
init?: RequestInit
5873
): GetBorrowResponse => {
59-
const url = new URL(`${BORROW_URL}/${query.id}`)
74+
const url = new URL(`${BORROW_URL}/${id}`)
6075
const headers = new Headers(init?.headers)
6176
headers.set('Content-Type', 'application/json')
6277
init = {
6378
...init,
6479
headers,
6580
}
81+
Object.entries(query).forEach(([key, value]) => {
82+
if (value) {
83+
url.searchParams.append(key, String(value))
84+
}
85+
})
86+
87+
if (status) {
88+
if (status === 'active') {
89+
url.searchParams.append('is_active', 'true')
90+
} else if (status === 'overdue') {
91+
url.searchParams.append('is_overdue', 'true')
92+
} else if (status === 'returned') {
93+
url.searchParams.append('is_returned', 'true')
94+
} else if (status === 'lost') {
95+
url.searchParams.append('is_lost', 'true')
96+
}
97+
}
6698
const response = await fetch(url.toString(), init)
6799
if (!response.ok) {
68100
const e = await response.json()

lib/types/borrow.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export type BorrowDetail = Omit<Borrow, 'book' | 'subscription' | 'staff'> & {
2323
book: Book
2424
subscription: SubscriptionDetail
2525
staff: Staff
26+
prev_id?: string
27+
next_id?: string
2628
}
2729

2830
export type Return = WithCommon<{

0 commit comments

Comments
 (0)