From 6e501aa75f716d395532a9b09745aaff5c6326ce Mon Sep 17 00:00:00 2001 From: andrzej Date: Thu, 26 Sep 2024 12:37:52 +0200 Subject: [PATCH] begin implementation of edit story functionality --- prisma/dev.db | Bin 69632 -> 69632 bytes src/app/lib/create.ts | 12 ++--- src/app/lib/edit.ts | 15 ------ src/app/lib/update.ts | 74 ++++++++++++++++++++---------- src/app/lib/validate.ts | 24 ++++++---- src/app/publication/create.tsx | 2 +- src/app/publication/edit.tsx | 40 ++++++++++++++++ src/app/story/create.tsx | 2 +- src/app/story/edit.tsx | 28 +++++++++++ src/app/submission/create.tsx | 12 +---- src/app/tailwind.css | 21 +++++++++ src/app/ui/forms/pub.tsx | 8 ++-- src/app/ui/forms/story.tsx | 12 +++-- src/app/ui/forms/sub.tsx | 2 +- src/app/ui/tables/contextMenu.tsx | 12 ++--- src/app/ui/tables/data-table.tsx | 27 +++++++---- 16 files changed, 198 insertions(+), 93 deletions(-) delete mode 100644 src/app/lib/edit.ts create mode 100644 src/app/publication/edit.tsx create mode 100644 src/app/story/edit.tsx diff --git a/prisma/dev.db b/prisma/dev.db index 168826f4e4b51091b9045b732edc724d44f47c10..e870c4b704b5cea7c66597e7de37bbb807107b97 100644 GIT binary patch delta 762 zcmX9(OH30{6rJ0dN13+sE>N@rgAUN9#ZN*C8$P8XF%?@%NsWoR&_Ns7;7H3SZj_=d zTp-B!Sr~;FFflP9q#72+xL_pG7fL4F%r`ZoVSp2fl-&7~}u2 z6<_cTzJfoov2ZXYAy;adP^W;Nvu~m0;uo!~hKm}`o35$G3LeE@Jj_G9mUAvD3xx*l ztYR+Ajwa=lBsjdVH>r=g21xFMJw2DxJxS6v&aEr#37ZOQSIPjCzW;CW0JDqHH2v#1E* zNV0AsF=^_H`;R;lm@EZE8B~>ZSmikLTBe{>Ma3X)3Jh2$RiwE2d@-a@%rXeDF3XxQ zcNQBXEMoZ+F4SpoJK$A(im$OqbARJQoWyUqiS76U-{LFWMgwQC2M4hamvI))Q7#cL zI3=)hMQs+7-8M`0Z6#KlZDwi+Z_#jnnIr?Kfu@o>-eI#N=*11{ilu6z%VrAwU^VP< zf)psZ5|P68YFLweXgJLBttR&}KEv<$6+fauS=zCK(KgAbh4pO~YKJXUn`v?8w`IV> zpx4GK3*((OnlE<))s{+jlb1LVv9YUA+zn|C&&(SSw7Q^g{kRKGI+VQUfGhyymAnGl U(vSz1=v{c;qV^*14`$i#sB~S delta 752 zcmY*VO-NKx6u#&E%*>ma_f9%8QS;2}IFIqCLPpypVrDTjgryllt9p|U6$YQqQ-=ac zt8Hn%%7vs783lsK6tjqE;i?w>h+qUFxU*F)TDSjsv>#o9Eu74uB57$aiXa1s4O)skM z%$?!0=Kn?8BqFrSTV~#JdULrvE3CpQQZE?tR!7!$EP86d%-VfS3k!APuIc<+Zui{} z@XW2A${elM*`^Nh_Jw|np3K@A+Gkr19kJ>8fiY{a*UHjkG=blUBE;cGue&-GOTctz zjwbtUXM`sIT<#e%uUbdEpV)7p5D|ABlfzPRjFh42dZ%N7Ih?~y4zp0!*nqCN5N^0h zWJNMW1#}%B5-$iTIV1uIajBe%aePODAh$3@Ou3I2YE|4LDj3Yh3!>p}E<{`TEE#?@ znNu3D4U9kUgEHkg9QoU8LtEYQf2e+uGjG{>$PYT z55zo@0-zf;c!T&@zT^BCCY=D2Dw!P$KufVgg~I^N#dZze Fz%RE#vmpQg diff --git a/src/app/lib/create.ts b/src/app/lib/create.ts index d0f2d2b..fa99386 100644 --- a/src/app/lib/create.ts +++ b/src/app/lib/create.ts @@ -11,7 +11,7 @@ import { prepGenreData, prepStoryData } from "./validate" //TODO - data validation, error handling, unauthorized access handling -export async function createStory(data: Story & { genres: number[] }): Promise { +export async function createStory(data: Story & { genres: number[] }): Promise<{ success: string }> { // will return undefined if middleware authorization fails "use server" try { @@ -27,7 +27,7 @@ export async function createStory(data: Story & { genres: number[] }): Promise { +export async function createPub(data: Pub & { genres: number[] }): Promise<{ success: string }> { "use server" //prepare data const pubData = { @@ -67,7 +67,7 @@ export async function createPub(data: Pub & { genres: number[] }): Promise { +export async function createSub(data: Sub): Promise { "use server" try { subSchema.parse(data) @@ -86,6 +86,6 @@ export async function createSub(data: Sub): Promise { return res } catch (error) { console.error(error) - return undefined + return false } } diff --git a/src/app/lib/edit.ts b/src/app/lib/edit.ts deleted file mode 100644 index 273e5b3..0000000 --- a/src/app/lib/edit.ts +++ /dev/null @@ -1,15 +0,0 @@ -"use server" -import prisma from "./db" -import { revalidatePath } from "next/cache" -import { redirect } from "next/navigation" -import { SubForm } from "app/ui/forms/sub" - -export async function editSubmission(data: SubForm) { - const res = await prisma.sub.update({ - where: { id: data.id }, - data - }) - console.log(`updated ${data} to ${res}`) - revalidatePath("/submission") - redirect("/submission") -} diff --git a/src/app/lib/update.ts b/src/app/lib/update.ts index e9c38be..1ec01b5 100644 --- a/src/app/lib/update.ts +++ b/src/app/lib/update.ts @@ -1,5 +1,6 @@ "use server" -import { Genre, Story, Sub } from "@prisma/client" +import { prepGenreData, prepPubData, prepStoryData } from "./validate" +import { Genre, Pub, Story, Sub } from "@prisma/client" import prisma from "./db" import { revalidatePath } from "next/cache" import { redirect } from "next/navigation" @@ -21,7 +22,7 @@ export async function updateField({ datum, table, column, id, pathname }: { datu return res } catch (error) { console.error(error) - return undefined + return false } } @@ -39,11 +40,11 @@ export async function updateGenres({ genres, table, id, pathname }: { genres: { return res } catch (error) { console.error(error) - return undefined + return false } } -export async function updateSub(data: Sub): Promise { +export async function updateSub(data: Sub): Promise { "use server" try { subSchema.parse(data) @@ -52,35 +53,62 @@ export async function updateSub(data: Sub): Promise { return res } catch (error) { console.error(error) - return undefined + return false } } -export async function updateStory(data: Story & { genres: number[] }): Promise { +export async function updateStory(data: Story & { genres: number[] }): Promise { "use server" - //prepare data - const genresArray = data.genres.map((e) => { return { id: e } }) - const storyData = { - title: data.title, - word_count: data.word_count, - } - //prepare schemas - const schema = storySchema.omit({ id: true, genres: true }) - const genreSchema = z.object({ id: z.number() }) - const genresSchema = z.array(genreSchema) - try { - //validate - schema.safeParse(storyData) - genresSchema.safeParse(genresArray) + try { + //prep and validate + const storyData = await prepStoryData(data) + const genresArray = await prepGenreData(data.genres) + //submit + const res = prisma.story.update({ + where: { id: data.id }, + data: storyData + }) + await prisma.story.update({ + where: { id: data.id }, + data: { + genres: { set: genresArray } + } + }) + return res } catch (error) { console.error(error) - return undefined + return false + } +} + + +export async function updatePub(data: Pub & { genres: number[] }): Promise { + "use server" + + try { + //prep and validate + const pubData = await prepPubData + (data) + const genresArray = await prepGenreData(data.genres) + //submit + const res = prisma.pub.update({ + where: { id: data.id }, + data: pubData + }) + await prisma.pub.update({ + where: { id: data.id }, + data: { + genres: { set: genresArray } + } + }) + return res + } catch (error) { + console.error(error) + return false } } - - diff --git a/src/app/lib/validate.ts b/src/app/lib/validate.ts index 8b1bdb1..9aa04a7 100644 --- a/src/app/lib/validate.ts +++ b/src/app/lib/validate.ts @@ -1,26 +1,30 @@ import { z } from "zod"; import { storySchema } from "app/ui/forms/schemas"; -import { Story } from "@prisma/client"; +import { Pub, Story } from "@prisma/client"; +import { pubSchema } from "app/ui/forms/schemas"; //schemas -const schema = storySchema.omit({ id: true, genres: true }) +const storySchemaTrimmed = storySchema.omit({ genres: true }) +const pubSchemaTrimmed = pubSchema.omit({ genres: true }) const genreSchema = z.object({ id: z.number() }) const genresSchema = z.array(genreSchema) export async function prepStoryData(data: Story & { genres: number[] }): Promise<{ title: string, word_count: number }> { - const storyData = { - title: data.title, - word_count: data.word_count, - } - //prepare schemas - + const storyData = structuredClone(data) + delete storyData.genres //throw an error if validation fails - schema.safeParse(storyData) - + storySchemaTrimmed.safeParse(storyData) return storyData } +export async function prepPubData(data: Pub & { genres: number[] }): Promise { + const pubData = structuredClone(data) + delete pubData.genres + pubSchemaTrimmed.safeParse(pubData) + return pubData +} + export async function prepGenreData(data: number[]): Promise<{ id: number }[]> { "use server" diff --git a/src/app/publication/create.tsx b/src/app/publication/create.tsx index a50db64..be438a7 100644 --- a/src/app/publication/create.tsx +++ b/src/app/publication/create.tsx @@ -29,7 +29,7 @@ export default function CreatePubDialog({ genres }: ComponentProps<"div"> & { ge New publication Create an entry for a new publication i.e. a place you intend to submit stories to. - + diff --git a/src/app/publication/edit.tsx b/src/app/publication/edit.tsx new file mode 100644 index 0000000..d25c862 --- /dev/null +++ b/src/app/publication/edit.tsx @@ -0,0 +1,40 @@ +"use client" +import { Dialog, DialogHeader, DialogTrigger, DialogContent, DialogClose, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { ComponentProps } from "react"; +import { Genre } from "@prisma/client"; +import { createPub } from "app/lib/create"; +import PubForm from "app/ui/forms/pub"; +import { Plus } from "lucide-react"; +import { useState } from "react"; + +export default function CreatePubDialog({ genres }: ComponentProps<"div"> & { genres: Genre[] }) { + + const [isOpen, setIsOpen] = useState(false) + function closeDialog() { + setIsOpen(false) + } + + return ( + + + + + + + + Edit publication + Modify an entry for an existing publication. + + + + + + + + + ) +} diff --git a/src/app/story/create.tsx b/src/app/story/create.tsx index d3cba95..630b6a3 100644 --- a/src/app/story/create.tsx +++ b/src/app/story/create.tsx @@ -28,7 +28,7 @@ export default function CreateStoryDialog({ genres }: ComponentProps<"div"> & { New story Create an entry for a new story i.e. a thing you intend to submit for publication. - + diff --git a/src/app/story/edit.tsx b/src/app/story/edit.tsx new file mode 100644 index 0000000..d64b575 --- /dev/null +++ b/src/app/story/edit.tsx @@ -0,0 +1,28 @@ +"use client" +import { createStory } from "app/lib/create" +import { Dialog, DialogHeader, DialogTrigger, DialogContent, DialogClose, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { ComponentProps, useState } from "react"; +import { Genre, Story } from "@prisma/client"; +import StoryForm from "app/ui/forms/story"; +import { Plus } from "lucide-react"; + + +export default function EditStoryDialog({ genres, closeDialog, defaults }: ComponentProps<"div"> & { genres: Genre[], closeDialog: () => void, defaults: Story }) { + + + return ( + <> + + Edit story + Create an entry for a new story i.e. a thing you intend to submit for publication. + + + + + + + + ) +} + diff --git a/src/app/submission/create.tsx b/src/app/submission/create.tsx index 0c26631..0c4dd7c 100644 --- a/src/app/submission/create.tsx +++ b/src/app/submission/create.tsx @@ -10,16 +10,8 @@ import { Plus } from "lucide-react"; import { useState } from "react"; -type CreateSubDefaults = { - subId?: number, - storyId: number, - pubId: number, - submitted: Date, - responded: Date, - 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 }: ComponentProps<"div"> & { stories: Story[], pubs: Pub[], responses: Response[] }) { const [isOpen, setIsOpen] = useState(false) function closeDialog() { @@ -39,7 +31,7 @@ export default function CreateSubmissionDialog({ stories, pubs, responses, defau New submission Create an entry for a new story i.e. a thing you intend to submit for publication. - + diff --git a/src/app/tailwind.css b/src/app/tailwind.css index 3e4b5f8..70a70ce 100644 --- a/src/app/tailwind.css +++ b/src/app/tailwind.css @@ -777,6 +777,14 @@ body { margin-top: 1.5rem; } +.mt-auto { + margin-top: auto; +} + +.mb-4 { + margin-bottom: 1rem; +} + .block { display: block; } @@ -879,6 +887,10 @@ body { height: 100vh; } +.h-5\/6 { + height: 83.333333%; +} + .max-h-96 { max-height: 24rem; } @@ -964,6 +976,10 @@ body { width: 100vw; } +.w-5\/6 { + width: 83.333333%; +} + .min-w-\[8rem\] { min-width: 8rem; } @@ -1251,6 +1267,11 @@ body { border-top-right-radius: 1.5rem; } +.rounded-l-3xl { + border-top-left-radius: 1.5rem; + border-bottom-left-radius: 1.5rem; +} + .rounded-tl-3xl { border-top-left-radius: 1.5rem; } diff --git a/src/app/ui/forms/pub.tsx b/src/app/ui/forms/pub.tsx index 139df87..c0e1c7f 100644 --- a/src/app/ui/forms/pub.tsx +++ b/src/app/ui/forms/pub.tsx @@ -17,13 +17,13 @@ import { toast } from "@/components/ui/use-toast" import { randomPublicationTitle } from "app/lib/shortStoryTitleGenerator" import { ComponentProps } from "react" -import { Genre } from "@prisma/client" +import { Genre, Pub } from "@prisma/client" import GenrePicker from "./genrePicker" import { pubSchema } from "./schemas" import { useRouter } from "next/navigation" import { Ban } from "lucide-react" -export default function PubForm({ genres, createPub, className, closeDialog }: ComponentProps<"div"> & { genres: Array, createPub: (data: any) => void, closeDialog: () => void }) { +export default function PubForm({ genres, dbAction, className, closeDialog, defaults }: ComponentProps<"div"> & { genres: Array, dbAction: (data: any) => Promise<{ success: string }>, closeDialog: () => void, defaults?: Pub }) { const form = useForm>({ resolver: zodResolver(pubSchema), defaultValues: { @@ -38,9 +38,9 @@ export default function PubForm({ genres, createPub, className, closeDialog }: C async function onSubmit(values: z.infer) { try { - const res = await createPub(values) + const res = await dbAction(values) if (!res) throw new Error("something went wrong") - toast({ title: "Successfully submitted:", description: values.title }) + toast({ title: "Success!", description: res.success }) router.refresh() closeDialog() } catch (error) { diff --git a/src/app/ui/forms/story.tsx b/src/app/ui/forms/story.tsx index e5e1a52..ea3354d 100644 --- a/src/app/ui/forms/story.tsx +++ b/src/app/ui/forms/story.tsx @@ -29,23 +29,25 @@ export const formSchema = z.object({ genres: z.array(z.number()) }) -export default function StoryForm({ genres, createStory, className, closeDialog }: ComponentProps<"div"> & { genres: Array, createStory: (data: any) => void, className: string, closeDialog: () => void }) { +export default function StoryForm({ genres, dbAction, className, closeDialog, defaults }: ComponentProps<"div"> & { genres: Array, dbAction: (data: any) => Promise<{ success: string }>, className: string, closeDialog: () => void, defaults?: Story }) { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - word_count: 500, + title: defaults?.title ?? "", + word_count: defaults?.word_count ?? 500, genres: [] }, }) + console.log("DEFAULTS: " + defaults) const router = useRouter() async function onSubmit(values: z.infer) { try { - const res = await createStory(values) + const res = await dbAction(values) //server actions return undefined if middleware authentication fails - if (!res) throw new Error("something went wrong") - toast({ title: "Sucessfully submitted:", description: values.title }) + if (!res.success) throw new Error("something went wrong") + toast({ title: "Success!", description: res.success }) router.refresh() closeDialog() } catch (error) { diff --git a/src/app/ui/forms/sub.tsx b/src/app/ui/forms/sub.tsx index 989b1d1..0dd2005 100644 --- a/src/app/ui/forms/sub.tsx +++ b/src/app/ui/forms/sub.tsx @@ -40,7 +40,7 @@ import { Ban } from "lucide-react" export type SubForm = z.infer -export default function SubmissionForm({ stories, pubs, responses, defaults, closeDialog }: { stories: any, pubs: any, responses: any, defaults: any, closeDialog: () => void }) { +export default function SubmissionForm({ stories, pubs, responses, defaults, closeDialog }: { stories: any, pubs: any, responses: any, defaults?: any, closeDialog: () => void }) { const form = useForm>({ resolver: zodResolver(subSchema), defaultValues: { diff --git a/src/app/ui/tables/contextMenu.tsx b/src/app/ui/tables/contextMenu.tsx index c12e13c..2a41b25 100644 --- a/src/app/ui/tables/contextMenu.tsx +++ b/src/app/ui/tables/contextMenu.tsx @@ -19,15 +19,9 @@ export default function FormContextMenu({ table, row, openEditDialog, openDelete : "" } - { - pathname === "/submission" ? - <> - openEditDialog(row)}> - Edit - - - : "" - } + openEditDialog(row)}> + Edit + { selectedRows.length > 0 ? diff --git a/src/app/ui/tables/data-table.tsx b/src/app/ui/tables/data-table.tsx index 70dec7e..5a9ed5b 100644 --- a/src/app/ui/tables/data-table.tsx +++ b/src/app/ui/tables/data-table.tsx @@ -52,6 +52,7 @@ import EditSubmissionDialog from "app/submission/edit" import { DialogTitle } from "@radix-ui/react-dialog" import { toast } from "@/components/ui/use-toast" import { useRouter } from "next/navigation" +import EditStoryDialog from "app/story/edit" export interface DataTableProps { columns: ColumnDef[] @@ -167,13 +168,23 @@ export function DataTable({ - + {tableName === "sub" ? + + : tableName === "story" ? + < EditStoryDialog + genres={genres} + // TODO: prepare genre data so that it can be read by StoryForm + defaults={dialogRow?.original} + closeDialog={closeEditDialog} + /> + : "" + } @@ -223,7 +234,7 @@ export function DataTable({ const recordIds = rowIds.map(id => Number(table.getRow(id).original.id)) const res = await deleteRecords(recordIds, pathname) if (!res) toast({ title: "Oh dear...", description: "Failed to delete." }) - if (res) toast({ title: "Sucessfully deleted records of id:", description: JSON.stringify(recordIds) }) + if (res) toast({ title: "Successfully deleted records of id:", description: JSON.stringify(recordIds) }) router.refresh() setIsDeleteDialogVisible(false) }}>