Skip to content

Commit f26fbd7

Browse files
committed
feat: borrow detail page
1 parent dd0fe59 commit f26fbd7

File tree

12 files changed

+460
-94
lines changed

12 files changed

+460
-94
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import {
2+
Breadcrumb,
3+
BreadcrumbItem,
4+
BreadcrumbLink,
5+
BreadcrumbList,
6+
BreadcrumbPage,
7+
BreadcrumbSeparator,
8+
} from '@/components/ui/breadcrumb'
9+
import Link from 'next/link'
10+
import { Verify } from '@/lib/firebase/firebase'
11+
import { getBorrow } from '@/lib/api/borrow'
12+
import { Badge } from '@/components/ui/badge'
13+
import { getBorrowStatus } from '@/lib/utils'
14+
import { CardBorrow } from '@/components/borrows/CardBorrow'
15+
import { CardBook } from '@/components/books/CardBook'
16+
import { Carduser } from '@/components/users/CardUser'
17+
import { Cardstaff } from '@/components/staffs/CardStaff'
18+
import { Cardsubscription } from '@/components/subscriptions/CardSubscription'
19+
import { CardMembership } from '@/components/memberships/CardMembership'
20+
import { BtnReturnBook } from '@/components/borrows/BtnReturnBorrow'
21+
22+
export default async function BorrowDetailsPage({
23+
params,
24+
}: {
25+
params: Promise<{ id: string }>
26+
}) {
27+
const { id } = await params
28+
29+
await Verify({ from: `/borrows/${id}` })
30+
31+
const [borrowRes] = await Promise.all([getBorrow({ id })])
32+
33+
if ('error' in borrowRes) {
34+
console.log({ libRes: borrowRes })
35+
return <div>{JSON.stringify(borrowRes.message)}</div>
36+
}
37+
38+
// const cookieStore = await cookies()
39+
// const sessionName = process.env.SESSION_COOKIE_NAME as string
40+
// const session = cookieStore.get(sessionName)
41+
42+
return (
43+
<div className="space-y-4">
44+
<h1 className="text-2xl font-semibold">{borrowRes.data.book.title}</h1>
45+
<div className="flex justify-between items-center">
46+
<Breadcrumb>
47+
<BreadcrumbList>
48+
<BreadcrumbItem>
49+
<Link href="/" passHref legacyBehavior>
50+
<BreadcrumbLink>Home</BreadcrumbLink>
51+
</Link>
52+
</BreadcrumbItem>
53+
<BreadcrumbSeparator />
54+
<BreadcrumbItem>
55+
<Link href="/borrows" passHref legacyBehavior>
56+
<BreadcrumbLink>Borrows</BreadcrumbLink>
57+
</Link>
58+
</BreadcrumbItem>
59+
<BreadcrumbSeparator />
60+
<BreadcrumbItem>
61+
<BreadcrumbPage>{borrowRes.data.book.title}</BreadcrumbPage>
62+
</BreadcrumbItem>
63+
</BreadcrumbList>
64+
</Breadcrumb>
65+
66+
<Badge
67+
variant={
68+
getBorrowStatus(borrowRes.data) === 'overdue'
69+
? 'destructive'
70+
: getBorrowStatus(borrowRes.data) === 'returned'
71+
? 'secondary'
72+
: 'default'
73+
}
74+
className="uppercase h-8 min-w-24 justify-center"
75+
>
76+
{getBorrowStatus(borrowRes.data)}
77+
</Badge>
78+
</div>
79+
80+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
81+
<CardBorrow borrow={borrowRes.data} />
82+
<CardBook book={borrowRes.data.book} />
83+
<Carduser user={borrowRes.data.subscription.user} />
84+
<Cardstaff staff={borrowRes.data.staff} />
85+
<Cardsubscription subscription={borrowRes.data.subscription} />
86+
<CardMembership membership={borrowRes.data.subscription.membership} />
87+
</div>
88+
{!borrowRes.data.returning && (
89+
<div className="bottom-0 sticky py-2">
90+
<BtnReturnBook
91+
variant="outline"
92+
className="w-full"
93+
borrow={borrowRes.data}
94+
/>
95+
</div>
96+
)}
97+
</div>
98+
)
99+
}

app/(protected)/borrows/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CardBorrow } from '@/components/borrows/CardBorrow'
1+
import { ListCardBorrow } from '@/components/borrows/ListCardBorrow'
22
import {
33
Breadcrumb,
44
BreadcrumbItem,
@@ -94,7 +94,7 @@ export default async function Borrows({
9494
</div>
9595
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
9696
{res.data.map((borrow) => (
97-
<CardBorrow key={borrow.id} borrow={borrow} />
97+
<ListCardBorrow key={borrow.id} borrow={borrow} />
9898
))}
9999
</div>
100100

components/books/CardBook.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Book } from '@/lib/types/book'
2+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
3+
4+
export const CardBook: React.FC<{ book: Book }> = ({ book }) => {
5+
return (
6+
<Card>
7+
<CardHeader>
8+
<CardTitle className="text-lg line-clamp-2">Book</CardTitle>
9+
</CardHeader>
10+
<CardContent>
11+
<dl className="grid gap-2">
12+
<div className="grid grid-cols-3">
13+
<dt className="font-medium">Title:</dt>
14+
<dd className="col-span-2">{book.title}</dd>
15+
</div>
16+
<div className="grid grid-cols-3">
17+
<dt className="font-medium">Author:</dt>
18+
<dd className="col-span-2">{book.author}</dd>
19+
</div>
20+
<div className="grid grid-cols-3">
21+
<dt className="font-medium">Code:</dt>
22+
<dd className="col-span-2">{book.code}</dd>
23+
</div>
24+
</dl>
25+
</CardContent>
26+
</Card>
27+
)
28+
}

components/borrows/CardBorrow.tsx

Lines changed: 54 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,65 @@
1-
import { BtnReturnBook } from '@/components/borrows/BtnReturnBorrow'
2-
import { Badge } from '@/components/ui/badge'
3-
import { Borrow } from '@/lib/types/borrow'
4-
import { formatDate } from '@/lib/utils'
5-
import {
6-
Calendar,
7-
CalendarClock,
8-
CalendarX,
9-
LibraryIcon,
10-
User,
11-
} from 'lucide-react'
1+
import { BorrowDetail } from '@/lib/types/borrow'
2+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
3+
import { formatDate, isBorrowDue } from '@/lib/utils'
4+
import { differenceInDays, formatDistanceToNowStrict } from 'date-fns'
125

13-
import {
14-
Card,
15-
CardContent,
16-
CardDescription,
17-
CardFooter,
18-
CardHeader,
19-
CardTitle,
20-
} from '@/components/ui/card'
21-
import clsx from 'clsx'
22-
23-
const checkIsDue = (borrow: Borrow) => {
24-
const now = new Date()
25-
const due = new Date(borrow.due_at)
26-
return now > due && !borrow.returning
27-
}
28-
29-
const getBorrowStatus = (borrow: Borrow) => {
30-
if (borrow.returning?.returned_at) return 'returned'
31-
32-
return checkIsDue(borrow) ? 'overdue' : 'active'
33-
}
6+
export const CardBorrow: React.FC<{ borrow: BorrowDetail }> = ({ borrow }) => {
7+
const overduedDays = differenceInDays(
8+
borrow.returning ? new Date(borrow.returning.returned_at) : new Date(),
9+
new Date(borrow.due_at)
10+
)
11+
const finePerDay = borrow.subscription.membership.fine_per_day ?? 0
3412

35-
export const CardBorrow: React.FC<{ borrow: Borrow }> = ({ borrow }) => {
36-
const isDue = checkIsDue(borrow)
13+
const fine = overduedDays * finePerDay
3714

3815
return (
39-
<Card
40-
key={borrow.id}
41-
className={clsx('relative', {
42-
'bg-destructive/5': isDue,
43-
})}
44-
>
16+
<Card>
4517
<CardHeader>
46-
<div className="flex justify-between items-start min-h-20">
47-
<div>
48-
<CardTitle className="text-lg line-clamp-2">
49-
<abbr title={borrow.book.title} className="no-underline">
50-
{borrow.book.title}
51-
</abbr>
52-
</CardTitle>
53-
<CardDescription>{borrow.book.code}</CardDescription>
54-
</div>
55-
<Badge
56-
variant={
57-
getBorrowStatus(borrow) === 'overdue'
58-
? 'destructive'
59-
: getBorrowStatus(borrow) === 'returned'
60-
? 'secondary'
61-
: 'default'
62-
}
63-
className="capitalize"
64-
>
65-
{getBorrowStatus(borrow)}
66-
</Badge>
67-
</div>
18+
<CardTitle className="text-lg line-clamp-2">Borrow</CardTitle>
6819
</CardHeader>
69-
<CardContent className="space-y-3">
70-
<div className="flex items-center gap-2 text-sm">
71-
<User className="h-4 w-4 text-muted-foreground" />
72-
<span>{borrow.subscription.user.name}</span>
73-
</div>
74-
<div className="flex items-center gap-2 text-sm">
75-
<LibraryIcon className="h-4 w-4 text-muted-foreground" />
76-
<span>{borrow.subscription.membership.library.name}</span>
77-
</div>
78-
<div className="flex items-center gap-2 text-sm">
79-
<Calendar className="h-4 w-4 text-muted-foreground" />
80-
<span>Borrowed: {formatDate(borrow.borrowed_at)}</span>
81-
</div>
82-
<div className="flex items-center gap-2 text-sm">
83-
{isDue ? (
84-
<CalendarX className="h-4 w-4 text-destructive" />
85-
) : (
86-
<CalendarClock className="h-4 w-4 text-muted-foreground" />
20+
<CardContent>
21+
<dl className="grid gap-2">
22+
<div className="grid grid-cols-3">
23+
<dt className="font-medium">Borrowed At:</dt>
24+
<dd className="col-span-2">
25+
{formatDate(borrow.borrowed_at)}
26+
{!borrow.returning &&
27+
` (${formatDistanceToNowStrict(new Date(borrow.borrowed_at), { addSuffix: true })})`}
28+
</dd>
29+
</div>
30+
<div className="grid grid-cols-3">
31+
<dt className="font-medium">Due At:</dt>
32+
<dd className="col-span-2">
33+
{formatDate(borrow.due_at)}
34+
{!borrow.returning &&
35+
` (${formatDistanceToNowStrict(new Date(borrow.due_at), { addSuffix: true })})`}
36+
</dd>
37+
</div>
38+
39+
{borrow.returning && (
40+
<>
41+
<div className="grid grid-cols-3">
42+
<dt className="font-medium">Returned At:</dt>
43+
<dd className="col-span-2">
44+
{formatDate(borrow.returning.returned_at)}
45+
</dd>
46+
</div>
47+
<div className="grid grid-cols-3">
48+
<dt className="font-medium">Fine Received:</dt>
49+
<dd className="col-span-2">
50+
{borrow.returning.fine ?? '-'} Pts
51+
</dd>
52+
</div>
53+
</>
54+
)}
55+
{isBorrowDue(borrow) && (
56+
<div className="grid grid-cols-3">
57+
<dt className="font-medium">Fine:</dt>
58+
<dd className="col-span-2">{fine ?? '-'} Pts</dd>
59+
</div>
8760
)}
88-
<span className={`${isDue ? 'text-destructive' : ''}`}>
89-
Due: {formatDate(borrow.due_at)}
90-
</span>
91-
</div>
61+
</dl>
9262
</CardContent>
93-
<CardFooter>
94-
<BtnReturnBook variant="outline" className="w-full" borrow={borrow}>
95-
Return Book
96-
</BtnReturnBook>
97-
</CardFooter>
9863
</Card>
9964
)
10065
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { BtnReturnBook } from '@/components/borrows/BtnReturnBorrow'
2+
import { Badge } from '@/components/ui/badge'
3+
import { Borrow } from '@/lib/types/borrow'
4+
import { isBorrowDue, formatDate, getBorrowStatus } from '@/lib/utils'
5+
import {
6+
Calendar,
7+
CalendarClock,
8+
CalendarX,
9+
LibraryIcon,
10+
User,
11+
} from 'lucide-react'
12+
13+
import {
14+
Card,
15+
CardContent,
16+
CardDescription,
17+
CardFooter,
18+
CardHeader,
19+
CardTitle,
20+
} from '@/components/ui/card'
21+
import clsx from 'clsx'
22+
import Link from 'next/link'
23+
24+
export const ListCardBorrow: React.FC<{ borrow: Borrow }> = ({ borrow }) => {
25+
const isDue = isBorrowDue(borrow)
26+
27+
return (
28+
<Card
29+
key={borrow.id}
30+
className={clsx('relative', {
31+
'bg-destructive/5': isDue,
32+
})}
33+
>
34+
<CardHeader>
35+
<Link
36+
href={`/borrows/${borrow.id}`}
37+
className="flex justify-between items-start min-h-20"
38+
>
39+
<div>
40+
<CardTitle className="text-lg line-clamp-2">
41+
<abbr title={borrow.book.title} className="no-underline">
42+
{borrow.book.title}
43+
</abbr>
44+
</CardTitle>
45+
<CardDescription>{borrow.book.code}</CardDescription>
46+
</div>
47+
<Badge
48+
variant={
49+
getBorrowStatus(borrow) === 'overdue'
50+
? 'destructive'
51+
: getBorrowStatus(borrow) === 'returned'
52+
? 'secondary'
53+
: 'default'
54+
}
55+
className="capitalize"
56+
>
57+
{getBorrowStatus(borrow)}
58+
</Badge>
59+
</Link>
60+
</CardHeader>
61+
<CardContent className="space-y-3">
62+
<div className="flex items-center gap-2 text-sm">
63+
<User className="h-4 w-4 text-muted-foreground" />
64+
<span>{borrow.subscription.user.name}</span>
65+
</div>
66+
<div className="flex items-center gap-2 text-sm">
67+
<LibraryIcon className="h-4 w-4 text-muted-foreground" />
68+
<span>{borrow.subscription.membership.library.name}</span>
69+
</div>
70+
<div className="flex items-center gap-2 text-sm">
71+
<Calendar className="h-4 w-4 text-muted-foreground" />
72+
<span>Borrowed: {formatDate(borrow.borrowed_at)}</span>
73+
</div>
74+
<div className="flex items-center gap-2 text-sm">
75+
{isDue ? (
76+
<CalendarX className="h-4 w-4 text-destructive" />
77+
) : (
78+
<CalendarClock className="h-4 w-4 text-muted-foreground" />
79+
)}
80+
<span className={`${isDue ? 'text-destructive' : ''}`}>
81+
Due: {formatDate(borrow.due_at)}
82+
</span>
83+
</div>
84+
</CardContent>
85+
<CardFooter>
86+
<BtnReturnBook variant="outline" className="w-full" borrow={borrow}>
87+
Return Book
88+
</BtnReturnBook>
89+
</CardFooter>
90+
</Card>
91+
)
92+
}

0 commit comments

Comments
 (0)