Skip to content

Commit 94757d0

Browse files
committed
feat: admin portal components
1 parent 29f6259 commit 94757d0

File tree

33 files changed

+1371
-593
lines changed

33 files changed

+1371
-593
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default async function BorrowDetailsPage({
1010
}) {
1111
const { id } = await params
1212

13-
await Verify({ from: `/borrows/${id}` })
13+
await Verify({ from: `/admin/borrows/${id}` })
1414

1515
const [borrowRes] = await Promise.all([getBorrow({ id })])
1616
if ('error' in borrowRes) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default async function BorrowDetailsPage({
99
}) {
1010
const { id } = await params
1111

12-
await Verify({ from: `/borrows/${id}` })
12+
await Verify({ from: `/admin/borrows/${id}` })
1313

1414
const [borrowRes] = await Promise.all([getBorrow({ id })])
1515

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,14 @@ export default async function BorrowDetailsLayout({
3737
<Breadcrumb>
3838
<BreadcrumbList>
3939
<BreadcrumbItem>
40-
<BreadcrumbLink href="/">Home</BreadcrumbLink>
40+
<BreadcrumbLink href="..">Home</BreadcrumbLink>
4141
</BreadcrumbItem>
4242
<BreadcrumbSeparator />
4343
<BreadcrumbItem>
44-
<BreadcrumbLink href="/borrows">Borrows</BreadcrumbLink>
44+
<BreadcrumbLink href=".">Borrows</BreadcrumbLink>
4545
</BreadcrumbItem>
4646
<BreadcrumbSeparator />
47-
<BreadcrumbItem>
48-
<BreadcrumbLink href={`/borrows/${id}`}>
49-
{borrowRes.data.book.title}
50-
</BreadcrumbLink>
51-
</BreadcrumbItem>
47+
<BreadcrumbItem>{borrowRes.data.book.title}</BreadcrumbItem>
5248
</BreadcrumbList>
5349
</Breadcrumb>
5450

Lines changed: 28 additions & 299 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,13 @@
1-
import Link from 'next/link'
21
import { IsLoggedIn, Verify } from '@/lib/firebase/firebase'
32
import { getBorrow, getListBorrows } from '@/lib/api/borrow'
4-
import { Badge } from '@/components/ui/badge'
5-
import {
6-
getSubscriptionStatus,
7-
isBorrowDue,
8-
isSubscriptionActive,
9-
} from '@/lib/utils'
10-
import { BtnReturnBook } from '@/components/borrows/BtnReturnBorrow'
11-
import Image from 'next/image'
12-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
13-
import {
14-
Book,
15-
Calendar,
16-
CalendarCheck,
17-
CalendarClock,
18-
CalendarX,
19-
Clock,
20-
CreditCard,
21-
Gavel,
22-
Library,
23-
Pen,
24-
Tally5,
25-
User,
26-
UserCog,
27-
} from 'lucide-react'
28-
import clsx from 'clsx'
29-
import { differenceInDays } from 'date-fns'
303
import { Borrow } from '@/lib/types/borrow'
4+
import { redirect, RedirectType } from 'next/navigation'
5+
import { DetailBorrow } from '@/components/borrows/DetailBorrow'
6+
import { BtnReturnBook } from '@/components/borrows/BtnReturnBorrow'
317
import { Button } from '@/components/ui/button'
328
import { BtnUndoReturn } from '@/components/borrows/BtnUndoReturn'
33-
import { redirect, RedirectType } from 'next/navigation'
34-
import { DateTime } from '@/components/common/DateTime'
35-
import { ThreeDBook } from '@/components/books/three-d-book'
9+
import Link from 'next/link'
10+
import { Pen } from 'lucide-react'
3611

3712
export default async function BorrowDetailsPage({
3813
params,
@@ -41,7 +16,7 @@ export default async function BorrowDetailsPage({
4116
}) {
4217
const { id } = await params
4318

44-
const from = `/borrows/${id}`
19+
const from = `/admin/borrows/${id}`
4520

4621
const headers = await Verify({ from })
4722

@@ -51,8 +26,6 @@ export default async function BorrowDetailsPage({
5126
return <div>{JSON.stringify(borrowRes.message)}</div>
5227
}
5328

54-
const isDue = isBorrowDue(borrowRes.data)
55-
5629
const claim = await IsLoggedIn()
5730
if (!claim || !claim.librarease) {
5831
redirect(`/login?from=${encodeURIComponent(from)}`, RedirectType.replace)
@@ -78,273 +51,29 @@ export default async function BorrowDetailsPage({
7851
prevBorrows = prevBorrowsRes.data
7952
}
8053

81-
const isSuperAdmin = claim.librarease.role === 'SUPERADMIN'
82-
const isAdmin = claim.librarease.role === 'ADMIN'
83-
const isStaff = claim.librarease.admin_libs
84-
.concat(claim.librarease.staff_libs)
85-
.includes(borrowRes.data.subscription.membership.library_id)
86-
8754
return (
88-
<>
89-
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
90-
<Card className="md:row-span-2">
91-
<CardHeader>
92-
<CardTitle>Book Information</CardTitle>
93-
</CardHeader>
94-
<CardContent className="grid place-self-center md:place-self-auto md:grid-cols-2 gap-4">
95-
<Link href={`/books/${borrowRes.data.book.id}`}>
96-
{/* <Image
97-
src={borrowRes.data.book?.cover ?? '/book-placeholder.svg'}
98-
alt={borrowRes.data.book.title + "'s cover"}
99-
width={256}
100-
height={256}
101-
className="rounded-md w-56 h-auto hover:shadow-md hover:scale-105 transition-transform"
102-
priority
103-
/> */}
104-
<ThreeDBook book={borrowRes.data.book} />
105-
</Link>
106-
<div>
107-
<Link href={`/books/${borrowRes.data.book.id}`} className="link">
108-
<h2 className="text-xl font-semibold">
109-
{borrowRes.data.book.title}
110-
</h2>
111-
</Link>
112-
<p className="text-foreground/80">{borrowRes.data.book.author}</p>
113-
<p className="text-sm text-foreground/60">
114-
{borrowRes.data.book.code}
115-
</p>
116-
</div>
117-
</CardContent>
118-
</Card>
119-
120-
<Card>
121-
<CardHeader>
122-
<CardTitle>User Information</CardTitle>
123-
</CardHeader>
124-
<CardContent className="grid gap-2 grid-cols-[max-content_1fr] items-center">
125-
<User className="size-4" />
126-
<p>
127-
<span className="font-medium">Name:&nbsp;</span>
128-
{/* <Link href={`/users/${borrowRes.data.subscription.user.id}`}> */}
129-
{borrowRes.data.subscription.user.name}
130-
{/* </Link> */}
131-
</p>
132-
<Library className="size-4" />
133-
<p>
134-
<span className="font-medium">Library:&nbsp;</span>
135-
<Link
136-
className="link"
137-
href={`/libraries/${borrowRes.data.subscription.membership.library.id}`}
138-
>
139-
{borrowRes.data.subscription.membership.library.name}
140-
</Link>
141-
</p>
142-
{/* <CreditCard className="size-4" />
143-
<p>
144-
<span className="font-medium">Membership:&nbsp;</span>
145-
{borrowRes.data.subscription.membership.name}
146-
</p>
147-
<Clock className="size-4" />
148-
<p>
149-
<span className="font-medium">Expires:&nbsp;</span>
150-
{formatDate(borrowRes.data.subscription.expires_at)}
151-
</p> */}
152-
</CardContent>
153-
</Card>
154-
155-
<Card>
156-
<CardHeader>
157-
<CardTitle>Borrow Details</CardTitle>
158-
</CardHeader>
159-
<CardContent className="grid gap-2 grid-cols-[max-content_1fr] items-center">
160-
<UserCog className="size-4 text-muted-foreground" />
161-
<p>
162-
<span className="font-medium">Staff:&nbsp;</span>
163-
{borrowRes.data.staff.name}
164-
&nbsp;
165-
{borrowRes.data.returning
166-
? '/ ' + borrowRes.data.returning.staff.name
167-
: null}
168-
</p>
169-
170-
<Calendar className="size-4 text-muted-foreground" />
171-
<p>
172-
<span className="font-medium">Borrowed:&nbsp;</span>
173-
<DateTime
174-
dateTime={borrowRes.data.borrowed_at}
175-
relative={!borrowRes.data.returning}
176-
/>
177-
</p>
178-
{isDue ? (
179-
<>
180-
<Gavel className="size-4 text-muted-foreground" />
181-
<p>
182-
<span className="font-medium">Fine Expected:&nbsp;</span>
183-
{differenceInDays(
184-
borrowRes.data.returning
185-
? new Date(borrowRes.data.returning.returned_at)
186-
: new Date(),
187-
new Date(borrowRes.data.due_at)
188-
) +
189-
' x ' +
190-
(borrowRes.data.subscription.fine_per_day ?? 0) +
191-
' = ' +
192-
differenceInDays(
193-
borrowRes.data.returning
194-
? new Date(borrowRes.data.returning.returned_at)
195-
: new Date(),
196-
new Date(borrowRes.data.due_at)
197-
) *
198-
(borrowRes.data.subscription.fine_per_day ?? 0) +
199-
' Pts'}
200-
</p>
201-
<CalendarX className="size-4 text-destructive" />
202-
</>
203-
) : (
204-
<CalendarClock className="size-4 text-muted-foreground" />
205-
)}
206-
<p className={clsx({ 'text-destructive': isDue })}>
207-
<span className="font-medium">Due:&nbsp;</span>
208-
<DateTime
209-
dateTime={borrowRes.data.due_at}
210-
relative={!borrowRes.data.returning}
211-
/>
212-
</p>
213-
{borrowRes.data.returning ? (
214-
<>
215-
<CalendarCheck className="size-4 text-muted-foreground" />
216-
<p>
217-
<span className="font-medium">Returned:&nbsp;</span>
218-
<DateTime dateTime={borrowRes.data.returning.returned_at} />
219-
</p>
220-
<Gavel className="size-4 text-muted-foreground" />
221-
<p>
222-
<span className="font-medium">Fine Received:&nbsp;</span>
223-
{borrowRes.data.returning.fine ?? '-'} Pts
224-
</p>
225-
</>
226-
) : null}
227-
</CardContent>
228-
</Card>
55+
<DetailBorrow borrow={borrowRes.data} prevBorrows={prevBorrows}>
56+
<div className="bottom-0 sticky py-2 grid md:grid-cols-2 gap-2">
57+
{borrowRes.data.returning ? (
58+
<BtnUndoReturn
59+
variant="outline"
60+
className="w-full backdrop-blur-md"
61+
borrow={borrowRes.data}
62+
/>
63+
) : (
64+
<BtnReturnBook
65+
variant="outline"
66+
className="w-full"
67+
borrow={borrowRes.data}
68+
/>
69+
)}
70+
<Button asChild>
71+
<Link href={`./${borrowRes.data.id}/edit`} className="w-full">
72+
<Pen />
73+
Edit
74+
</Link>
75+
</Button>
22976
</div>
230-
<Card>
231-
<CardHeader>
232-
<div className="flex justify-between items-center">
233-
<CardTitle>Membership</CardTitle>
234-
<Badge
235-
variant={
236-
isSubscriptionActive(borrowRes.data.subscription)
237-
? 'default'
238-
: 'secondary'
239-
}
240-
className="capitalize"
241-
>
242-
{getSubscriptionStatus(borrowRes.data.subscription)}
243-
</Badge>
244-
</div>
245-
</CardHeader>
246-
<CardContent className="grid gap-2 grid-cols-[max-content_1fr] md:grid-cols-[max-content_1fr_max-content_1fr] items-center">
247-
<CreditCard className="size-4" />
248-
<p>
249-
<span className="font-medium">Membership:&nbsp;</span>
250-
<Link
251-
href={`/subscriptions/${borrowRes.data.subscription.id}`}
252-
className="link"
253-
>
254-
{borrowRes.data.subscription.membership.name}
255-
</Link>
256-
</p>
257-
<Clock className="size-4" />
258-
<p>
259-
<span className="font-medium">Expires:&nbsp;</span>
260-
<DateTime dateTime={borrowRes.data.subscription.expires_at} />
261-
</p>
262-
<CalendarClock className="size-4" />
263-
<p>
264-
<span className="font-medium">Borrow Period:&nbsp;</span>
265-
{borrowRes.data.subscription.loan_period} D
266-
</p>
267-
<Tally5 className="size-4" />
268-
<p>
269-
<span className="font-medium">Usage Limit:&nbsp;</span>
270-
{borrowRes.data.subscription.usage_limit ?? '-'}
271-
</p>
272-
<Book className="size-4" />
273-
<p>
274-
<span className="font-medium">Active Borrow Limit:&nbsp;</span>
275-
{borrowRes.data.subscription.active_loan_limit ?? '-'}
276-
</p>
277-
<Gavel className="size-4" />
278-
<p>
279-
<span className="font-medium">Fine per Day:&nbsp;</span>
280-
{borrowRes.data.subscription.fine_per_day ?? '-'} Pts
281-
</p>
282-
<Calendar className="size-4" />
283-
<p>
284-
<span className="font-medium">Purchased At:&nbsp;</span>
285-
<DateTime dateTime={borrowRes.data.subscription.created_at} />
286-
</p>
287-
</CardContent>
288-
</Card>
289-
{prevBorrows.length > 0 && (
290-
<Card>
291-
<CardHeader>
292-
<CardTitle>Previous Borrows</CardTitle>
293-
</CardHeader>
294-
<CardContent className="flex items-end overflow-x-scroll p-6 isolate">
295-
{prevBorrows.map((b) => (
296-
<Link
297-
href={`/borrows/${b.id}`}
298-
key={b.id}
299-
className={clsx(
300-
'shrink-0 relative left-0 transition-all not-first-of-type:-ml-12 brightness-75',
301-
'hover:transition-all hover:-translate-y-4 hover:transform-none hover:brightness-100',
302-
'peer peer-hover:left-12 peer-hover:transition-all',
303-
'[transform:perspective(800px)_rotateY(20deg)]',
304-
{
305-
'z-10 -translate-y-4 brightness-100 transform-none':
306-
b.id === id,
307-
}
308-
)}
309-
>
310-
<Image
311-
src={b.book?.cover ?? '/book-placeholder.svg'}
312-
alt={b.book.title + "'s cover"}
313-
width={160}
314-
height={240}
315-
className="shadow-md rounded-lg w-40 h-60 place-self-center object-cover"
316-
/>
317-
</Link>
318-
))}
319-
</CardContent>
320-
</Card>
321-
)}
322-
{(isSuperAdmin || isAdmin || isStaff) && (
323-
<div className="bottom-0 sticky py-2 grid md:grid-cols-2 gap-2">
324-
{borrowRes.data.returning ? (
325-
<BtnUndoReturn
326-
variant="outline"
327-
className="w-full backdrop-blur-md"
328-
borrow={borrowRes.data}
329-
/>
330-
) : (
331-
<BtnReturnBook
332-
variant="outline"
333-
className="w-full"
334-
borrow={borrowRes.data}
335-
/>
336-
)}
337-
<Button asChild>
338-
<Link
339-
href={`/borrows/${borrowRes.data.id}/edit`}
340-
className="w-full"
341-
>
342-
<Pen />
343-
Edit
344-
</Link>
345-
</Button>
346-
</div>
347-
)}
348-
</>
77+
</DetailBorrow>
34978
)
35079
}

0 commit comments

Comments
 (0)