implement genre picker cell (janky)
This commit is contained in:
parent
6073a1dce5
commit
1b49672be6
BIN
prisma/dev.db
BIN
prisma/dev.db
Binary file not shown.
|
@ -1,16 +1,15 @@
|
|||
"use server"
|
||||
import { Genre, Story } from "@prisma/client"
|
||||
import { StoryWithGenres } from "app/story/page"
|
||||
import { Genre } from "@prisma/client"
|
||||
import prisma from "./db"
|
||||
import { revalidatePath } from "next/cache"
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
|
||||
export async function updateField({ string: string, number, table, column, id, pathname }: { string?: string, number?: number, table: string, column: string, id: number, pathname: string }) {
|
||||
export async function updateField({ datum, table, column, id, pathname }: { datum?: string | number | Genre[], table: string, column: string, id: number, pathname: string }) {
|
||||
const res = await prisma[table].update({
|
||||
where: { id },
|
||||
data: {
|
||||
[column]: string ?? number
|
||||
[column]: datum
|
||||
}
|
||||
})
|
||||
console.log(`updated record in ${table}: ${JSON.stringify(res)}`)
|
||||
|
@ -18,3 +17,14 @@ export async function updateField({ string: string, number, table, column, id, p
|
|||
redirect(pathname)
|
||||
}
|
||||
|
||||
export async function updateGenres({ genres, table, id, pathname }: { genres: { id: number }[], table: string, id: number, pathname: string }) {
|
||||
const res = await prisma[table].update({
|
||||
where: { id },
|
||||
data: {
|
||||
genres: { set: genres }
|
||||
}
|
||||
})
|
||||
console.log(`updated record in ${table}: ${JSON.stringify(res)}`)
|
||||
revalidatePath(pathname)
|
||||
redirect(pathname)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { selectCol } from "app/ui/tables/selectColumn"
|
|||
import NumberInputCell from "app/ui/tables/inputs/numberInput"
|
||||
import { formSchema } from "app/ui/forms/story"
|
||||
import { TextInputCell } from "app/ui/tables/inputs/textInput"
|
||||
import GenrePickerInputCell from "app/ui/tables/inputs/genrePickerInput"
|
||||
const columnHelper = createColumnHelper<StoryWithGenres>()
|
||||
|
||||
export const columns: ColumnDef<StoryWithGenres>[] = [
|
||||
|
@ -50,11 +51,13 @@ export const columns: ColumnDef<StoryWithGenres>[] = [
|
|||
}
|
||||
},
|
||||
columnHelper.accessor("genres", {
|
||||
cell: props => {
|
||||
const genres = props.getValue()
|
||||
return <GenreBadges genres={genres} />
|
||||
},
|
||||
filterFn: "arrIncludes"
|
||||
// cell: props => {
|
||||
// const genres = props.getValue()
|
||||
// return <GenreBadges genres={genres} />
|
||||
// },
|
||||
cell: GenrePickerInputCell,
|
||||
filterFn: "arrIncludes",
|
||||
meta: {}
|
||||
//TODO - write custom filter function, to account for an array of objects
|
||||
}),
|
||||
|
||||
|
|
|
@ -15,15 +15,15 @@ export type StoryWithGenres = Story & { genres: Array<Genre> }
|
|||
export default async function Page() {
|
||||
const genres = await getGenres()
|
||||
const storiesWithGenres: Array<StoryWithGenres> = await getStoriesWithGenres()
|
||||
const pubsWithGenres = await getPubsWithGenres()
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="container mx-auto">
|
||||
<DataTable columns={columns} data={storiesWithGenres} tableName="story">
|
||||
<DataTable columns={columns} data={storiesWithGenres} tableName="story"
|
||||
genres={genres}
|
||||
>
|
||||
<CreateStoryDialog genres={genres} />
|
||||
{/* TODO - EDIT STORY DIALOG */}
|
||||
</DataTable>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -3,26 +3,9 @@ import { CellContext, ColumnDef, createColumnHelper } from "@tanstack/react-tabl
|
|||
import { ArrowUpDown } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { SubComplete } from "./page"
|
||||
import { actions } from "app/ui/tables/actions"
|
||||
import { selectCol } from "app/ui/tables/selectColumn"
|
||||
import EditSubmissionDialog from "./edit"
|
||||
|
||||
|
||||
const EditSubCell = (props: CellContext<any, any>) => {
|
||||
// return <EditSubmissionDialog
|
||||
// stories={props.table.options.meta.stories}
|
||||
// pubs={props.table.options.meta.pubs}
|
||||
// responses={props.table.options.meta.responses}
|
||||
// defaults={props.row.original}
|
||||
// >{
|
||||
// props.getValue() instanceof Date ?
|
||||
// <p className="w-full text-center">
|
||||
// {props.getValue().toLocaleDateString()}
|
||||
// </p>
|
||||
// : <p className="w-full text-left">{props.getValue()}</p>
|
||||
//
|
||||
// }</EditSubmissionDialog>
|
||||
}
|
||||
|
||||
export const columns: ColumnDef<SubComplete>[] = [
|
||||
selectCol,
|
||||
|
|
|
@ -17,7 +17,7 @@ type CreateSubDefaults = {
|
|||
respoonseId: number
|
||||
}
|
||||
|
||||
export default function CreateSubmissionDialog({ stories, pubs, responses, defaults }: ComponentProps<"div"> & { stories: Story[], pubs: Pub[], responses: Response[], defaults: CreateSubDefaults }) {
|
||||
export default function CreateSubmissionDialog({ stories, pubs, responses, defaults }: ComponentProps<"div"> & { stories: Story[], pubs: Pub[], responses: Response[], defaults?: CreateSubDefaults }) {
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
|
|
|
@ -15,12 +15,14 @@ export default async function Page() {
|
|||
const stories = await getStories()
|
||||
const pubs = await getPubs()
|
||||
const responses = await getResponses()
|
||||
const genres = await getGenres()
|
||||
return (
|
||||
<div className="container">
|
||||
<DataTable data={subs} columns={columns} tableName="sub"
|
||||
stories={stories}
|
||||
pubs={pubs}
|
||||
responses={responses}
|
||||
genres={genres}
|
||||
>
|
||||
<CreateSubmissionDialog
|
||||
stories={stories}
|
||||
|
|
|
@ -862,10 +862,6 @@ body {
|
|||
height: fit-content;
|
||||
}
|
||||
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.h-px {
|
||||
height: 1px;
|
||||
}
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import { FormItem, FormControl, FormLabel } from "@/components/ui/form"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
export default function GenreCheckbox({ field, item }) {
|
||||
return (
|
||||
<FormItem
|
||||
key={item.id}
|
||||
className="flex flex-row items-start space-x-3 space-y-0"
|
||||
>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value?.includes(item.id)}
|
||||
onCheckedChange={(checked) => {
|
||||
return checked
|
||||
? field.onChange([...field.value, item.id])
|
||||
: field.onChange(
|
||||
field.value?.filter(
|
||||
(value) => value !== item.id
|
||||
)
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="text-sm font-normal">
|
||||
{item.name}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
|
||||
|
||||
)
|
||||
}
|
|
@ -4,9 +4,11 @@ import { Button } from "@/components/ui/button"
|
|||
import { Badge } from "@/components/ui/badge"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { cn } from "@/lib/utils"
|
||||
import GenreCheckbox from "./genreCheckbox"
|
||||
import { ComponentProps } from "react"
|
||||
import { Genre } from "@prisma/client"
|
||||
import { UseFormReturn } from "react-hook-form"
|
||||
|
||||
export default function GenrePicker({ genres, form }) {
|
||||
export default function GenrePicker({ genres, form }: ComponentProps<"div"> & { genres: Genre[], form: UseFormReturn }) {
|
||||
return (
|
||||
<Popover modal={true}>
|
||||
<FormField
|
||||
|
|
|
@ -46,8 +46,7 @@ import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, Di
|
|||
import pluralize from "app/lib/pluralize"
|
||||
import { updateField } from "app/lib/update"
|
||||
import { tableNameToItemName } from "app/lib/nameMaps"
|
||||
import { Pub, Response, Story } from "@prisma/client"
|
||||
import { response } from "express"
|
||||
import { Genre, Pub, Response, Story } from "@prisma/client"
|
||||
|
||||
export interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[]
|
||||
|
@ -62,8 +61,9 @@ export function DataTable<TData, TValue>({
|
|||
tableName,
|
||||
stories,
|
||||
pubs,
|
||||
responses
|
||||
}: DataTableProps<TData, TValue> & ComponentProps<"div"> & { tableName: string, stories?: Story[], pubs?: Pub[], responses?: Response[] }) {
|
||||
responses,
|
||||
genres
|
||||
}: DataTableProps<TData, TValue> & ComponentProps<"div"> & { tableName: string, stories?: Story[], pubs?: Pub[], responses?: Response[], genres?: Genre[] }) {
|
||||
//STATE
|
||||
const [sorting, setSorting] = useState<SortingState>([])
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(
|
||||
|
@ -97,7 +97,8 @@ export function DataTable<TData, TValue>({
|
|||
pathname,
|
||||
stories,
|
||||
pubs,
|
||||
responses
|
||||
responses,
|
||||
genres
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
"use client"
|
||||
import { FormField, FormItem, FormLabel, FormMessage, FormControl, Form } from "@/components/ui/form"
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { ComponentProps, useState } from "react"
|
||||
import { useForm, UseFormReturn } from "react-hook-form"
|
||||
import { CellContext } from "@tanstack/react-table"
|
||||
import { z } from "zod"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { toast } from "@/components/ui/use-toast"
|
||||
import GenreBadges from "app/ui/genreBadges"
|
||||
import { updateField, updateGenres } from "app/lib/update"
|
||||
export default function GenrePickerInputCell(props: CellContext<any, any>) {
|
||||
|
||||
|
||||
|
||||
const table = props.table.options.meta.tableName
|
||||
const pathname = props.table.options.meta.pathname
|
||||
const id = props.row.original.id
|
||||
const column = props.column.id
|
||||
const value = props.cell.getValue()
|
||||
const genres = props.table.options.meta.genres
|
||||
const [isActive, setIsActive] = useState(false)
|
||||
|
||||
async function onSubmit({ genres }: { genres: number[] }) {
|
||||
const genresArray = genres.map((e) => { return { id: e } })
|
||||
toast({
|
||||
title: "You submitted the following values:",
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(genresArray, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
const res = await updateGenres({
|
||||
id,
|
||||
table,
|
||||
genres: genresArray,
|
||||
pathname
|
||||
})
|
||||
setIsActive(false)
|
||||
}
|
||||
|
||||
function onErrors(errors) {
|
||||
toast({
|
||||
title: "You have errors",
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(errors, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
})
|
||||
console.log(JSON.stringify(errors))
|
||||
}
|
||||
const formSchema = z.object({
|
||||
genres: z.array(z.number())
|
||||
})
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
genres: value.map(e => e.id)
|
||||
}
|
||||
})
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit, onErrors)} id="editGenresForm">
|
||||
|
||||
<Popover modal={true}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="genres"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<PopoverTrigger>
|
||||
{value.length > 0 ? <GenreBadges genres={value} /> : <Button variant="ghost">Add genres</Button>
|
||||
}
|
||||
</PopoverTrigger>
|
||||
|
||||
|
||||
|
||||
<PopoverContent align="start">
|
||||
{genres.map((item) => (
|
||||
< FormField
|
||||
key={item.id}
|
||||
control={form.control}
|
||||
name="genres"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem
|
||||
key={item.id}
|
||||
className="flex flex-row items-start space-x-3 space-y-0"
|
||||
>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value?.includes(item.id)}
|
||||
onCheckedChange={(checked) => {
|
||||
console.log(field.value)
|
||||
return checked
|
||||
? field.onChange([...field.value, item.id])
|
||||
: field.onChange(
|
||||
field.value?.filter(
|
||||
(value) => value !== item.id
|
||||
)
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="text-sm font-normal">
|
||||
{item.name}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<Button variant="ghost" form="editGenresForm">Submit</Button>
|
||||
<Button variant="link" className="p-0" onClick={() => form.setValue("genres", [])}>Clear</Button>
|
||||
</PopoverContent>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</Popover>
|
||||
|
||||
</form>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -17,7 +17,6 @@ export default function NumberInputCell(props: CellContext<any, any>) {
|
|||
const column = props.column.id
|
||||
const pathname = props.table.options.meta.pathname
|
||||
const value = props.cell.getValue()
|
||||
console.log(`|${value}|`)
|
||||
const formSchema = props.column.columnDef.meta.formSchema.pick({ [column]: true })
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
|
|
|
@ -38,7 +38,7 @@ export function TextInputCell(props: CellContext<any, any>) {
|
|||
const res = await updateField({
|
||||
id,
|
||||
table,
|
||||
string: value[column],
|
||||
datum: value[column],
|
||||
column,
|
||||
pathname
|
||||
})
|
||||
|
@ -59,7 +59,7 @@ export function TextInputCell(props: CellContext<any, any>) {
|
|||
return (
|
||||
<div
|
||||
onDoubleClick={() => setIsActive(prev => !prev)}
|
||||
className="w-full h-fit flex items-center justify-center"
|
||||
className="w-full h-fit flex items-center justify-left"
|
||||
tabIndex={0}
|
||||
onKeyDown={e => {
|
||||
if (e.code === "Enter" && !isActive) {
|
||||
|
|
Loading…
Reference in New Issue