Add custom inputs to commission types
This commit is contained in:
@ -4,28 +4,31 @@ import { updateCommissionType } from "@/actions/items/commissions/types/updateTy
|
||||
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 { CommissionCustomInput, CommissionExtra, CommissionOption, CommissionType, CommissionTypeCustomInput, 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 { CommissionCustomInputField } from "./form/CommissionCustomInputField";
|
||||
import { CommissionExtraField } from "./form/CommissionExtraField";
|
||||
import { CommissionOptionField } from "./form/CommissionOptionField";
|
||||
|
||||
type CommissionTypeWithConnections = CommissionType & {
|
||||
options: (CommissionTypeOption & { option: CommissionOption })[]
|
||||
extras: (CommissionTypeExtra & { extra: CommissionExtra })[]
|
||||
customInputs: (CommissionTypeCustomInput & { customInput: CommissionCustomInput })[]
|
||||
}
|
||||
|
||||
type Props = {
|
||||
type: CommissionTypeWithConnections
|
||||
allOptions: CommissionOption[],
|
||||
allExtras: CommissionExtra[],
|
||||
allCustomInputs: CommissionCustomInput[]
|
||||
}
|
||||
|
||||
export default function EditTypeForm({ type, allOptions, allExtras }: Props) {
|
||||
export default function EditTypeForm({ type, allOptions, allExtras, allCustomInputs }: Props) {
|
||||
const router = useRouter();
|
||||
const form = useForm<z.infer<typeof commissionTypeSchema>>({
|
||||
resolver: zodResolver(commissionTypeSchema),
|
||||
@ -44,6 +47,13 @@ export default function EditTypeForm({ type, allOptions, allExtras }: Props) {
|
||||
pricePercent: e.pricePercent ?? undefined,
|
||||
priceRange: e.priceRange ?? undefined,
|
||||
})),
|
||||
customInputs: type.customInputs.map((f) => ({
|
||||
fieldId: f.customInputId,
|
||||
fieldType: f.inputType,
|
||||
label: f.label,
|
||||
name: f.customInput.name,
|
||||
required: f.required,
|
||||
})),
|
||||
},
|
||||
})
|
||||
|
||||
@ -93,6 +103,7 @@ export default function EditTypeForm({ type, allOptions, allExtras }: Props) {
|
||||
|
||||
<CommissionOptionField options={allOptions} />
|
||||
<CommissionExtraField extras={allExtras} />
|
||||
<CommissionCustomInputField customInputs={allCustomInputs} />
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<Button type="submit">Submit</Button>
|
||||
|
@ -6,7 +6,7 @@ import SortableItemCard from "@/components/drag/SortableItemCard";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { CommissionExtra, CommissionOption, CommissionType, CommissionTypeExtra, CommissionTypeOption } from "@/generated/prisma";
|
||||
import { CommissionCustomInput, CommissionExtra, CommissionOption, CommissionType, CommissionTypeCustomInput, CommissionTypeExtra, CommissionTypeOption } from "@/generated/prisma";
|
||||
import {
|
||||
closestCenter,
|
||||
DndContext,
|
||||
@ -30,6 +30,9 @@ type CommissionTypeWithItems = CommissionType & {
|
||||
})[]
|
||||
extras: (CommissionTypeExtra & {
|
||||
extra: CommissionExtra | null
|
||||
})[],
|
||||
customInputs: (CommissionTypeCustomInput & {
|
||||
customInput: CommissionCustomInput
|
||||
})[]
|
||||
}
|
||||
|
||||
@ -122,6 +125,16 @@ export default function ListTypes({ types }: { types: CommissionTypeWithItems[]
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold">Custom Inputs</h4>
|
||||
<ul className="pl-4 list-disc">
|
||||
{type.customInputs.map((ci) => (
|
||||
<li key={ci.id}>
|
||||
{ci.label}: {ci.inputType}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex flex-col gap-2">
|
||||
<Link
|
||||
|
@ -4,22 +4,24 @@ import { createCommissionType } from "@/actions/items/commissions/types/newType"
|
||||
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 } from "@/generated/prisma";
|
||||
import { CommissionCustomInput, CommissionExtra, CommissionOption } 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 { CommissionCustomInputField } from "./form/CommissionCustomInputField";
|
||||
import { CommissionExtraField } from "./form/CommissionExtraField";
|
||||
import { CommissionOptionField } from "./form/CommissionOptionField";
|
||||
|
||||
type Props = {
|
||||
options: CommissionOption[],
|
||||
extras: CommissionExtra[],
|
||||
customInputs: CommissionCustomInput[]
|
||||
}
|
||||
|
||||
export default function NewTypeForm({ options, extras }: Props) {
|
||||
export default function NewTypeForm({ options, extras, customInputs }: Props) {
|
||||
const router = useRouter();
|
||||
const form = useForm<z.infer<typeof commissionTypeSchema>>({
|
||||
resolver: zodResolver(commissionTypeSchema),
|
||||
@ -78,6 +80,7 @@ export default function NewTypeForm({ options, extras }: Props) {
|
||||
|
||||
<CommissionOptionField options={options} />
|
||||
<CommissionExtraField extras={extras} />
|
||||
<CommissionCustomInputField customInputs={customInputs} />
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<Button type="submit">Submit</Button>
|
||||
|
@ -0,0 +1,205 @@
|
||||
"use client"
|
||||
|
||||
import { createCommissionCustomInput } from "@/actions/items/commissions/types/newType"
|
||||
import SortableItem from "@/components/drag/SortableItem"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
} from "@/components/ui/form"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { CommissionCustomInput } from "@/generated/prisma"
|
||||
import {
|
||||
closestCenter,
|
||||
DndContext,
|
||||
DragEndEvent,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from "@dnd-kit/core"
|
||||
import {
|
||||
SortableContext,
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useFieldArray, useFormContext } from "react-hook-form"
|
||||
import { ComboboxCreateable } from "./ComboboxCreateable"
|
||||
|
||||
type Props = {
|
||||
customInputs: CommissionCustomInput[]
|
||||
}
|
||||
|
||||
export function CommissionCustomInputField({ customInputs: initialInputs }: Props) {
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const { control, setValue } = useFormContext()
|
||||
const { fields, append, remove, move } = useFieldArray({
|
||||
control,
|
||||
name: "customInputs",
|
||||
})
|
||||
|
||||
const [customInputs, setCustomInputs] = useState(initialInputs)
|
||||
const sensors = useSensors(useSensor(PointerSensor))
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
const handleDragEnd = (event: DragEndEvent) => {
|
||||
const { active, over } = event
|
||||
if (!over || active.id === over.id) return
|
||||
const oldIndex = fields.findIndex((f) => f.id === active.id)
|
||||
const newIndex = fields.findIndex((f) => f.id === over.id)
|
||||
if (oldIndex !== -1 && newIndex !== -1) {
|
||||
move(oldIndex, newIndex)
|
||||
}
|
||||
}
|
||||
|
||||
if (!mounted) return null
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold">Custom Inputs</h3>
|
||||
|
||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||
<SortableContext items={fields.map((f) => f.id)} strategy={verticalListSortingStrategy}>
|
||||
{fields.map((field, index) => {
|
||||
return (
|
||||
<SortableItem key={field.id} id={field.id}>
|
||||
<div className="grid grid-cols-5 gap-4 items-end">
|
||||
|
||||
{/* Picker */}
|
||||
<FormField
|
||||
control={control}
|
||||
name={`customInputs.${index}.customInputId`}
|
||||
render={({ field: inputField }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Input</FormLabel>
|
||||
<FormControl>
|
||||
<ComboboxCreateable
|
||||
options={customInputs.map((ci) => ({
|
||||
label: ci.name,
|
||||
value: ci.id,
|
||||
}))}
|
||||
selected={inputField.value}
|
||||
onSelect={(val) => {
|
||||
const selected = customInputs.find((ci) => ci.id === val)
|
||||
inputField.onChange(val)
|
||||
if (selected) {
|
||||
setValue(`customInputs.${index}.label`, selected.name)
|
||||
setValue(`customInputs.${index}.inputType`, "text")
|
||||
setValue(`customInputs.${index}.required`, false)
|
||||
}
|
||||
}}
|
||||
onCreateNew={async (name) => {
|
||||
const slug = name.toLowerCase().replace(/\s+/g, "-")
|
||||
const newInput = await createCommissionCustomInput({
|
||||
name,
|
||||
fieldId: slug,
|
||||
})
|
||||
setCustomInputs((prev) => [...prev, newInput])
|
||||
inputField.onChange(newInput.id)
|
||||
|
||||
setValue(`customInputs.${index}.label`, newInput.name)
|
||||
setValue(`customInputs.${index}.inputType`, "text")
|
||||
setValue(`customInputs.${index}.required`, false)
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Label */}
|
||||
<FormField
|
||||
control={control}
|
||||
name={`customInputs.${index}.label`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Label</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Input Type */}
|
||||
<FormField
|
||||
control={control}
|
||||
name={`customInputs.${index}.inputType`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Input Type</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select input type" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="text">Text</SelectItem>
|
||||
<SelectItem value="textarea">Textarea</SelectItem>
|
||||
<SelectItem value="number">Number</SelectItem>
|
||||
<SelectItem value="checkbox">Checkbox</SelectItem>
|
||||
<SelectItem value="date">Date</SelectItem>
|
||||
<SelectItem value="select">Dropdown (Select)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Required */}
|
||||
<FormField
|
||||
control={control}
|
||||
name={`customInputs.${index}.required`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Required</FormLabel>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Remove */}
|
||||
<Button type="button" variant="destructive" onClick={() => remove(index)}>
|
||||
Remove
|
||||
</Button>
|
||||
</div>
|
||||
</SortableItem>
|
||||
)
|
||||
})}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
append({
|
||||
customInputId: "",
|
||||
label: "",
|
||||
inputType: "text",
|
||||
required: false,
|
||||
})
|
||||
}
|
||||
>
|
||||
Add Input
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user