diff --git a/src/components/templates/CategoriesFilter.tsx b/src/components/templates/CategoriesFilter.tsx index 14cf2508..6b7ad032 100644 --- a/src/components/templates/CategoriesFilter.tsx +++ b/src/components/templates/CategoriesFilter.tsx @@ -1,19 +1,36 @@ -import { MultiSelect } from "@mantine/core" -import appConfig from "app.config" +import { Loader, MultiSelect } from "@mantine/core" +import { useState } from "react" +import { useDebounce } from "use-debounce" import { useFilter } from "~/contexts/FilterContext" import { useSiteTranslation } from "~/hooks/useSiteTranslation" +import { getEmptyCategoryGroups, getGroupForCategory } from "~/utils/categoryUtils" +import { trpc } from "~/utils/trpc" export const CategoriesFilter = () => { const { t } = useSiteTranslation() const filter = useFilter() + const [prefix, setPrefix] = useState("") + const [prefixDebounced] = useDebounce(prefix, 500) + const { data, error, status } = trpc.categories.getCategories.useQuery({ + language: filter.language, + prefix: prefixDebounced, + }) + + const groupsWithCategories = getEmptyCategoryGroups() + data?.categories.forEach((category) => { + const group = getGroupForCategory(category.key) + groupsWithCategories[group].push(category.key) + }) return ( setPrefix(value)} value={filter.categories} onChange={(value) => filter.setCategories(value)} - data={Object.entries(appConfig.availableCategories).map(([group, categories]) => ({ + data={Object.entries(groupsWithCategories).map(([group, categories]) => ({ group: t(`categories.group.${group}`), items: categories.map((category) => ({ value: category, @@ -22,6 +39,8 @@ export const CategoriesFilter = () => { }))} searchable clearable + rightSection={status === "loading" ? : null} + error={error?.message} /> ) } diff --git a/src/server/routers/categories.ts b/src/server/routers/categories.ts index 5416e5b0..64ea9c88 100644 --- a/src/server/routers/categories.ts +++ b/src/server/routers/categories.ts @@ -1,41 +1,22 @@ -import { Prisma } from "@prisma/client" -import { paginate } from "~/utils/pagination" import { GetCategoriesSchema } from "~/validations/categories" import { prisma } from "../prisma" import { publicProcedure, router } from "../trpc" export const categoriesRouter = router({ getCategories: publicProcedure.input(GetCategoriesSchema).query(async ({ input }) => { - const { language, filter, skip, take } = input + const { language, prefix } = input - const where: Prisma.CategoryWhereInput = { - templates: { some: { language } }, - key: filter ? { contains: filter, mode: "insensitive" } : {}, - } - - const { - items: categories, - hasMore, - nextPage, - count, - } = await paginate({ - skip, - take, - count: () => prisma.category.count({ where }), - query: (paginateArgs) => - prisma.category.findMany({ - ...paginateArgs, - where, - orderBy: { key: "asc" }, - select: { key: true }, - }), + const categories = await prisma.category.findMany({ + where: { + templates: language ? { some: { language } } : {}, + key: { startsWith: prefix }, + }, + orderBy: { key: "asc" }, + select: { key: true }, }) return { - categories: categories.map((category) => category.key), - nextPage, - hasMore, - count, + categories, } }), }) diff --git a/src/server/routers/templates.ts b/src/server/routers/templates.ts index f7797910..fcb5dc9b 100644 --- a/src/server/routers/templates.ts +++ b/src/server/routers/templates.ts @@ -1,3 +1,4 @@ +import { Prisma } from "@prisma/client" import { AUTHOR_ASC, AUTHOR_DESC, @@ -14,7 +15,7 @@ export const templatesRouter = router({ getTemplates: publicProcedure.input(GetTemplatesSchema).query(async ({ input }) => { const { categories, language, search, username, sorting, skip, take } = input - const where = { + const where: Prisma.TemplateWhereInput = { AND: categories.map((category) => ({ categories: { some: { key: category } }, })), diff --git a/src/utils/categoryUtils.ts b/src/utils/categoryUtils.ts new file mode 100644 index 00000000..bfcfaccc --- /dev/null +++ b/src/utils/categoryUtils.ts @@ -0,0 +1,20 @@ +import appConfig from "app.config" + +const categoryToGroupMap: Record = {} +for (const [group, categories] of Object.entries(appConfig.availableCategories)) { + for (const category of categories) { + categoryToGroupMap[category] = group + } +} + +export function getGroupForCategory(category: string): string { + return categoryToGroupMap[category] +} + +const emptyCategoryGroups = Object.fromEntries( + Object.keys(appConfig.availableCategories).map((group) => [group, []]) +) + +export function getEmptyCategoryGroups(): Record { + return JSON.parse(JSON.stringify(emptyCategoryGroups)) +} diff --git a/src/validations/categories.ts b/src/validations/categories.ts index 4e407c80..57e3ac92 100644 --- a/src/validations/categories.ts +++ b/src/validations/categories.ts @@ -1,7 +1,6 @@ import { z } from "zod" -import { PaginationSchema } from "./common" -export const GetCategoriesSchema = PaginationSchema.extend({ +export const GetCategoriesSchema = z.object({ language: z.string(), - filter: z.string().optional(), + prefix: z.string(), })