added fuzzy search for table

This commit is contained in:
Polaris 2024-08-21 13:34:23 -04:00
parent 7fe79979b5
commit 007b23783a
5 changed files with 226 additions and 96 deletions

View File

@ -1,21 +1,19 @@
"use server";
import { artemis, daphnis } from "@/lib/prisma";
import type * as Prisma from "@prisma/client";
type ChuniScorePlaylog = Prisma.PrismaClient;
type ChuniStaticMusic = Prisma.PrismaClient;
type ChuniScorePlaylog = Prisma.PrismaClient;
type ChuniStaticMusic = Prisma.PrismaClient;
type LinkSharingToken = {
playlogId: number;
};
playlogId: number;
};
export async function getSongsWithTitles(userId: number) {
try {
const songs: ChuniScorePlaylog[] = await artemis.chuni_score_playlog.findMany({
try {
const songs: ChuniScorePlaylog[] =
await artemis.chuni_score_playlog.findMany({
where: {
user: userId,
},
@ -63,12 +61,13 @@ export async function getSongsWithTitles(userId: number) {
ticketId: true,
},
});
const chuniScorePlaylogMusicId = songs
.map((song) => song.musicId)
.filter((id): id is number => id !== null);
const staticMusicInfo: ChuniStaticMusic[] = await artemis.chuni_static_music.findMany({
const chuniScorePlaylogMusicId = songs
.map((song) => song.musicId)
.filter((id): id is number => id !== null);
const staticMusicInfo: ChuniStaticMusic[] =
await artemis.chuni_static_music.findMany({
where: {
songId: {
in: chuniScorePlaylogMusicId,
@ -82,74 +81,76 @@ export async function getSongsWithTitles(userId: number) {
level: true,
genre: true,
worldsEndTag: true,
jacketPath: true,
jacketPath: true,
},
});
const playCounts = await artemis.chuni_score_playlog.groupBy({
by: ['musicId'],
_count: {
musicId: true,
const playCounts = await artemis.chuni_score_playlog.groupBy({
by: ["musicId"],
_count: {
musicId: true,
},
where: {
user: userId,
musicId: {
in: chuniScorePlaylogMusicId,
},
where: {
user: userId,
musicId: {
in: chuniScorePlaylogMusicId,
},
},
});
const playCountMap = playCounts.reduce((map, item) => {
},
});
const playCountMap = playCounts.reduce(
(map, item) => {
if (item.musicId !== null) {
map[item.musicId] = item._count.musicId;
}
return map;
}, {} as Record<number, number>);
const songsWithTitles = songs.map((song) => {
const staticInfo = staticMusicInfo.find(
(chuniStaticMusic) =>
chuniStaticMusic.songId === song.musicId &&
chuniStaticMusic.chartId === song.level
);
return {
...song,
title: staticInfo?.title || "Unknown Title",
artist: staticInfo?.artist || "Unknown Artist",
genre: staticInfo?.genre || "Unknown Genre",
chartId: staticInfo?.chartId || "Unknown chartId",
level: staticInfo?.level || "Unknown Level",
chartlevel: song.level || "Unknown Level",
playCount: song.musicId !== null ? playCountMap[song.musicId] || 0 : 0,
jacketPath: staticInfo?.jacketPath || "",
};
});
return songsWithTitles;
} catch (error) {
console.error("Error fetching songs with titles:", error);
throw error;
}
}
},
{} as Record<number, number>,
);
const songsWithTitles = songs.map((song) => {
const staticInfo = staticMusicInfo.find(
(chuniStaticMusic) =>
chuniStaticMusic.songId === song.musicId &&
chuniStaticMusic.chartId === song.level,
);
return {
...song,
title: staticInfo?.title || "Unknown Title",
artist: staticInfo?.artist || "Unknown Artist",
genre: staticInfo?.genre || "Unknown Genre",
chartId: staticInfo?.chartId || "Unknown chartId",
level: staticInfo?.level || "Unknown Level",
chartlevel: song.level || "Unknown Level",
playCount: song.musicId !== null ? playCountMap[song.musicId] || 0 : 0,
jacketPath: staticInfo?.jacketPath || "",
};
});
return songsWithTitles;
} catch (error) {
console.error("Error fetching songs with titles:", error);
throw error;
}
}
export async function generatePlaylogId(playlogid: number) {
try {
const tokens = (await daphnis.linkSharingToken.findMany({
where: {
playlogId: playlogid,
},
select: {
playlogId: true,
},
})) as LinkSharingToken[];
const playlogIds: number[] = tokens.map((token) => token.playlogId);
return playlogIds;
} catch (error) {
console.error("Error fetching playlogIds:", error);
throw error;
}
}
try {
const tokens = (await daphnis.linkSharingToken.findMany({
where: {
playlogId: playlogid,
},
select: {
playlogId: true,
},
})) as LinkSharingToken[];
const playlogIds: number[] = tokens.map((token) => token.playlogId);
return playlogIds;
} catch (error) {
console.error("Error fetching playlogIds:", error);
throw error;
}
}

View File

@ -41,16 +41,7 @@ const HeaderNavigation = async () => {
</SheetContent>
</Sheet>
<div className="flex w-full items-center gap-4 md:ml-auto md:gap-2 lg:gap-4">
<form className="ml-auto flex-1 sm:flex-initial">
{/* <div className="relative">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
type="search"
placeholder="Search"
className="pl-8 sm:w-[300px] md:w-[200px] lg:w-[300px]"
/>
</div> */}
</form>
<div className="ml-auto flex-1 sm:flex-initial"></div>
<DarkToggle />
<DropdownMenu>

View File

@ -0,0 +1,37 @@
import React, { useEffect, useState } from "react";
import { Input } from "../ui/input";
export default function DebouncedInput({
value: initialValue,
onChange,
debounce = 500,
...props
}: {
value: string | number;
onChange: (value: string | number) => void;
debounce?: number;
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange">) {
const [value, setValue] = useState(initialValue);
useEffect(() => {
setValue(initialValue);
}, [initialValue]);
useEffect(() => {
const timeout = setTimeout(() => {
onChange(value);
}, debounce);
return () => clearTimeout(timeout);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value]);
return (
<Input
className="m-4 w-[400px]"
{...props}
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
}

View File

@ -0,0 +1,87 @@
"use server";
import { artemis } from "@/lib/prisma";
import type * as Prisma from "@prisma/client";
type ChuniScorePlaylog = Prisma.PrismaClient;
type ChuniStaticMusic = Prisma.PrismaClient;
export async function searchSongWithTitle(
userId: number,
searchQuery: string = "",
) {
try {
const songs: ChuniScorePlaylog[] =
await artemis.chuni_score_playlog.findMany({
where: {
user: userId,
},
orderBy: {
userPlayDate: "desc",
},
select: {
id: true,
},
});
const chuniScorePlaylogMusicId = songs
.map((song) => song.musicId)
.filter((id): id is number => id !== null);
const staticMusicInfo: ChuniStaticMusic[] =
await artemis.chuni_static_music.findMany({
where: {
songId: {
in: chuniScorePlaylogMusicId,
},
title: {
contains: searchQuery,
},
},
select: {
title: true,
},
});
const playCounts = await artemis.chuni_score_playlog.groupBy({
by: ["musicId"],
_count: {
musicId: true,
},
where: {
user: userId,
musicId: {
in: chuniScorePlaylogMusicId,
},
},
});
const playCountMap = playCounts.reduce(
(map, item) => {
if (item.musicId !== null) {
map[item.musicId] = item._count.musicId;
}
return map;
},
{} as Record<number, number>,
);
const songsWithTitles = songs.map((song) => {
const staticInfo = staticMusicInfo.find(
(chuniStaticMusic) =>
chuniStaticMusic.songId === song.musicId &&
chuniStaticMusic.chartId === song.level,
);
return {
...song,
title: staticInfo?.title || "Unknown Title",
};
});
return songsWithTitles;
} catch (error) {
console.error("Error fetching songs with titles:", error);
throw error;
}
}

View File

@ -1,10 +1,14 @@
"use client";
// https://github.com/dracor-org/einakter/blob/466ca1663098a16cc1141129a6ba22628135b04c/src/components/Table.tsx#L26
// used the above for reference on how to fuzzy search
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
ColumnDef,
SortingState,
getSortedRowModel,
flexRender,
getFilteredRowModel,
getCoreRowModel,
getPaginationRowModel,
useReactTable,
@ -18,8 +22,7 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import { useState } from "react";
import DebouncedInput from "./DebouncedInput";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
@ -30,21 +33,32 @@ export function DataTable<TData, TValue>({
data,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([]);
const [globalFilter, setGlobalFilter] = useState("");
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
state: {
sorting,
globalFilter,
},
onSortingChange: setSorting,
onGlobalFilterChange: setGlobalFilter,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
});
return (
<div className="rounded-md border ">
<div className="rounded-md border">
<div className="mb-2">
<DebouncedInput
value={globalFilter ?? ""}
onChange={(value) => setGlobalFilter(String(value))}
placeholder={`Search`}
/>
</div>
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
@ -55,7 +69,7 @@ export function DataTable<TData, TValue>({
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
header.getContext(),
)}
</TableHead>
))}
@ -85,7 +99,7 @@ export function DataTable<TData, TValue>({
)}
</TableBody>
</Table>
<div className="flex items-center justify-end space-x-2 py- p-4">
<div className="py- flex items-center justify-end space-x-2 p-4">
<Button
variant="outline"
size="sm"