implement genre picker cell (janky)
This commit is contained in:
parent
41951a2ac6
commit
79e3403902
BIN
prisma/dev.db
BIN
prisma/dev.db
Binary file not shown.
|
@ -1,16 +1,15 @@
|
||||||
"use server"
|
"use server"
|
||||||
import { Genre, Story } from "@prisma/client"
|
import { Genre } from "@prisma/client"
|
||||||
import { StoryWithGenres } from "app/story/page"
|
|
||||||
import prisma from "./db"
|
import prisma from "./db"
|
||||||
import { revalidatePath } from "next/cache"
|
import { revalidatePath } from "next/cache"
|
||||||
import { redirect } from "next/navigation"
|
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({
|
const res = await prisma[table].update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
[column]: string ?? number
|
[column]: datum
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.log(`updated record in ${table}: ${JSON.stringify(res)}`)
|
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)
|
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 NumberInputCell from "app/ui/tables/inputs/numberInput"
|
||||||
import { formSchema } from "app/ui/forms/story"
|
import { formSchema } from "app/ui/forms/story"
|
||||||
import { TextInputCell } from "app/ui/tables/inputs/textInput"
|
import { TextInputCell } from "app/ui/tables/inputs/textInput"
|
||||||
|
import GenrePickerInputCell from "app/ui/tables/inputs/genrePickerInput"
|
||||||
const columnHelper = createColumnHelper<StoryWithGenres>()
|
const columnHelper = createColumnHelper<StoryWithGenres>()
|
||||||
|
|
||||||
export const columns: ColumnDef<StoryWithGenres>[] = [
|
export const columns: ColumnDef<StoryWithGenres>[] = [
|
||||||
|
@ -50,11 +51,13 @@ export const columns: ColumnDef<StoryWithGenres>[] = [
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
columnHelper.accessor("genres", {
|
columnHelper.accessor("genres", {
|
||||||
cell: props => {
|
// cell: props => {
|
||||||
const genres = props.getValue()
|
// const genres = props.getValue()
|
||||||
return <GenreBadges genres={genres} />
|
// return <GenreBadges genres={genres} />
|
||||||
},
|
// },
|
||||||
filterFn: "arrIncludes"
|
cell: GenrePickerInputCell,
|
||||||
|
filterFn: "arrIncludes",
|
||||||
|
meta: {}
|
||||||
//TODO - write custom filter function, to account for an array of objects
|
//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() {
|
export default async function Page() {
|
||||||
const genres = await getGenres()
|
const genres = await getGenres()
|
||||||
const storiesWithGenres: Array<StoryWithGenres> = await getStoriesWithGenres()
|
const storiesWithGenres: Array<StoryWithGenres> = await getStoriesWithGenres()
|
||||||
const pubsWithGenres = await getPubsWithGenres()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto">
|
<div className="container mx-auto">
|
||||||
<DataTable columns={columns} data={storiesWithGenres} tableName="story">
|
<DataTable columns={columns} data={storiesWithGenres} tableName="story"
|
||||||
|
genres={genres}
|
||||||
|
>
|
||||||
<CreateStoryDialog genres={genres} />
|
<CreateStoryDialog genres={genres} />
|
||||||
{/* TODO - EDIT STORY DIALOG */}
|
|
||||||
</DataTable>
|
</DataTable>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,26 +3,9 @@ import { CellContext, ColumnDef, createColumnHelper } from "@tanstack/react-tabl
|
||||||
import { ArrowUpDown } from "lucide-react"
|
import { ArrowUpDown } from "lucide-react"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { SubComplete } from "./page"
|
import { SubComplete } from "./page"
|
||||||
import { actions } from "app/ui/tables/actions"
|
|
||||||
import { selectCol } from "app/ui/tables/selectColumn"
|
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>[] = [
|
export const columns: ColumnDef<SubComplete>[] = [
|
||||||
selectCol,
|
selectCol,
|
||||||
|
|
|
@ -17,7 +17,7 @@ type CreateSubDefaults = {
|
||||||
respoonseId: number
|
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 (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
|
|
|
@ -15,12 +15,14 @@ export default async function Page() {
|
||||||
const stories = await getStories()
|
const stories = await getStories()
|
||||||
const pubs = await getPubs()
|
const pubs = await getPubs()
|
||||||
const responses = await getResponses()
|
const responses = await getResponses()
|
||||||
|
const genres = await getGenres()
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<DataTable data={subs} columns={columns} tableName="sub"
|
<DataTable data={subs} columns={columns} tableName="sub"
|
||||||
stories={stories}
|
stories={stories}
|
||||||
pubs={pubs}
|
pubs={pubs}
|
||||||
responses={responses}
|
responses={responses}
|
||||||
|
genres={genres}
|
||||||
>
|
>
|
||||||
<CreateSubmissionDialog
|
<CreateSubmissionDialog
|
||||||
stories={stories}
|
stories={stories}
|
||||||
|
|
|
@ -862,10 +862,6 @@ body {
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-full {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-px {
|
.h-px {
|
||||||
height: 1px;
|
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 { Badge } from "@/components/ui/badge"
|
||||||
import { Checkbox } from "@/components/ui/checkbox"
|
import { Checkbox } from "@/components/ui/checkbox"
|
||||||
import { cn } from "@/lib/utils"
|
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 (
|
return (
|
||||||
<Popover modal={true}>
|
<Popover modal={true}>
|
||||||
<FormField
|
<FormField
|
||||||
|
|
|
@ -46,8 +46,7 @@ import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, Di
|
||||||
import pluralize from "app/lib/pluralize"
|
import pluralize from "app/lib/pluralize"
|
||||||
import { updateField } from "app/lib/update"
|
import { updateField } from "app/lib/update"
|
||||||
import { tableNameToItemName } from "app/lib/nameMaps"
|
import { tableNameToItemName } from "app/lib/nameMaps"
|
||||||
import { Pub, Response, Story } from "@prisma/client"
|
import { Genre, Pub, Response, Story } from "@prisma/client"
|
||||||
import { response } from "express"
|
|
||||||
|
|
||||||
export interface DataTableProps<TData, TValue> {
|
export interface DataTableProps<TData, TValue> {
|
||||||
columns: ColumnDef<TData, TValue>[]
|
columns: ColumnDef<TData, TValue>[]
|
||||||
|
@ -62,8 +61,9 @@ export function DataTable<TData, TValue>({
|
||||||
tableName,
|
tableName,
|
||||||
stories,
|
stories,
|
||||||
pubs,
|
pubs,
|
||||||
responses
|
responses,
|
||||||
}: DataTableProps<TData, TValue> & ComponentProps<"div"> & { tableName: string, stories?: Story[], pubs?: Pub[], responses?: Response[] }) {
|
genres
|
||||||
|
}: DataTableProps<TData, TValue> & ComponentProps<"div"> & { tableName: string, stories?: Story[], pubs?: Pub[], responses?: Response[], genres?: Genre[] }) {
|
||||||
//STATE
|
//STATE
|
||||||
const [sorting, setSorting] = useState<SortingState>([])
|
const [sorting, setSorting] = useState<SortingState>([])
|
||||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(
|
||||||
|
@ -97,7 +97,8 @@ export function DataTable<TData, TValue>({
|
||||||
pathname,
|
pathname,
|
||||||
stories,
|
stories,
|
||||||
pubs,
|
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 column = props.column.id
|
||||||
const pathname = props.table.options.meta.pathname
|
const pathname = props.table.options.meta.pathname
|
||||||
const value = props.cell.getValue()
|
const value = props.cell.getValue()
|
||||||
console.log(`|${value}|`)
|
|
||||||
const formSchema = props.column.columnDef.meta.formSchema.pick({ [column]: true })
|
const formSchema = props.column.columnDef.meta.formSchema.pick({ [column]: true })
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
|
|
|
@ -38,7 +38,7 @@ export function TextInputCell(props: CellContext<any, any>) {
|
||||||
const res = await updateField({
|
const res = await updateField({
|
||||||
id,
|
id,
|
||||||
table,
|
table,
|
||||||
string: value[column],
|
datum: value[column],
|
||||||
column,
|
column,
|
||||||
pathname
|
pathname
|
||||||
})
|
})
|
||||||
|
@ -59,7 +59,7 @@ export function TextInputCell(props: CellContext<any, any>) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onDoubleClick={() => setIsActive(prev => !prev)}
|
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}
|
tabIndex={0}
|
||||||
onKeyDown={e => {
|
onKeyDown={e => {
|
||||||
if (e.code === "Enter" && !isActive) {
|
if (e.code === "Enter" && !isActive) {
|
||||||
|
|
Loading…
Reference in New Issue