From c33a621cbbcf66f0bbb22b31b000af0e75f6c5bf Mon Sep 17 00:00:00 2001 From: Jorge Vargas Date: Mon, 31 Mar 2025 17:40:03 -0600 Subject: [PATCH] wi[p --- package.json | 4 +- src/components/form/AsyncMultiSelect.tsx | 91 ++++++++ src/components/form/Input.tsx | 64 +++++- src/pages/admin/album/[id].astro | 92 ++++++++ src/pages/api/anim/find.ts | 21 ++ src/pages/api/game/find.ts | 21 ++ src/pages/api/platform/find.ts | 21 ++ src/styles/global.css | 4 + src/utils/prisma-client.ts | 2 +- yarn.lock | 268 ++++++++++++++++++++++- 10 files changed, 575 insertions(+), 13 deletions(-) create mode 100644 src/components/form/AsyncMultiSelect.tsx create mode 100644 src/pages/admin/album/[id].astro create mode 100644 src/pages/api/anim/find.ts create mode 100644 src/pages/api/game/find.ts create mode 100644 src/pages/api/platform/find.ts diff --git a/package.json b/package.json index 88da508..8723b5f 100644 --- a/package.json +++ b/package.json @@ -20,14 +20,16 @@ "@types/react-dom": "^18.3.1", "astro": "^5.3.0", "astro-icon": "^1.1.1", - "better-auth": "^1.1.11", "axios": "^1.8.1", + "better-auth": "^1.1.11", "clsx": "^2.1.1", "nodemailer": "^6.10.0", "prisma": "^6.4.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hot-toast": "^2.4.1", + "react-multi-select": "^0.1.0", + "react-multi-select-component": "^4.3.4", "react-svg-spinners": "^0.3.1", "sharp": "^0.33.5", "slugify": "^1.6.6", diff --git a/src/components/form/AsyncMultiSelect.tsx b/src/components/form/AsyncMultiSelect.tsx new file mode 100644 index 0000000..86ff9ea --- /dev/null +++ b/src/components/form/AsyncMultiSelect.tsx @@ -0,0 +1,91 @@ +import clsx from 'clsx' +import { useEffect, useState } from 'react' +import { MultiSelect, type Option } from 'react-multi-select-component' + +interface Props { + url: string + nameColumn: string + className?: string + valueColumn?: string + defaultSelected?: any[] + name: string +} + +function toMapValue(data: any[], nameColumn: string, valueColumn: string) { + return data.map((item: { [nameColumn]: string; [valueColumn]: string }) => ({ + label: item[nameColumn], + value: item[valueColumn] + })) +} + +export default function AsyncMultiSelect(props: Props) { + const { url: defaultUrl, nameColumn, valueColumn = 'id', className, defaultSelected = [], name } = props + const [url, setUrl] = useState(defaultUrl) + const [loading, setLoading] = useState(false) + const [selected, setSelected] = useState(toMapValue(defaultSelected, nameColumn, valueColumn)) + const [options, setOptions] = useState([]) + + useEffect(() => { + async function fetchData() { + try { + setLoading(true) + const res = await fetch(url) + if (!res.ok) return + + const data = await res.json() + const dataOptions = toMapValue(data, nameColumn, valueColumn) + + setOptions(dataOptions) + } catch (err) { + console.error(err) + setOptions([]) + } finally { + setLoading(false) + } + } + + fetchData() + }, [url]) + + function filterOptions(options: Option[], search: string) { + if (search.length === 0) setUrl(defaultUrl) + else setUrl(`${defaultUrl}?q=${search}`) + + return options + } + + function valueRenderer(selected: Option[], options: Option[]) { + return selected.map((s) => {s.label}) + } + + const itemRenderer = ({ + checked, + option, + onClick, + disabled + }: { + checked: boolean + option: Option + onClick: () => void + disabled: boolean + }) => ( +
+ + {option.label} +
+ ) + + return ( + .search]:text-black', className)} + valueRenderer={valueRenderer} + ItemRenderer={itemRenderer} + /> + ) +} diff --git a/src/components/form/Input.tsx b/src/components/form/Input.tsx index 9c0bbf5..311f1ae 100644 --- a/src/components/form/Input.tsx +++ b/src/components/form/Input.tsx @@ -1,15 +1,67 @@ import type { ComponentProps, PropsWithChildren } from 'react' import clsx from 'clsx' -export default function Input(props: PropsWithChildren>) { - const { name, className, children, ...attrs } = props +export function InputLabel(props: PropsWithChildren<{ dark: boolean; name: string }>) { + const { dark, name, children } = props + return ( + + ) +} + +interface CustomInputProps { + name: string + label: string + dark?: boolean + defaultValue?: string | number | null +} + +export function Input(props: CustomInputProps & Omit, 'defaultValue'>) { + const { name, className, dark = false, defaultValue, label, ...attrs } = props return (
- - + + {label} + + +
+ ) +} + +export function InputArea(props: CustomInputProps & Omit, 'defaultValue'>) { + const { name, className, dark = false, defaultValue, label, ...attrs } = props + + return ( +
+ + {label} + +