search results

This commit is contained in:
yxshv 2024-04-13 15:01:51 +05:30
parent 460b345997
commit 070ead4551
4 changed files with 120 additions and 36 deletions

View file

@ -6,36 +6,51 @@ import {
sessions,
StoredContent,
storedContent,
StoredSpace,
users,
space
} from "@/server/db/schema";
import { SearchResult } from "@/contexts/MemoryContext";
import { like, eq, and, sql } from "drizzle-orm";
import { union } from "drizzle-orm/sqlite-core"
import { auth as authOptions } from "@/server/auth";
// @todo: (future) pagination not yet needed
export async function searchMemoriesAndSpaces(userId: string, query: string) {
const searchMemoriesQuery = db.select({
type: sql<string>`'memory'`,
space: sql`NULL`,
memory: storedContent as any
}).from(storedContent).where(and(
eq(storedContent.user, userId),
like(storedContent.title, `%${query}%`)
))
export async function searchMemoriesAndSpaces(query: string): Promise<SearchResult[]> {
const searchSpacesQuery = db.select({
type: sql<string>`'space'`,
space: space as any,
memory: sql`NULL`,
}).from(space).where(
and(
eq(space.user, userId),
like(space.name, `%${query}%`)
)
)
const user = await getUser()
if (!user) {
return []
}
try {
const searchMemoriesQuery = db.select({
type: sql<string>`'memory'`,
space: sql`NULL`,
memory: storedContent as any
}).from(storedContent).where(and(
eq(storedContent.user, user.id),
like(storedContent.title, `%${query}%`)
)).all()
const searchSpacesQuery = db.select({
type: sql<string>`'space'`,
space: space as any,
memory: sql`NULL`,
}).from(space).where(
and(
eq(space.user, user.id),
like(space.name, `%${query}%`)
)
).all()
const data = await Promise.all([searchSpacesQuery, searchMemoriesQuery])
return data.reduce((acc, i) => [...acc, ...i]) as SearchResult[]
} catch {
return []
}
return await union(searchMemoriesQuery, searchSpacesQuery)
}
async function getUser() {

View file

@ -10,6 +10,7 @@ import { Input, InputWithIcon } from "../ui/input";
import {
ArrowUpRight,
Edit3,
Loader,
MoreHorizontal,
Plus,
Search,
@ -25,7 +26,7 @@ import {
} from "../ui/dropdown-menu";
import { useEffect, useMemo, useRef, useState } from "react";
import { Variant, useAnimate, motion } from "framer-motion";
import { useMemory } from "@/contexts/MemoryContext";
import { SearchResult, useMemory } from "@/contexts/MemoryContext";
import { SpaceIcon } from "@/assets/Memories";
import {
Dialog,
@ -44,10 +45,12 @@ import { AddMemoryPage, NoteAddPage, SpaceAddPage } from "./AddMemoryDialog";
import { ExpandedSpace } from "./ExpandedSpace";
import { StoredContent, StoredSpace } from "@/server/db/schema";
import Image from "next/image"
import { useDebounce } from "@/hooks/useDebounce";
import { searchMemoriesAndSpaces } from "@/actions/db";
export function MemoriesBar() {
const [parent, enableAnimations] = useAutoAnimate();
const { spaces, deleteSpace, freeMemories } = useMemory();
const { spaces, deleteSpace, freeMemories, search } = useMemory();
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [addMemoryState, setAddMemoryState] = useState<
@ -55,6 +58,11 @@ export function MemoriesBar() {
>(null);
const [expandedSpace, setExpandedSpace] = useState<number | null>(null);
const [searchQuery, setSearcyQuery] = useState("");
const [searchLoading, setSearchLoading] = useState(false)
const query = useDebounce(searchQuery, 1000)
const [searchResults, setSearchResults] = useState<SearchResult[]>([])
if (expandedSpace) {
return (
@ -65,14 +73,31 @@ export function MemoriesBar() {
);
}
useEffect(() => {
const q = query.trim()
if (q.length < 1) {
setSearchResults([])
return
}
setSearchLoading(true);
(async () => {
setSearchResults(await search(q))
setSearchLoading(false)
})();
}, [query])
return (
<div className="text-rgray-11 flex w-full flex-col items-start py-8 text-left">
<div className="w-full px-8">
<h1 className="w-full text-2xl">Your Memories</h1>
<InputWithIcon
placeholder="Search"
icon={<Search className="text-rgray-11 h-5 w-5 opacity-50" />}
icon={searchLoading ? <Loader className="text-rgray-11 h-5 w-5 opacity-50 animate-spin" /> : <Search className="text-rgray-11 h-5 w-5 opacity-50" />}
className="bg-rgray-4 mt-2 w-full"
value={searchQuery}
onChange={(e) => setSearcyQuery(e.target.value)}
/>
</div>
<div className="mt-2 flex w-full px-8">
@ -123,17 +148,34 @@ export function MemoriesBar() {
ref={parent}
className="grid w-full grid-flow-row grid-cols-3 gap-1 px-2 py-5"
>
{spaces.map((space) => (
<SpaceItem
onDelete={() => {}}
key={space.id}
//onClick={() => setExpandedSpace(space.id)}
{...space}
/>
))}
{freeMemories.map(m => (
<MemoryItem {...m} key={m.id} />
))}
{searchQuery.trim().length > 0 ? (
<>
{searchResults.map(({ type, space, memory }, i) => (
<>
{type === "memory" && (
<MemoryItem {...memory!} key={i} />
)}
{type === "space" && (
<SpaceItem {...space!} key={i} onDelete={() => {}} />
)}
</>
))}
</>
): (
<>
{spaces.map((space) => (
<SpaceItem
onDelete={() => {}}
key={space.id}
//onClick={() => setExpandedSpace(space.id)}
{...space}
/>
))}
{freeMemories.map(m => (
<MemoryItem {...m} key={m.id} />
))}
</>
)}
</div>
</div>
);

View file

@ -62,7 +62,7 @@ export const MemoryProvider: React.FC<
if (!user.id) {
throw new Error('user id is not define')
}
const data = await searchMemoriesAndSpaces(user.id, query)
const data = await searchMemoriesAndSpaces(query)
return data as SearchResult[]
}

View file

@ -0,0 +1,27 @@
import { useEffect, useState } from 'react';
/**
* Use this hook when you need to debounce a value.
* @param value
* @param delay in milliseconds
*/
export const useDebounce = <T>(value: T, delay: number) => {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
// Update debounced value after delay
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// Cancel the timeout if value changes (also on delay change or unmount)
// This is how we prevent debounced value from updating if the value is changed
// within the delay period. Timeout gets cleared and restarted.
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
};