Skip to content

Commit c781b6c

Browse files
committed
feat: search
1 parent ef2cd0c commit c781b6c

File tree

10 files changed

+177
-62
lines changed

10 files changed

+177
-62
lines changed

apps/web/@/hooks/useGetImages.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const getImages = async (filter: TImageFilter) => {
66

77
// TODO: will update filter params later
88
// Add filter parameters to the query string
9-
// if (filter.) queryParams.append('search', filter.search)
9+
if (filter.search) queryParams.append("search", filter.search)
1010
if (filter.order) queryParams.append("order", filter.order)
1111
if (filter.orderBy) queryParams.append("orderBy", filter.orderBy)
1212
// Add any other filter parameters as needed

apps/web/@/messages/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"register": "Register",
2929
"profile": "Profile",
3030
"search": "Search",
31+
"clear": "Clear",
3132
"searchPlaceholder": "Enter your search here...",
3233
"searchResults": "Search Results",
3334
"searchNoResults": "No results found",

apps/web/@/molecules/upload/AssetsManagement.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
1-
import { useEffect, useState } from "react"
2-
3-
import { Image, TListImageResponse } from "database"
1+
import { useMemo } from "react"
42

53
import { useGetImages } from "@/hooks/useGetImages"
64

7-
import SearchBar from "../nav/search-bar"
5+
import { useFileManager } from "./FileManagerContainer"
86
import ImageList from "./ImageList"
7+
import ImageSearchBar from "./ImageSearchBar"
98

109
const AssetManagement = () => {
11-
const { images, isLoading, isError } = useGetImages({})
10+
const { search } = useFileManager()
11+
12+
const filterParams = useMemo(() => {
13+
return {
14+
search,
15+
}
16+
}, [search])
17+
18+
const { images, isLoading, isError } = useGetImages(filterParams)
1219

1320
return (
1421
<div className="h-[400px] overflow-scroll px-4 py-4">
15-
<div className="flex gap-4">
16-
<SearchBar />
17-
<div>filter...</div>
18-
</div>
22+
<ImageSearchBar />
1923

2024
<ImageList
2125
isLoading={isLoading}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from "react"
2+
import Image from "next/image"
3+
4+
import { Image as ImageType } from "database"
5+
import { Check, CheckCircle, Circle, CircleDot, TrashIcon } from "lucide-react"
6+
import { Button, cn } from "ui"
7+
8+
import { useFileManager } from "./FileManagerContainer"
9+
10+
interface ImageItemProps {
11+
image: ImageType
12+
}
13+
14+
export default function ImageItem({ image }: ImageItemProps) {
15+
const { selectedFiles, setSelectedFiles } = useFileManager()
16+
17+
const handleSelect = (image: ImageType) => {
18+
setSelectedFiles([image])
19+
}
20+
21+
return (
22+
<div
23+
className={cn("group relative border-2", {
24+
"border-blue-500": selectedFiles?.at(0)?.id === image.id,
25+
})}
26+
>
27+
<Image
28+
src={`${process.env.NEXT_PUBLIC_FRONTEND_URL}${image.url}`}
29+
alt={image.name}
30+
width={160}
31+
height={160}
32+
className="cursor-pointe h-[160px] w-[160px] bg-gray-300 object-cover"
33+
/>
34+
<Button
35+
variant="destructive"
36+
size="icon"
37+
className="absolute bottom-1 right-1 h-7 w-7 rounded-full opacity-0 transition-opacity group-hover:opacity-100"
38+
onClick={() => console.log("delete")}
39+
>
40+
<TrashIcon className="h-4 w-4" />
41+
</Button>
42+
<Button
43+
variant="outline"
44+
className="absolute right-1 top-1 h-7 w-7 rounded-full p-0"
45+
onClick={() => {
46+
if (selectedFiles?.at(0)?.id === image.id) {
47+
setSelectedFiles([])
48+
} else {
49+
handleSelect(image)
50+
}
51+
}}
52+
>
53+
{selectedFiles?.at(0)?.id === image.id ? (
54+
<CheckCircle className="h-6 w-6 text-blue-500" />
55+
) : (
56+
<Circle className="h-6 w-6" />
57+
)}
58+
</Button>
59+
</div>
60+
)
61+
}

apps/web/@/molecules/upload/ImageList.tsx

Lines changed: 14 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,33 @@
11
import React from "react"
2-
import Image from "next/image"
32

43
import { Image as ImageType } from "database"
5-
import { Check, CheckCircle, Circle, CircleDot, TrashIcon } from "lucide-react"
6-
import { Button } from "ui"
74

8-
import { useFileManager } from "./FileManagerContainer"
5+
import NoItemFounded from "../no-item-founded"
6+
import ImageItem from "./ImageItem"
7+
import Loading from "./Loading"
98

109
type ImageListProps = {
1110
images: ImageType[]
1211
isLoading: boolean
1312
}
1413

1514
const ImageList: React.FC<ImageListProps> = ({ images, isLoading }) => {
16-
const { selectedFiles, setSelectedFiles } = useFileManager()
15+
if (isLoading) {
16+
return <Loading />
17+
}
1718

18-
const handleSelect = (image: ImageType) => {
19-
setSelectedFiles([image])
19+
if (images?.length === 0) {
20+
return <NoItemFounded />
2021
}
2122

2223
return (
2324
<div className="mt-2 flex flex-wrap gap-3 p-1">
24-
{isLoading ? (
25-
<div>loading...</div>
26-
) : (
27-
images?.map((image) => (
28-
<div
29-
key={image.id}
30-
className="group relative border"
31-
>
32-
<Image
33-
src={`${process.env.NEXT_PUBLIC_FRONTEND_URL}${image.url}`}
34-
alt={`Image ${image.name}`}
35-
width={160}
36-
height={160}
37-
className="cursor-pointe h-[160px] w-[160px] bg-gray-300 object-cover"
38-
/>
39-
<Button
40-
variant="destructive"
41-
size="icon"
42-
className="absolute bottom-1 right-1 h-7 w-7 rounded-full opacity-0 transition-opacity group-hover:opacity-100"
43-
onClick={() => console.log("delete")}
44-
>
45-
<TrashIcon className="h-4 w-4" />
46-
</Button>
47-
<Button
48-
variant="outline"
49-
className="absolute right-1 top-1 h-7 w-7 rounded-full p-0"
50-
onClick={() => {
51-
if (selectedFiles?.at(0)?.id === image.id) {
52-
setSelectedFiles([])
53-
} else {
54-
handleSelect(image)
55-
}
56-
}}
57-
>
58-
{selectedFiles?.at(0)?.id === image.id ? (
59-
<CheckCircle className="h-6 w-6 text-blue-500" />
60-
) : (
61-
<Circle className="h-6 w-6" />
62-
)}
63-
</Button>
64-
</div>
65-
))
66-
)}
25+
{images?.map((image) => (
26+
<ImageItem
27+
key={image.id}
28+
image={image}
29+
/>
30+
))}
6731
</div>
6832
)
6933
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"use client"
2+
3+
import { useTranslations } from "next-intl"
4+
import { Button, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "ui"
5+
6+
import { useFileManager } from "./FileManagerContainer"
7+
8+
export default function ImageSearchBar() {
9+
const t = useTranslations()
10+
const { search, setSearch } = useFileManager()
11+
12+
const onClearSearch = () => {
13+
setSearch("")
14+
}
15+
16+
return (
17+
<div className="flex gap-4">
18+
<Input
19+
value={search}
20+
onChange={(e) => setSearch(e.target.value)}
21+
className="max-w-[300px]"
22+
/>
23+
24+
<Select>
25+
<SelectTrigger className="w-[180px]">
26+
<SelectValue placeholder="Select a fruit" />
27+
</SelectTrigger>
28+
<SelectContent>
29+
<SelectItem value="apple">Apple</SelectItem>
30+
<SelectItem value="banana">Banana</SelectItem>
31+
<SelectItem value="blueberry">Blueberry</SelectItem>
32+
<SelectItem value="grapes">Grapes</SelectItem>
33+
<SelectItem value="pineapple">Pineapple</SelectItem>
34+
</SelectContent>
35+
</Select>
36+
37+
{search && (
38+
<Button
39+
variant="link"
40+
onClick={onClearSearch}
41+
>
42+
{t("common.clear")}
43+
</Button>
44+
)}
45+
</div>
46+
)
47+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Skeleton } from "ui"
2+
3+
export default function Loading() {
4+
return (
5+
<div className="mt-2 flex flex-wrap gap-3 p-1">
6+
{Array.from({ length: 10 }).map((_, i) => (
7+
<Skeleton className="w-[160px h-[160px] " />
8+
))}
9+
</div>
10+
)
11+
}

apps/web/app/api/protected/images/route.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import fs from "fs/promises"
22
import path from "path"
33
import { NextRequest } from "next/server"
44

5-
import { createImage, getImage, getImages } from "database"
5+
import { createImage, getImage, getImages, TImageFilter } from "database"
66
import sharp from "sharp"
77
import { v4 as uuidv4 } from "uuid"
88

@@ -14,7 +14,22 @@ import { getServerSession } from "@/utils/auth"
1414
// GET /api/protected/images/list?page=1&limit=10&userId=1&caption=test
1515
// GET /api/protected/images/list?page=1&limit=10&userId=1&caption=test&mime=image/jpeg
1616
// GET /api/protected/images/list?page=1&limit=10&userId=1&caption=test&mime=image/jpeg&sort=createdAt:desc
17-
export async function GET(request: NextRequest, { params }: { params: { imageId: string } }) {
17+
export async function GET(request: NextRequest) {
18+
const searchParams = request.nextUrl.searchParams
19+
const page = searchParams.get("page") ? parseInt(searchParams.get("page")!) : 1
20+
const limit = searchParams.get("limit") ? parseInt(searchParams.get("limit")!) : 10
21+
const search = searchParams.get("search") || undefined
22+
// const orderBy = searchParams.get("orderBy") || undefined
23+
// const order = searchParams.get("order") as "asc" | "desc" | undefined
24+
25+
const params: TImageFilter = {
26+
page,
27+
limit,
28+
search,
29+
// orderBy,
30+
// order,
31+
}
32+
1833
try {
1934
const session = await getServerSession()
2035

@@ -27,6 +42,7 @@ export async function GET(request: NextRequest, { params }: { params: { imageId:
2742
}
2843

2944
const images = await getImages({
45+
...params,
3046
userId: session?.user?.id,
3147
})
3248

packages/database/src/images/queries.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ export const getImages = async (
2323
}
2424
}
2525

26+
if (options.search) {
27+
where = {
28+
...where,
29+
name: {
30+
contains: options.search,
31+
mode: "insensitive",
32+
},
33+
}
34+
}
35+
2636
let orderBy = {}
2737

2838
if (options.orderBy) {

packages/database/src/images/type.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export type TImageFilter = {
77
userId?: string
88
orderBy?: string
99
order?: OrderBy
10+
search?: string
1011
}
1112

1213
export type TListImageResponse = ActionReturnType<TGetListResponse<Image>>

0 commit comments

Comments
 (0)