Add custom inputs to commission types
This commit is contained in:
34
prisma/migrations/20250707151218_type_field/migration.sql
Normal file
34
prisma/migrations/20250707151218_type_field/migration.sql
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "CommissionField" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"sortIndex" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"fieldType" TEXT NOT NULL,
|
||||||
|
"label" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"required" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
|
||||||
|
CONSTRAINT "CommissionField_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "CommissionTypeField" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"sortIndex" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"typeId" TEXT NOT NULL,
|
||||||
|
"fieldId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "CommissionTypeField_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "CommissionTypeField_typeId_fieldId_key" ON "CommissionTypeField"("typeId", "fieldId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "CommissionTypeField" ADD CONSTRAINT "CommissionTypeField_typeId_fkey" FOREIGN KEY ("typeId") REFERENCES "CommissionType"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "CommissionTypeField" ADD CONSTRAINT "CommissionTypeField_fieldId_fkey" FOREIGN KEY ("fieldId") REFERENCES "CommissionField"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the `CommissionField` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `CommissionTypeField` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "CommissionTypeField" DROP CONSTRAINT "CommissionTypeField_fieldId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "CommissionTypeField" DROP CONSTRAINT "CommissionTypeField_typeId_fkey";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "CommissionField";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "CommissionTypeField";
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "CommissionCustomInput" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"sortIndex" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"inputType" TEXT NOT NULL,
|
||||||
|
"label" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"required" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
|
||||||
|
CONSTRAINT "CommissionCustomInput_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "CommissionTypeCustomInput" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"sortIndex" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"typeId" TEXT NOT NULL,
|
||||||
|
"customInputId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "CommissionTypeCustomInput_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "CommissionTypeCustomInput_typeId_customInputId_key" ON "CommissionTypeCustomInput"("typeId", "customInputId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "CommissionTypeCustomInput" ADD CONSTRAINT "CommissionTypeCustomInput_typeId_fkey" FOREIGN KEY ("typeId") REFERENCES "CommissionType"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "CommissionTypeCustomInput" ADD CONSTRAINT "CommissionTypeCustomInput_customInputId_fkey" FOREIGN KEY ("customInputId") REFERENCES "CommissionCustomInput"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `inputType` on the `CommissionCustomInput` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `label` on the `CommissionCustomInput` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `required` on the `CommissionCustomInput` table. All the data in the column will be lost.
|
||||||
|
- A unique constraint covering the columns `[name]` on the table `CommissionCustomInput` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- Added the required column `fieldId` to the `CommissionCustomInput` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `inputType` to the `CommissionTypeCustomInput` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `label` to the `CommissionTypeCustomInput` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "CommissionCustomInput" DROP COLUMN "inputType",
|
||||||
|
DROP COLUMN "label",
|
||||||
|
DROP COLUMN "required",
|
||||||
|
ADD COLUMN "fieldId" TEXT NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "CommissionTypeCustomInput" ADD COLUMN "inputType" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "label" TEXT NOT NULL,
|
||||||
|
ADD COLUMN "required" BOOLEAN NOT NULL DEFAULT false;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "CommissionCustomInput_name_key" ON "CommissionCustomInput"("name");
|
@ -26,6 +26,7 @@ model CommissionType {
|
|||||||
|
|
||||||
options CommissionTypeOption[]
|
options CommissionTypeOption[]
|
||||||
extras CommissionTypeExtra[]
|
extras CommissionTypeExtra[]
|
||||||
|
customInputs CommissionTypeCustomInput[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model CommissionOption {
|
model CommissionOption {
|
||||||
@ -54,6 +55,18 @@ model CommissionExtra {
|
|||||||
types CommissionTypeExtra[]
|
types CommissionTypeExtra[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model CommissionCustomInput {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
sortIndex Int @default(0)
|
||||||
|
|
||||||
|
name String @unique
|
||||||
|
fieldId String
|
||||||
|
|
||||||
|
types CommissionTypeCustomInput[]
|
||||||
|
}
|
||||||
|
|
||||||
model CommissionTypeOption {
|
model CommissionTypeOption {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
@ -92,6 +105,25 @@ model CommissionTypeExtra {
|
|||||||
@@unique([typeId, extraId])
|
@@unique([typeId, extraId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model CommissionTypeCustomInput {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
sortIndex Int @default(0)
|
||||||
|
|
||||||
|
typeId String
|
||||||
|
customInputId String
|
||||||
|
|
||||||
|
inputType String
|
||||||
|
label String
|
||||||
|
required Boolean @default(false)
|
||||||
|
|
||||||
|
type CommissionType @relation(fields: [typeId], references: [id])
|
||||||
|
customInput CommissionCustomInput @relation(fields: [customInputId], references: [id])
|
||||||
|
|
||||||
|
@@unique([typeId, customInputId])
|
||||||
|
}
|
||||||
|
|
||||||
model TermsOfService {
|
model TermsOfService {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
@ -21,6 +21,18 @@ export async function createCommissionExtra(data: { name: string }) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createCommissionCustomInput(data: {
|
||||||
|
name: string
|
||||||
|
fieldId: string
|
||||||
|
}) {
|
||||||
|
return await prisma.commissionCustomInput.create({
|
||||||
|
data: {
|
||||||
|
name: data.name,
|
||||||
|
fieldId: data.fieldId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export async function createCommissionType(formData: commissionTypeSchema) {
|
export async function createCommissionType(formData: commissionTypeSchema) {
|
||||||
const parsed = commissionTypeSchema.safeParse(formData)
|
const parsed = commissionTypeSchema.safeParse(formData)
|
||||||
|
|
||||||
@ -53,6 +65,15 @@ export async function createCommissionType(formData: commissionTypeSchema) {
|
|||||||
sortIndex: index,
|
sortIndex: index,
|
||||||
})) || [],
|
})) || [],
|
||||||
},
|
},
|
||||||
|
customInputs: {
|
||||||
|
create: data.customInputs?.map((c, index) => ({
|
||||||
|
customInput: { connect: { id: c.customInputId } },
|
||||||
|
label: c.label,
|
||||||
|
inputType: c.inputType,
|
||||||
|
required: c.required,
|
||||||
|
sortIndex: index,
|
||||||
|
})) || [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -35,10 +35,21 @@ export async function updateCommissionType(
|
|||||||
sortIndex: index,
|
sortIndex: index,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
|
customInputs: {
|
||||||
|
deleteMany: {},
|
||||||
|
create: data.customInputs?.map((c, index) => ({
|
||||||
|
customInput: { connect: { id: c.customInputId } },
|
||||||
|
label: c.label,
|
||||||
|
inputType: c.inputType,
|
||||||
|
required: c.required,
|
||||||
|
sortIndex: index,
|
||||||
|
})) || [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
options: true,
|
options: true,
|
||||||
extras: true,
|
extras: true,
|
||||||
|
customInputs: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ export default async function CommissionTypesEditPage({ params }: { params: { id
|
|||||||
include: {
|
include: {
|
||||||
options: { include: { option: true }, orderBy: { sortIndex: "asc" } },
|
options: { include: { option: true }, orderBy: { sortIndex: "asc" } },
|
||||||
extras: { include: { extra: true }, orderBy: { sortIndex: "asc" } },
|
extras: { include: { extra: true }, orderBy: { sortIndex: "asc" } },
|
||||||
|
customInputs: { include: { customInput: true }, orderBy: { sortIndex: "asc" } },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const options = await prisma.commissionOption.findMany({
|
const options = await prisma.commissionOption.findMany({
|
||||||
@ -18,6 +19,9 @@ export default async function CommissionTypesEditPage({ params }: { params: { id
|
|||||||
const extras = await prisma.commissionExtra.findMany({
|
const extras = await prisma.commissionExtra.findMany({
|
||||||
orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
|
orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
|
||||||
})
|
})
|
||||||
|
const customInputs = await prisma.commissionCustomInput.findMany({
|
||||||
|
orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
|
||||||
|
})
|
||||||
|
|
||||||
if (!commissionType) {
|
if (!commissionType) {
|
||||||
return <div>Type not found</div>
|
return <div>Type not found</div>
|
||||||
@ -28,7 +32,7 @@ export default async function CommissionTypesEditPage({ params }: { params: { id
|
|||||||
<div className="flex gap-4 justify-between pb-8">
|
<div className="flex gap-4 justify-between pb-8">
|
||||||
<h1 className="text-2xl font-bold mb-4">Edit Commission Type</h1>
|
<h1 className="text-2xl font-bold mb-4">Edit Commission Type</h1>
|
||||||
</div>
|
</div>
|
||||||
<EditTypeForm type={commissionType} allOptions={options} allExtras={extras} />
|
<EditTypeForm type={commissionType} allOptions={options} allExtras={extras} allCustomInputs={customInputs} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -8,6 +8,9 @@ export default async function CommissionTypesNewPage() {
|
|||||||
const extras = await prisma.commissionExtra.findMany({
|
const extras = await prisma.commissionExtra.findMany({
|
||||||
orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
|
orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
|
||||||
})
|
})
|
||||||
|
const customInputs = await prisma.commissionCustomInput.findMany({
|
||||||
|
orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -15,7 +18,7 @@ export default async function CommissionTypesNewPage() {
|
|||||||
<div className="flex gap-4 justify-between pb-8">
|
<div className="flex gap-4 justify-between pb-8">
|
||||||
<h1 className="text-2xl font-bold mb-4">New Commission Type</h1>
|
<h1 className="text-2xl font-bold mb-4">New Commission Type</h1>
|
||||||
</div>
|
</div>
|
||||||
<NewTypeForm options={options} extras={extras} />
|
<NewTypeForm options={options} extras={extras} customInputs={customInputs} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -8,6 +8,7 @@ export default async function CommissionTypesPage() {
|
|||||||
include: {
|
include: {
|
||||||
options: { include: { option: true }, orderBy: { sortIndex: "asc" } },
|
options: { include: { option: true }, orderBy: { sortIndex: "asc" } },
|
||||||
extras: { include: { extra: true }, orderBy: { sortIndex: "asc" } },
|
extras: { include: { extra: true }, orderBy: { sortIndex: "asc" } },
|
||||||
|
customInputs: { include: { customInput: true }, orderBy: { sortIndex: "asc" } },
|
||||||
},
|
},
|
||||||
orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
|
orderBy: [{ sortIndex: "asc" }, { name: "asc" }],
|
||||||
});
|
});
|
||||||
|
@ -4,28 +4,31 @@ import { updateCommissionType } from "@/actions/items/commissions/types/updateTy
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
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 { commissionTypeSchema } from "@/schemas/commissionType";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import * as z from "zod/v4";
|
import * as z from "zod/v4";
|
||||||
|
import { CommissionCustomInputField } from "./form/CommissionCustomInputField";
|
||||||
import { CommissionExtraField } from "./form/CommissionExtraField";
|
import { CommissionExtraField } from "./form/CommissionExtraField";
|
||||||
import { CommissionOptionField } from "./form/CommissionOptionField";
|
import { CommissionOptionField } from "./form/CommissionOptionField";
|
||||||
|
|
||||||
type CommissionTypeWithConnections = CommissionType & {
|
type CommissionTypeWithConnections = CommissionType & {
|
||||||
options: (CommissionTypeOption & { option: CommissionOption })[]
|
options: (CommissionTypeOption & { option: CommissionOption })[]
|
||||||
extras: (CommissionTypeExtra & { extra: CommissionExtra })[]
|
extras: (CommissionTypeExtra & { extra: CommissionExtra })[]
|
||||||
|
customInputs: (CommissionTypeCustomInput & { customInput: CommissionCustomInput })[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
type: CommissionTypeWithConnections
|
type: CommissionTypeWithConnections
|
||||||
allOptions: CommissionOption[],
|
allOptions: CommissionOption[],
|
||||||
allExtras: CommissionExtra[],
|
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 router = useRouter();
|
||||||
const form = useForm<z.infer<typeof commissionTypeSchema>>({
|
const form = useForm<z.infer<typeof commissionTypeSchema>>({
|
||||||
resolver: zodResolver(commissionTypeSchema),
|
resolver: zodResolver(commissionTypeSchema),
|
||||||
@ -44,6 +47,13 @@ export default function EditTypeForm({ type, allOptions, allExtras }: Props) {
|
|||||||
pricePercent: e.pricePercent ?? undefined,
|
pricePercent: e.pricePercent ?? undefined,
|
||||||
priceRange: e.priceRange ?? 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} />
|
<CommissionOptionField options={allOptions} />
|
||||||
<CommissionExtraField extras={allExtras} />
|
<CommissionExtraField extras={allExtras} />
|
||||||
|
<CommissionCustomInputField customInputs={allCustomInputs} />
|
||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<Button type="submit">Submit</Button>
|
<Button type="submit">Submit</Button>
|
||||||
|
@ -6,7 +6,7 @@ import SortableItemCard from "@/components/drag/SortableItemCard";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
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 {
|
import {
|
||||||
closestCenter,
|
closestCenter,
|
||||||
DndContext,
|
DndContext,
|
||||||
@ -30,6 +30,9 @@ type CommissionTypeWithItems = CommissionType & {
|
|||||||
})[]
|
})[]
|
||||||
extras: (CommissionTypeExtra & {
|
extras: (CommissionTypeExtra & {
|
||||||
extra: CommissionExtra | null
|
extra: CommissionExtra | null
|
||||||
|
})[],
|
||||||
|
customInputs: (CommissionTypeCustomInput & {
|
||||||
|
customInput: CommissionCustomInput
|
||||||
})[]
|
})[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +125,16 @@ export default function ListTypes({ types }: { types: CommissionTypeWithItems[]
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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>
|
</CardContent>
|
||||||
<CardFooter className="flex flex-col gap-2">
|
<CardFooter className="flex flex-col gap-2">
|
||||||
<Link
|
<Link
|
||||||
|
@ -4,22 +4,24 @@ import { createCommissionType } from "@/actions/items/commissions/types/newType"
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
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 { commissionTypeSchema } from "@/schemas/commissionType";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import * as z from "zod/v4";
|
import * as z from "zod/v4";
|
||||||
|
import { CommissionCustomInputField } from "./form/CommissionCustomInputField";
|
||||||
import { CommissionExtraField } from "./form/CommissionExtraField";
|
import { CommissionExtraField } from "./form/CommissionExtraField";
|
||||||
import { CommissionOptionField } from "./form/CommissionOptionField";
|
import { CommissionOptionField } from "./form/CommissionOptionField";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
options: CommissionOption[],
|
options: CommissionOption[],
|
||||||
extras: CommissionExtra[],
|
extras: CommissionExtra[],
|
||||||
|
customInputs: CommissionCustomInput[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NewTypeForm({ options, extras }: Props) {
|
export default function NewTypeForm({ options, extras, customInputs }: Props) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const form = useForm<z.infer<typeof commissionTypeSchema>>({
|
const form = useForm<z.infer<typeof commissionTypeSchema>>({
|
||||||
resolver: zodResolver(commissionTypeSchema),
|
resolver: zodResolver(commissionTypeSchema),
|
||||||
@ -78,6 +80,7 @@ export default function NewTypeForm({ options, extras }: Props) {
|
|||||||
|
|
||||||
<CommissionOptionField options={options} />
|
<CommissionOptionField options={options} />
|
||||||
<CommissionExtraField extras={extras} />
|
<CommissionExtraField extras={extras} />
|
||||||
|
<CommissionCustomInputField customInputs={customInputs} />
|
||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<Button type="submit">Submit</Button>
|
<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>
|
||||||
|
)
|
||||||
|
}
|
@ -16,11 +16,19 @@ const extraField = z.object({
|
|||||||
priceRange: z.string().regex(rangePattern, "Format must be like '10–80'").optional(),
|
priceRange: z.string().regex(rangePattern, "Format must be like '10–80'").optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const customInputsField = z.object({
|
||||||
|
customInputId: z.string(),
|
||||||
|
inputType: z.string(),
|
||||||
|
label: z.string(),
|
||||||
|
required: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
export const commissionTypeSchema = z.object({
|
export const commissionTypeSchema = z.object({
|
||||||
name: z.string().min(1, "Name is required. Min 1 character."),
|
name: z.string().min(1, "Name is required. Min 1 character."),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
options: z.array(optionField).optional(),
|
options: z.array(optionField).optional(),
|
||||||
extras: z.array(extraField).optional(),
|
extras: z.array(extraField).optional(),
|
||||||
|
customInputs: z.array(customInputsField).optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type commissionTypeSchema = z.infer<typeof commissionTypeSchema>
|
export type commissionTypeSchema = z.infer<typeof commissionTypeSchema>
|
Reference in New Issue
Block a user