Change mode toggle. Add type edit
This commit is contained in:
46
src/actions/items/commissions/types/updateType.ts
Normal file
46
src/actions/items/commissions/types/updateType.ts
Normal file
@ -0,0 +1,46 @@
|
||||
"use server"
|
||||
|
||||
import prisma from "@/lib/prisma"
|
||||
import { commissionTypeSchema } from "@/schemas/commissionType"
|
||||
import * as z from "zod/v4"
|
||||
|
||||
export async function updateCommissionType(
|
||||
id: string,
|
||||
rawData: z.infer<typeof commissionTypeSchema>
|
||||
) {
|
||||
const data = commissionTypeSchema.parse(rawData)
|
||||
|
||||
const updated = await prisma.commissionType.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
options: {
|
||||
deleteMany: {},
|
||||
create: data.options?.map((opt, index) => ({
|
||||
option: { connect: { id: opt.optionId } },
|
||||
price: opt.price ?? null,
|
||||
pricePercent: opt.pricePercent ?? null,
|
||||
priceRange: opt.priceRange ?? null,
|
||||
sortIndex: index,
|
||||
})),
|
||||
},
|
||||
extras: {
|
||||
deleteMany: {},
|
||||
create: data.extras?.map((ext, index) => ({
|
||||
extra: { connect: { id: ext.extraId } },
|
||||
price: ext.price ?? null,
|
||||
pricePercent: ext.pricePercent ?? null,
|
||||
priceRange: ext.priceRange ?? null,
|
||||
sortIndex: index,
|
||||
})),
|
||||
},
|
||||
},
|
||||
include: {
|
||||
options: true,
|
||||
extras: true,
|
||||
},
|
||||
})
|
||||
|
||||
return updated
|
||||
}
|
34
src/app/items/commissions/types/[id]/edit/page.tsx
Normal file
34
src/app/items/commissions/types/[id]/edit/page.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import EditTypeForm from "@/components/items/commissions/types/EditTypeForm";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function CommissionTypesEditPage({ params }: { params: { id: string } }) {
|
||||
const { id } = await params;
|
||||
const commissionType = await prisma.commissionType.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
include: {
|
||||
options: { include: { option: true }, orderBy: { sortIndex: "asc" } },
|
||||
extras: { include: { extra: true }, orderBy: { sortIndex: "asc" } },
|
||||
},
|
||||
})
|
||||
const options = await prisma.commissionOption.findMany({
|
||||
orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
|
||||
});
|
||||
const extras = await prisma.commissionExtra.findMany({
|
||||
orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
|
||||
})
|
||||
|
||||
if (!commissionType) {
|
||||
return <div>Type not found</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex gap-4 justify-between pb-8">
|
||||
<h1 className="text-2xl font-bold mb-4">New Commission Type</h1>
|
||||
</div>
|
||||
<EditTypeForm type={commissionType} allOptions={options} allExtras={extras} />
|
||||
</div>
|
||||
);
|
||||
}
|
@ -8,7 +8,7 @@ import { useEffect, useState } from "react"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
|
||||
|
||||
const modes = ["light", "dark"] as const
|
||||
const accents = ["zinc", "red", "rose", "orange", "green", "blue", "yellow", "violet", "default"] as const
|
||||
const accents = ["zinc", "red", "rose", "orange", "green", "blue", "yellow", "violet"] as const
|
||||
|
||||
const modeIcons = {
|
||||
light: <Sun className="h-4 w-4" />,
|
||||
@ -16,7 +16,7 @@ const modeIcons = {
|
||||
}
|
||||
|
||||
export default function ModeToggle() {
|
||||
const { setTheme, theme, resolvedTheme } = useTheme()
|
||||
const { setTheme, theme } = useTheme()
|
||||
const [mode, setMode] = useState("dark")
|
||||
const [accent, setAccent] = useState("violet")
|
||||
|
||||
@ -33,6 +33,17 @@ export default function ModeToggle() {
|
||||
setTheme(fullTheme)
|
||||
}
|
||||
|
||||
const accentColorMap: Record<string, string> = {
|
||||
zinc: "text-zinc-600",
|
||||
red: "text-red-600",
|
||||
rose: "text-rose-600",
|
||||
orange: "text-orange-600",
|
||||
green: "text-green-600",
|
||||
blue: "text-blue-600",
|
||||
yellow: "text-yellow-600",
|
||||
violet: "text-violet-600",
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-4 items-center">
|
||||
<Select
|
||||
@ -71,7 +82,7 @@ export default function ModeToggle() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{accents.map((a) => (
|
||||
<SelectItem key={a} value={a} className={cn(`text-${a}-600`)}>
|
||||
<SelectItem key={a} value={a} className={cn(accentColorMap[a])}>
|
||||
{a.charAt(0).toUpperCase() + a.slice(1)}
|
||||
</SelectItem>
|
||||
))}
|
||||
|
@ -12,6 +12,11 @@ export default function TopNav() {
|
||||
<Link href="/">Home</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink asChild className={navigationMenuTriggerStyle()}>
|
||||
<Link href="/items/commissions/types">CommissionTypes</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
);
|
||||
|
105
src/components/items/commissions/types/EditTypeForm.tsx
Normal file
105
src/components/items/commissions/types/EditTypeForm.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
"use client"
|
||||
|
||||
import { updateCommissionType } from "@/actions/items/commissions/types/updateType";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { CommissionExtra, CommissionOption, CommissionType, CommissionTypeExtra, CommissionTypeOption } from "@/generated/prisma";
|
||||
import { commissionTypeSchema } from "@/schemas/commissionType";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import * as z from "zod/v4";
|
||||
import { CommissionExtraField } from "./form/CommissionExtraField";
|
||||
import { CommissionOptionField } from "./form/CommissionOptionField";
|
||||
|
||||
type CommissionTypeWithConnections = CommissionType & {
|
||||
options: (CommissionTypeOption & { option: CommissionOption })[]
|
||||
extras: (CommissionTypeExtra & { extra: CommissionExtra })[]
|
||||
}
|
||||
|
||||
type Props = {
|
||||
type: CommissionTypeWithConnections
|
||||
allOptions: CommissionOption[],
|
||||
allExtras: CommissionExtra[],
|
||||
}
|
||||
|
||||
export default function EditTypeForm({ type, allOptions, allExtras }: Props) {
|
||||
const router = useRouter();
|
||||
const form = useForm<z.infer<typeof commissionTypeSchema>>({
|
||||
resolver: zodResolver(commissionTypeSchema),
|
||||
defaultValues: {
|
||||
name: type.name,
|
||||
description: type.description ?? "",
|
||||
options: type.options.map((o) => ({
|
||||
optionId: o.optionId,
|
||||
price: o.price ?? undefined,
|
||||
pricePercent: o.pricePercent ?? undefined,
|
||||
priceRange: o.priceRange ?? undefined,
|
||||
})),
|
||||
extras: type.extras.map((e) => ({
|
||||
extraId: e.extraId,
|
||||
price: e.price ?? undefined,
|
||||
pricePercent: e.pricePercent ?? undefined,
|
||||
priceRange: e.priceRange ?? undefined,
|
||||
})),
|
||||
},
|
||||
})
|
||||
|
||||
async function onSubmit(values: z.infer<typeof commissionTypeSchema>) {
|
||||
try {
|
||||
await updateCommissionType(type.id, values)
|
||||
toast.success("Commission type updated.")
|
||||
router.push("/items/commissions/types")
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
toast("Failed to create commission type.")
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8">
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>The name of the commission type.</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="description"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>Optional description.</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<CommissionOptionField options={allOptions} />
|
||||
<CommissionExtraField extras={allExtras} />
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<Button type="submit">Submit</Button>
|
||||
<Button type="reset" variant="secondary" onClick={() => router.back()}>Cancel</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
import { updateCommissionTypeSortOrder } from "@/actions/items/commissions/types/updateCommissionTypeSortOrder";
|
||||
import SortableItemCard from "@/components/drag/SortableItemCard";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { CommissionExtra, CommissionOption, CommissionType, CommissionTypeExtra, CommissionTypeOption } from "@/generated/prisma";
|
||||
import {
|
||||
closestCenter,
|
||||
@ -17,6 +17,8 @@ import {
|
||||
rectSortingStrategy,
|
||||
SortableContext
|
||||
} from "@dnd-kit/sortable";
|
||||
import { PencilIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type CommissionTypeWithItems = CommissionType & {
|
||||
@ -66,6 +68,7 @@ export default function ListTypes({ types }: { types: CommissionTypeWithItems[]
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl truncate">{type.name}</CardTitle>
|
||||
<CardDescription>{type.description}</CardDescription>
|
||||
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col justify-start gap-4">
|
||||
<div>
|
||||
@ -103,6 +106,15 @@ export default function ListTypes({ types }: { types: CommissionTypeWithItems[]
|
||||
</ul>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex items-center justify-between">
|
||||
<Link
|
||||
href={`/items/commissions/types/${type.id}/edit`}
|
||||
className="text-sm text-primary hover:underline flex items-center gap-1"
|
||||
>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
Edit
|
||||
</Link>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</SortableItemCard >
|
||||
))}
|
||||
|
@ -24,7 +24,7 @@ import {
|
||||
SortableContext,
|
||||
verticalListSortingStrategy
|
||||
} from "@dnd-kit/sortable"
|
||||
import { useState } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useFieldArray, useFormContext } from "react-hook-form"
|
||||
import { ComboboxCreateable } from "./ComboboxCreateable"
|
||||
|
||||
@ -33,12 +33,17 @@ type Props = {
|
||||
}
|
||||
|
||||
export function CommissionExtraField({ extras: initialExtras }: Props) {
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const { control } = useFormContext()
|
||||
const { fields, append, remove, move } = useFieldArray({
|
||||
control,
|
||||
name: "extras",
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
const [extras, setExtras] = useState(initialExtras)
|
||||
|
||||
const sensors = useSensors(useSensor(PointerSensor))
|
||||
@ -58,6 +63,8 @@ export function CommissionExtraField({ extras: initialExtras }: Props) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!mounted) return null
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold">Extras</h3>
|
||||
|
@ -24,7 +24,7 @@ import {
|
||||
SortableContext,
|
||||
verticalListSortingStrategy
|
||||
} from "@dnd-kit/sortable"
|
||||
import { useState } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useFieldArray, useFormContext } from "react-hook-form"
|
||||
import { ComboboxCreateable } from "./ComboboxCreateable"
|
||||
|
||||
@ -33,12 +33,17 @@ type Props = {
|
||||
}
|
||||
|
||||
export function CommissionOptionField({ options: initialOptions }: Props) {
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const { control } = useFormContext()
|
||||
const { fields, append, remove, move } = useFieldArray({
|
||||
control,
|
||||
name: "options",
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
const [options, setOptions] = useState(initialOptions)
|
||||
|
||||
const sensors = useSensors(useSensor(PointerSensor))
|
||||
@ -58,6 +63,8 @@ export function CommissionOptionField({ options: initialOptions }: Props) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!mounted) return null
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold">Options</h3>
|
||||
|
Reference in New Issue
Block a user