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" | ||||
| 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