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"
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
|
||||||
|
|
||||||
const modes = ["light", "dark"] as const
|
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 = {
|
const modeIcons = {
|
||||||
light: <Sun className="h-4 w-4" />,
|
light: <Sun className="h-4 w-4" />,
|
||||||
@ -16,7 +16,7 @@ const modeIcons = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ModeToggle() {
|
export default function ModeToggle() {
|
||||||
const { setTheme, theme, resolvedTheme } = useTheme()
|
const { setTheme, theme } = useTheme()
|
||||||
const [mode, setMode] = useState("dark")
|
const [mode, setMode] = useState("dark")
|
||||||
const [accent, setAccent] = useState("violet")
|
const [accent, setAccent] = useState("violet")
|
||||||
|
|
||||||
@ -33,6 +33,17 @@ export default function ModeToggle() {
|
|||||||
setTheme(fullTheme)
|
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 (
|
return (
|
||||||
<div className="flex gap-4 items-center">
|
<div className="flex gap-4 items-center">
|
||||||
<Select
|
<Select
|
||||||
@ -71,7 +82,7 @@ export default function ModeToggle() {
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{accents.map((a) => (
|
{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)}
|
{a.charAt(0).toUpperCase() + a.slice(1)}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
|
@ -12,6 +12,11 @@ export default function TopNav() {
|
|||||||
<Link href="/">Home</Link>
|
<Link href="/">Home</Link>
|
||||||
</NavigationMenuLink>
|
</NavigationMenuLink>
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
|
<NavigationMenuItem>
|
||||||
|
<NavigationMenuLink asChild className={navigationMenuTriggerStyle()}>
|
||||||
|
<Link href="/items/commissions/types">CommissionTypes</Link>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</NavigationMenuItem>
|
||||||
</NavigationMenuList>
|
</NavigationMenuList>
|
||||||
</NavigationMenu>
|
</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 { updateCommissionTypeSortOrder } from "@/actions/items/commissions/types/updateCommissionTypeSortOrder";
|
||||||
import SortableItemCard from "@/components/drag/SortableItemCard";
|
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 { CommissionExtra, CommissionOption, CommissionType, CommissionTypeExtra, CommissionTypeOption } from "@/generated/prisma";
|
||||||
import {
|
import {
|
||||||
closestCenter,
|
closestCenter,
|
||||||
@ -17,6 +17,8 @@ import {
|
|||||||
rectSortingStrategy,
|
rectSortingStrategy,
|
||||||
SortableContext
|
SortableContext
|
||||||
} from "@dnd-kit/sortable";
|
} from "@dnd-kit/sortable";
|
||||||
|
import { PencilIcon } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
type CommissionTypeWithItems = CommissionType & {
|
type CommissionTypeWithItems = CommissionType & {
|
||||||
@ -66,6 +68,7 @@ export default function ListTypes({ types }: { types: CommissionTypeWithItems[]
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-xl truncate">{type.name}</CardTitle>
|
<CardTitle className="text-xl truncate">{type.name}</CardTitle>
|
||||||
<CardDescription>{type.description}</CardDescription>
|
<CardDescription>{type.description}</CardDescription>
|
||||||
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col justify-start gap-4">
|
<CardContent className="flex flex-col justify-start gap-4">
|
||||||
<div>
|
<div>
|
||||||
@ -103,6 +106,15 @@ export default function ListTypes({ types }: { types: CommissionTypeWithItems[]
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</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>
|
</Card>
|
||||||
</SortableItemCard >
|
</SortableItemCard >
|
||||||
))}
|
))}
|
||||||
|
@ -24,7 +24,7 @@ import {
|
|||||||
SortableContext,
|
SortableContext,
|
||||||
verticalListSortingStrategy
|
verticalListSortingStrategy
|
||||||
} from "@dnd-kit/sortable"
|
} from "@dnd-kit/sortable"
|
||||||
import { useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { useFieldArray, useFormContext } from "react-hook-form"
|
import { useFieldArray, useFormContext } from "react-hook-form"
|
||||||
import { ComboboxCreateable } from "./ComboboxCreateable"
|
import { ComboboxCreateable } from "./ComboboxCreateable"
|
||||||
|
|
||||||
@ -33,12 +33,17 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CommissionExtraField({ extras: initialExtras }: Props) {
|
export function CommissionExtraField({ extras: initialExtras }: Props) {
|
||||||
|
const [mounted, setMounted] = useState(false)
|
||||||
const { control } = useFormContext()
|
const { control } = useFormContext()
|
||||||
const { fields, append, remove, move } = useFieldArray({
|
const { fields, append, remove, move } = useFieldArray({
|
||||||
control,
|
control,
|
||||||
name: "extras",
|
name: "extras",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const [extras, setExtras] = useState(initialExtras)
|
const [extras, setExtras] = useState(initialExtras)
|
||||||
|
|
||||||
const sensors = useSensors(useSensor(PointerSensor))
|
const sensors = useSensors(useSensor(PointerSensor))
|
||||||
@ -58,6 +63,8 @@ export function CommissionExtraField({ extras: initialExtras }: Props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mounted) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="text-lg font-semibold">Extras</h3>
|
<h3 className="text-lg font-semibold">Extras</h3>
|
||||||
|
@ -24,7 +24,7 @@ import {
|
|||||||
SortableContext,
|
SortableContext,
|
||||||
verticalListSortingStrategy
|
verticalListSortingStrategy
|
||||||
} from "@dnd-kit/sortable"
|
} from "@dnd-kit/sortable"
|
||||||
import { useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { useFieldArray, useFormContext } from "react-hook-form"
|
import { useFieldArray, useFormContext } from "react-hook-form"
|
||||||
import { ComboboxCreateable } from "./ComboboxCreateable"
|
import { ComboboxCreateable } from "./ComboboxCreateable"
|
||||||
|
|
||||||
@ -33,12 +33,17 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CommissionOptionField({ options: initialOptions }: Props) {
|
export function CommissionOptionField({ options: initialOptions }: Props) {
|
||||||
|
const [mounted, setMounted] = useState(false)
|
||||||
const { control } = useFormContext()
|
const { control } = useFormContext()
|
||||||
const { fields, append, remove, move } = useFieldArray({
|
const { fields, append, remove, move } = useFieldArray({
|
||||||
control,
|
control,
|
||||||
name: "options",
|
name: "options",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const [options, setOptions] = useState(initialOptions)
|
const [options, setOptions] = useState(initialOptions)
|
||||||
|
|
||||||
const sensors = useSensors(useSensor(PointerSensor))
|
const sensors = useSensors(useSensor(PointerSensor))
|
||||||
@ -58,6 +63,8 @@ export function CommissionOptionField({ options: initialOptions }: Props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mounted) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="text-lg font-semibold">Options</h3>
|
<h3 className="text-lg font-semibold">Options</h3>
|
||||||
|
Reference in New Issue
Block a user