Skip to content

Commit 8bcaebe

Browse files
committed
feat: book import job
1 parent e02b9c0 commit 8bcaebe

File tree

14 files changed

+335
-151
lines changed

14 files changed

+335
-151
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { Alert, AlertDescription } from '@/components/ui/alert'
2+
import { Badge } from '@/components/ui/badge'
3+
import {
4+
Breadcrumb,
5+
BreadcrumbItem,
6+
BreadcrumbLink,
7+
BreadcrumbList,
8+
BreadcrumbSeparator,
9+
} from '@/components/ui/breadcrumb'
10+
import { Button } from '@/components/ui/button'
11+
import { Verify } from '@/lib/firebase/firebase'
12+
import { Download, FileText } from 'lucide-react'
13+
14+
export default async function BorrowDetailsLayout({
15+
children,
16+
params,
17+
}: Readonly<{
18+
children: React.ReactNode
19+
params: Promise<{}>
20+
}>) {
21+
const headers = await Verify({ from: `/admin/books/import` })
22+
23+
return (
24+
<div className="space-y-4">
25+
<nav className="backdrop-blur-sm sticky top-0 z-10">
26+
<h1 className="text-2xl font-semibold">Import Books</h1>
27+
<div className="flex justify-between items-center">
28+
<Breadcrumb>
29+
<BreadcrumbList>
30+
<BreadcrumbItem>
31+
<BreadcrumbLink href="/admin">Home</BreadcrumbLink>
32+
</BreadcrumbItem>
33+
<BreadcrumbSeparator />
34+
<BreadcrumbItem>
35+
<BreadcrumbLink href="/admin/books">Books</BreadcrumbLink>
36+
</BreadcrumbItem>
37+
<BreadcrumbSeparator />
38+
<BreadcrumbItem>Import</BreadcrumbItem>
39+
</BreadcrumbList>
40+
</Breadcrumb>
41+
</div>
42+
</nav>
43+
{/* Instructions */}
44+
<Alert>
45+
<FileText className="h-4 w-4" />
46+
<AlertDescription>
47+
<div className="space-y-2">
48+
<p>Upload a CSV file with the following columns:</p>
49+
<ul className="list-disc list-inside text-sm space-y-1">
50+
<li>
51+
<strong>id</strong> - Book ID (optional, used to detect updates)
52+
</li>
53+
<li>
54+
<strong>code</strong> - Book code (required)
55+
</li>
56+
<li>
57+
<strong>title</strong> - Book title (required)
58+
</li>
59+
<li>
60+
<strong>author</strong> - Author name (optional)
61+
</li>
62+
<li>
63+
<strong>year</strong> - Year of publication (optional)
64+
</li>
65+
</ul>
66+
<Button variant="link" className="p-0 h-auto">
67+
<Download className="h-3 w-3 mr-1" />
68+
Download template
69+
</Button>
70+
</div>
71+
</AlertDescription>
72+
</Alert>
73+
{children}
74+
</div>
75+
)
76+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Button } from '@/components/ui/button'
2+
import {
3+
Card,
4+
CardContent,
5+
CardDescription,
6+
CardHeader,
7+
CardTitle,
8+
} from '@/components/ui/card'
9+
import { RefreshCw, Upload } from 'lucide-react'
10+
11+
export default function ImportBooksPage() {
12+
return (
13+
<Card>
14+
<CardHeader>
15+
<CardTitle>Upload CSV File</CardTitle>
16+
<CardDescription>
17+
Select a CSV file to preview the import
18+
</CardDescription>
19+
</CardHeader>
20+
<CardContent className="space-y-4">
21+
<div className="flex items-center gap-4">
22+
<div className="flex-1">
23+
<input
24+
type="file"
25+
accept=".csv,text/csv"
26+
// onChange={console.log}
27+
className="block w-full text-sm text-foreground
28+
file:mr-4 file:py-2 file:px-4
29+
file:rounded-md file:border-0
30+
file:text-sm file:font-semibold
31+
file:bg-primary/10 file:text-primary
32+
hover:file:bg-primary/20
33+
cursor-pointer"
34+
/>
35+
</div>
36+
<Button disabled={false}>
37+
{true ? (
38+
<>
39+
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
40+
Processing...
41+
</>
42+
) : (
43+
<>
44+
<Upload className="h-4 w-4 mr-2" />
45+
Generate Preview
46+
</>
47+
)}
48+
</Button>
49+
</div>
50+
{/* {file && (
51+
<div className="text-sm text-muted-foreground">
52+
Selected file: <span className="font-medium">{file.name}</span> ({(file.size / 1024).toFixed(2)} KB)
53+
</div>
54+
)} */}
55+
</CardContent>
56+
</Card>
57+
)
58+
}

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { getListBooks } from '@/lib/api/book'
1818
import Link from 'next/link'
1919
import type { Metadata } from 'next'
2020
import { SITE_NAME } from '@/lib/consts'
21-
import { Search } from 'lucide-react'
21+
import { Plus, Search, Upload } from 'lucide-react'
2222
import { DebouncedInput } from '@/components/common/DebouncedInput'
2323
import { Badge } from '@/components/ui/badge'
2424
import { ListBook } from '@/components/books/ListBook'
@@ -90,9 +90,20 @@ export default async function Books({
9090
</BreadcrumbItem>
9191
</BreadcrumbList>
9292
</Breadcrumb>
93-
<Button asChild>
94-
<Link href="/admin/books/new">Register New Book</Link>
95-
</Button>
93+
<div className="flex gap-2">
94+
<Button variant="outline" asChild>
95+
<Link href="/admin/books/import">
96+
<Upload className="mr-2 h-4 w-4" />
97+
Import Books
98+
</Link>
99+
</Button>
100+
<Button asChild>
101+
<Link href="/admin/books/new">
102+
<Plus className="mr-2 h-4 w-4" />
103+
Add Book
104+
</Link>
105+
</Button>
106+
</div>
96107
</div>
97108
</nav>
98109

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import BorrowListSkeleton from '@/components/borrows/BorrowListSkeleton'
2+
3+
export default function Loading() {
4+
return <BorrowListSkeleton />
5+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export default async function Jobs({
4343
skip?: number
4444
limit?: number
4545
status?: 'pending' | 'processing' | 'completed' | 'failed'
46-
type?: 'export:borrowings'
46+
type?: 'export:borrowings' | 'import:books'
4747
}>
4848
}) {
4949
const sp = await searchParams
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import BorrowListSkeleton from '@/components/borrows/BorrowListSkeleton'
2+
3+
export default function Loading() {
4+
return <BorrowListSkeleton />
5+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Skeleton } from '@/components/ui/skeleton'
2+
3+
export default function BorrowListSkeleton() {
4+
return (
5+
<div className="space-y-4">
6+
<nav className="backdrop-blur-sm sticky top-0 z-10">
7+
<div className="h-8 w-40 mb-2">
8+
<Skeleton className="h-full w-full" />
9+
</div>
10+
<div className="flex justify-between items-center">
11+
<div className="flex gap-2">
12+
<Skeleton className="h-6 w-24" />
13+
<Skeleton className="h-6 w-16" />
14+
</div>
15+
<div className="hidden md:flex gap-2">
16+
<Skeleton className="h-10 w-36" />
17+
<Skeleton className="h-10 w-36" />
18+
</div>
19+
</div>
20+
</nav>
21+
<div className="flex flex-col gap-2 md:flex-row justify-between">
22+
<div className="flex gap-2">
23+
<Skeleton className="h-8 w-20" />
24+
<Skeleton className="h-8 w-20" />
25+
<Skeleton className="h-8 w-20" />
26+
</div>
27+
<div className="self-end inline-flex gap-2">
28+
<Skeleton className="h-8 w-32" />
29+
<Skeleton className="h-8 w-32" />
30+
</div>
31+
</div>
32+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
33+
{[...Array(6)].map((_, i) => (
34+
<div key={i} className="space-y-2 p-4 border rounded-lg">
35+
<Skeleton className="h-6 w-1/2 mb-2" />
36+
<Skeleton className="h-4 w-1/3 mb-2" />
37+
<Skeleton className="h-4 w-1/4 mb-2" />
38+
<Skeleton className="h-8 w-full" />
39+
</div>
40+
))}
41+
</div>
42+
<div className="flex justify-center gap-4 mt-4">
43+
<Skeleton className="h-8 w-24" />
44+
<Skeleton className="h-8 w-24" />
45+
</div>
46+
</div>
47+
)
48+
}

components/jobs/BtnDownloadJobResult.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import { Job } from '@/lib/types/job'
44
import { useTransition } from 'react'
55
import { Button } from '../ui/button'
66
import { Loader, Download } from 'lucide-react'
7-
import { downloadJobResultAction } from '@/lib/actions/download-job-result'
7+
import { downloadJobAssetAction } from '@/lib/actions/download-job-asset'
88
import { toast } from 'sonner'
99

10-
export const BtnDownloadJobResult: React.FC<
10+
export const BtnDownloadJobAsset: React.FC<
1111
React.ComponentProps<typeof Button> & {
1212
job: Job
1313
}
@@ -16,7 +16,7 @@ export const BtnDownloadJobResult: React.FC<
1616

1717
const onClick = () => {
1818
startTransition(async () => {
19-
const res = await downloadJobResultAction(job.id)
19+
const res = await downloadJobAssetAction(job.id)
2020
if ('error' in res) {
2121
toast.error(res.error)
2222
return

0 commit comments

Comments
 (0)