Skip to content

Commit 6814b59

Browse files
committed
feat: return scanner
1 parent dbba18f commit 6814b59

File tree

9 files changed

+268
-75
lines changed

9 files changed

+268
-75
lines changed

app/(protected)/borrows/page.tsx

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ import {
1818

1919
import { getListBorrows } from '@/lib/api/borrow'
2020
import { Verify } from '@/lib/firebase/firebase'
21-
import { Book } from 'lucide-react'
21+
import { BookUser, Scan } from 'lucide-react'
2222
import Link from 'next/link'
2323
import type { Metadata } from 'next'
2424
import { SITE_NAME } from '@/lib/consts'
25+
import { DropdownMenuBorrow } from '@/components/borrows/DropdownMenuBorrow'
26+
import { BtnScanReturnBorrow } from '@/components/borrows/ModalReturnBorrow'
2527

2628
export const metadata: Metadata = {
2729
title: `Borrows · ${SITE_NAME}`,
@@ -86,12 +88,23 @@ export default async function Borrows({
8688
</BreadcrumbItem>
8789
</BreadcrumbList>
8890
</Breadcrumb>
89-
<Button asChild>
90-
<Link href="/borrows/new">
91-
<Book className="mr-2 size-4" />
92-
New Borrow
93-
</Link>
94-
</Button>
91+
<div className="md:hidden">
92+
<DropdownMenuBorrow />
93+
</div>
94+
<div className="hidden md:flex gap-2">
95+
<BtnScanReturnBorrow>
96+
<>
97+
<Scan className="mr-2 size-4" />
98+
Scan to Return
99+
</>
100+
</BtnScanReturnBorrow>
101+
<Button asChild>
102+
<Link href="/borrows/new">
103+
<BookUser className="mr-2 size-4" />
104+
Borrow a book
105+
</Link>
106+
</Button>
107+
</div>
95108
</div>
96109
</nav>
97110
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Button } from '@/components/ui/button'
2+
import {
3+
DropdownMenu,
4+
DropdownMenuContent,
5+
DropdownMenuGroup,
6+
DropdownMenuItem,
7+
DropdownMenuLabel,
8+
DropdownMenuTrigger,
9+
} from '@/components/ui/dropdown-menu'
10+
import { BookUser, Menu } from 'lucide-react'
11+
import Link from 'next/link'
12+
13+
export function DropdownMenuBorrow() {
14+
return (
15+
<DropdownMenu>
16+
<DropdownMenuTrigger asChild>
17+
<Button variant="outline">
18+
<Menu />
19+
Menu
20+
</Button>
21+
</DropdownMenuTrigger>
22+
<DropdownMenuContent className="w-56" align="start">
23+
<DropdownMenuLabel>Select an action</DropdownMenuLabel>
24+
<DropdownMenuGroup>
25+
<DropdownMenuItem>
26+
<BookUser className="mr-2 size-4" />
27+
<Link href="/borrows/new">Borrow a book</Link>
28+
</DropdownMenuItem>
29+
</DropdownMenuGroup>
30+
</DropdownMenuContent>
31+
</DropdownMenu>
32+
)
33+
}

components/borrows/FormBorrow.tsx

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
2424
import { Badge } from '@/components/ui/badge'
2525
import { Separator } from '@/components/ui/separator'
26-
import { useRouter, useSearchParams } from 'next/navigation'
26+
import { useRouter } from 'next/navigation'
2727
import { z } from 'zod'
2828
import { User } from '@/lib/types/user'
2929
import { getListUsers } from '@/lib/api/user'
@@ -67,9 +67,7 @@ import { Scanner } from '../common/Scanner'
6767
import Image from 'next/image'
6868

6969
const FormSchema = z.object({
70-
user_id: z.string({
71-
required_error: 'Please select a user.',
72-
}),
70+
user_id: z.string().optional(),
7371
book_id: z.string({
7472
required_error: 'Please select a book.',
7573
}),
@@ -90,13 +88,9 @@ type FormBorrowProps = {
9088

9189
export const FormBorrow: React.FC<FormBorrowProps> = (props) => {
9290
const router = useRouter()
93-
const searchParams = useSearchParams()
94-
const selectedMethod = searchParams.get('tab') || 'manual'
95-
const setSelectedMethod = (value: 'manual' | 'qr') => {
96-
const newParams = new URLSearchParams(searchParams.toString())
97-
newParams.set('tab', value)
98-
router.replace(`?${newParams.toString()}`)
99-
}
91+
const [selectedMethod, setSelectedMethod] = useState<'manual' | 'qr'>(
92+
'manual'
93+
)
10094

10195
const form = useForm<z.infer<typeof FormSchema>>({
10296
resolver: zodResolver(FormSchema),
@@ -337,7 +331,7 @@ export const FormBorrow: React.FC<FormBorrowProps> = (props) => {
337331
</CardContent>
338332
</Card>
339333

340-
{selectedUser && (
334+
{(selectedUser || form.formState.errors.subscription_id) && (
341335
<Card>
342336
<CardHeader>
343337
<CardTitle
@@ -409,7 +403,7 @@ export const FormBorrow: React.FC<FormBorrowProps> = (props) => {
409403
</Card>
410404
)}
411405

412-
{selectedSubscription && (
406+
{(selectedSubscription || form.formState.errors.book_id) && (
413407
<Card>
414408
<CardHeader>
415409
<CardTitle
@@ -506,7 +500,7 @@ export const FormBorrow: React.FC<FormBorrowProps> = (props) => {
506500
</Card>
507501
)}
508502

509-
{selectedBook && (
503+
{(selectedBook || form.formState.errors.staff_id) && (
510504
<Card>
511505
<CardHeader>
512506
<CardTitle
@@ -597,14 +591,15 @@ export const FormBorrow: React.FC<FormBorrowProps> = (props) => {
597591
<FormField
598592
control={form.control}
599593
name="subscription_id"
600-
render={({ field }) => (
594+
render={({ field, fieldState: { error } }) => (
601595
<Scanner
602596
title="Scan Subscription QR"
603597
description="Select to scan subscription QR code"
604598
onChange={(id) => {
605599
setSubQ({ id })
606600
field.onChange(id)
607601
}}
602+
error={error?.message}
608603
>
609604
{selectedSubscription && (
610605
<div className="p-4 border border-primary/40 bg-primary/5 rounded-lg">
@@ -635,14 +630,15 @@ export const FormBorrow: React.FC<FormBorrowProps> = (props) => {
635630
<FormField
636631
control={form.control}
637632
name="book_id"
638-
render={({ field }) => (
633+
render={({ field, fieldState: { error } }) => (
639634
<Scanner
640635
title="Scan Book QR"
641636
description="Select to scan book QR code"
642637
onChange={(id) => {
643638
setBookQ({ id })
644639
field.onChange(id)
645640
}}
641+
error={error?.message}
646642
>
647643
{selectedBook && (
648644
<div className="p-4 border border-primary/40 bg-primary/5 rounded-lg">

components/borrows/ModalEditBorrow.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,26 @@ import {
99
DialogTitle,
1010
} from '@/components/ui/dialog'
1111
import { BorrowDetail } from '@/lib/types/borrow'
12-
import { useRouter } from 'next/navigation'
12+
import { usePathname, useRouter } from 'next/navigation'
13+
import { useEffect, useRef, useState } from 'react'
1314

1415
export const ModalEditBorrow: React.FC<{ borrow: BorrowDetail }> = ({
1516
borrow,
1617
}) => {
1718
const router = useRouter()
19+
const pathname = usePathname()
20+
const [open, setOpen] = useState(true)
21+
const prevPathRef = useRef(pathname)
22+
23+
useEffect(() => {
24+
if (pathname !== prevPathRef.current) {
25+
setOpen(false)
26+
}
27+
prevPathRef.current = pathname
28+
}, [pathname])
29+
1830
return (
19-
<Dialog open={true} onOpenChange={router.back}>
31+
<Dialog open={open} onOpenChange={router.back}>
2032
<DialogContent>
2133
<DialogHeader>
2234
<DialogTitle>{borrow.book.title}</DialogTitle>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
'use client'
2+
3+
import {
4+
Dialog,
5+
DialogContent,
6+
DialogFooter,
7+
DialogHeader,
8+
DialogTitle,
9+
} from '@/components/ui/dialog'
10+
import { useState } from 'react'
11+
import { Scanner } from '@/components/common/Scanner'
12+
import { toast } from '@/components/hooks/use-toast'
13+
import { returnBorrowAction } from '@/lib/actions/return-borrow'
14+
import Link from 'next/link'
15+
import { Button } from '../ui/button'
16+
import { ArrowRight } from 'lucide-react'
17+
18+
export const ModalReturnBorrow: React.FC<{
19+
open: boolean
20+
onOpenChange: (open: boolean) => void
21+
}> = ({ open, onOpenChange }) => {
22+
const [id, setId] = useState<string>()
23+
24+
const onChange = async (id: string) => {
25+
const res = await returnBorrowAction(id)
26+
if ('error' in res) {
27+
toast({
28+
title: 'Failed to return book',
29+
description: res.error,
30+
variant: 'destructive',
31+
})
32+
} else {
33+
toast({
34+
title: 'Success',
35+
description: 'Book returned successfully',
36+
variant: 'default',
37+
})
38+
setId(id)
39+
}
40+
}
41+
42+
return (
43+
<Dialog open={open} onOpenChange={onOpenChange}>
44+
<DialogContent>
45+
<DialogHeader>
46+
<DialogTitle>Scan to Return</DialogTitle>
47+
</DialogHeader>
48+
49+
<Scanner
50+
title="Return a Book"
51+
onChange={onChange}
52+
value={''}
53+
initialFocus
54+
/>
55+
56+
<DialogFooter>
57+
{id && (
58+
<Button variant="ghost" asChild>
59+
<Link href={`/borrows/${id}`}>
60+
Go to Borrow Details
61+
<ArrowRight className="mr-2 size-4" />
62+
</Link>
63+
</Button>
64+
)}
65+
</DialogFooter>
66+
</DialogContent>
67+
</Dialog>
68+
)
69+
}
70+
71+
export const BtnScanReturnBorrow: React.FC<
72+
React.PropsWithChildren<Parameters<typeof Button>[0]>
73+
> = ({ children, ...props }) => {
74+
const [open, setOpen] = useState(false)
75+
76+
return (
77+
<>
78+
<Button onClick={() => setOpen(true)} variant="outline" {...props}>
79+
{children}
80+
</Button>
81+
<ModalReturnBorrow open={open} onOpenChange={setOpen} />
82+
</>
83+
)
84+
}

components/common/Scanner.tsx

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import React from 'react'
1+
'use client'
2+
3+
import React, { useEffect } from 'react'
24
import { Input } from '../ui/input'
35
import {
46
Card,
@@ -14,15 +16,33 @@ export const Scanner: React.FC<
1416
React.PropsWithChildren<{
1517
title?: string
1618
description?: string
19+
value?: string
1720
onChange: (id: string) => void
21+
error?: string
22+
isLoading?: boolean
23+
initialFocus?: boolean
1824
}>
19-
> = ({ title, description, onChange, children }) => {
25+
> = ({
26+
title,
27+
description,
28+
error,
29+
initialFocus,
30+
onChange,
31+
value,
32+
children,
33+
}) => {
2034
const inputRef = React.useRef<HTMLInputElement>(null)
2135

2236
const handleCardClick = () => {
2337
inputRef.current?.focus()
2438
}
2539

40+
useEffect(() => {
41+
if (initialFocus) {
42+
inputRef.current?.focus()
43+
}
44+
}, [initialFocus, inputRef])
45+
2646
return (
2747
<Card
2848
className={cn(
@@ -46,11 +66,22 @@ export const Scanner: React.FC<
4666
tabIndex={-1}
4767
className="opacity-0 w-0 h-0 p-0"
4868
ref={inputRef}
69+
value={value}
4970
onChange={(e) => onChange(e.target.value)}
5071
/>
5172
{children || (
52-
<div className="grid place-items-center py-8 border-2 border-dashed border-muted-foreground/25 rounded-lg">
53-
<Scan className="h-12 w-12 text-muted-foreground" />
73+
<div
74+
className={cn(
75+
'grid place-items-center py-8 border-2 border-dashed border-muted-foreground/25 rounded-lg',
76+
error && 'border-destructive/50'
77+
)}
78+
>
79+
<Scan
80+
className={cn(
81+
'h-12 w-12 text-muted-foreground',
82+
error && 'text-destructive'
83+
)}
84+
/>
5485
</div>
5586
)}
5687
</CardContent>

lib/actions/update-borrow.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import { revalidatePath } from 'next/cache'
44
import { updateBorrow } from '../api/borrow'
55
import { Verify } from '../firebase/firebase'
6-
import { redirect } from 'next/navigation'
76

87
export async function updateBorrowAction(
98
data: Parameters<typeof updateBorrow>[0]
@@ -24,6 +23,5 @@ export async function updateBorrowAction(
2423
return { error: 'failed to update borrow' }
2524
} finally {
2625
revalidatePath(`/borrows/${data.id}`)
27-
redirect(`/borrows/${data.id}`)
2826
}
2927
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@radix-ui/react-avatar": "^1.1.9",
1818
"@radix-ui/react-checkbox": "^1.1.3",
1919
"@radix-ui/react-dialog": "^1.1.6",
20-
"@radix-ui/react-dropdown-menu": "^2.1.14",
20+
"@radix-ui/react-dropdown-menu": "^2.1.15",
2121
"@radix-ui/react-label": "^2.1.1",
2222
"@radix-ui/react-popover": "^1.1.13",
2323
"@radix-ui/react-progress": "^1.1.2",

0 commit comments

Comments
 (0)