Add delete to commission types and fix dragging
This commit is contained in:
		
							
								
								
									
										19
									
								
								src/actions/items/commissions/types/deleteType.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/actions/items/commissions/types/deleteType.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
"use server"
 | 
			
		||||
 | 
			
		||||
import prisma from "@/lib/prisma"
 | 
			
		||||
 | 
			
		||||
export async function deleteCommissionType(typeId: string) {
 | 
			
		||||
 | 
			
		||||
  await prisma.commissionTypeOption.deleteMany({
 | 
			
		||||
    where: { typeId },
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  await prisma.commissionTypeExtra.deleteMany({
 | 
			
		||||
    where: { typeId },
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  await prisma.commissionType.delete({
 | 
			
		||||
    where: { id: typeId },
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -26,7 +26,7 @@ export default async function CommissionTypesEditPage({ params }: { params: { id
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <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">Edit Commission Type</h1>
 | 
			
		||||
      </div>
 | 
			
		||||
      <EditTypeForm type={commissionType} allOptions={options} allExtras={extras} />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
@ -29,11 +29,15 @@ export default function SortableItemCard({ id, children }: Props) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      ref={setNodeRef}
 | 
			
		||||
      style={style}
 | 
			
		||||
      {...attributes}
 | 
			
		||||
      {...listeners}
 | 
			
		||||
      className="cursor-grab"
 | 
			
		||||
    >
 | 
			
		||||
      style={style}>
 | 
			
		||||
      <div
 | 
			
		||||
        {...attributes}
 | 
			
		||||
        {...listeners}
 | 
			
		||||
        className="cursor-grab px-2 py-1 text-sm text-muted-foreground"
 | 
			
		||||
        title="Drag to reorder"
 | 
			
		||||
      >
 | 
			
		||||
        ☰
 | 
			
		||||
      </div>
 | 
			
		||||
      {children}
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,11 @@
 | 
			
		||||
"use client"
 | 
			
		||||
 | 
			
		||||
import { deleteCommissionType } from "@/actions/items/commissions/types/deleteType";
 | 
			
		||||
import { updateCommissionTypeSortOrder } from "@/actions/items/commissions/types/updateCommissionTypeSortOrder";
 | 
			
		||||
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 {
 | 
			
		||||
  closestCenter,
 | 
			
		||||
@ -17,9 +20,9 @@ import {
 | 
			
		||||
  rectSortingStrategy,
 | 
			
		||||
  SortableContext
 | 
			
		||||
} from "@dnd-kit/sortable";
 | 
			
		||||
import { PencilIcon } from "lucide-react";
 | 
			
		||||
import { PencilIcon, TrashIcon } from "lucide-react";
 | 
			
		||||
import Link from "next/link";
 | 
			
		||||
import { useEffect, useState } from "react";
 | 
			
		||||
import { useEffect, useState, useTransition } from "react";
 | 
			
		||||
 | 
			
		||||
type CommissionTypeWithItems = CommissionType & {
 | 
			
		||||
  options: (CommissionTypeOption & {
 | 
			
		||||
@ -33,6 +36,9 @@ type CommissionTypeWithItems = CommissionType & {
 | 
			
		||||
export default function ListTypes({ types }: { types: CommissionTypeWithItems[] }) {
 | 
			
		||||
  const [items, setItems] = useState(types)
 | 
			
		||||
  const [isMounted, setIsMounted] = useState(false)
 | 
			
		||||
  const [dialogOpen, setDialogOpen] = useState(false)
 | 
			
		||||
  const [deleteTargetId, setDeleteTargetId] = useState<string | null>(null)
 | 
			
		||||
  const [isPending, startTransition] = useTransition()
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setIsMounted(true)
 | 
			
		||||
@ -56,70 +62,116 @@ export default function ListTypes({ types }: { types: CommissionTypeWithItems[]
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const confirmDelete = () => {
 | 
			
		||||
    if (!deleteTargetId) return
 | 
			
		||||
    startTransition(async () => {
 | 
			
		||||
      await deleteCommissionType(deleteTargetId)
 | 
			
		||||
      setItems((prev) => prev.filter((i) => i.id !== deleteTargetId))
 | 
			
		||||
      setDialogOpen(false)
 | 
			
		||||
      setDeleteTargetId(null)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!isMounted) return null
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
 | 
			
		||||
      <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
 | 
			
		||||
        <SortableContext items={items.map((i) => i.id)} strategy={rectSortingStrategy}>
 | 
			
		||||
          {items.map(type => (
 | 
			
		||||
            <SortableItemCard key={type.id} id={type.id}>
 | 
			
		||||
              <Card>
 | 
			
		||||
                <CardHeader>
 | 
			
		||||
                  <CardTitle className="text-xl truncate">{type.name}</CardTitle>
 | 
			
		||||
                  <CardDescription>{type.description}</CardDescription>
 | 
			
		||||
    <>
 | 
			
		||||
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
 | 
			
		||||
        <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
 | 
			
		||||
          <SortableContext items={items.map((i) => i.id)} strategy={rectSortingStrategy}>
 | 
			
		||||
            {items.map(type => (
 | 
			
		||||
              <SortableItemCard key={type.id} id={type.id}>
 | 
			
		||||
                <Card>
 | 
			
		||||
                  <CardHeader>
 | 
			
		||||
                    <CardTitle className="text-xl truncate">{type.name}</CardTitle>
 | 
			
		||||
                    <CardDescription>{type.description}</CardDescription>
 | 
			
		||||
 | 
			
		||||
                </CardHeader>
 | 
			
		||||
                <CardContent className="flex flex-col justify-start gap-4">
 | 
			
		||||
                  <div>
 | 
			
		||||
                    <h4 className="font-semibold">Options</h4>
 | 
			
		||||
                    <ul className="pl-4 list-disc">
 | 
			
		||||
                      {type.options.map((opt) => (
 | 
			
		||||
                        <li key={opt.id}>
 | 
			
		||||
                          {opt.option?.name}:{" "}
 | 
			
		||||
                          {opt.price !== null
 | 
			
		||||
                            ? `${opt.price}€`
 | 
			
		||||
                            : opt.pricePercent
 | 
			
		||||
                              ? `+${opt.pricePercent}%`
 | 
			
		||||
                              : opt.priceRange
 | 
			
		||||
                                ? `${opt.priceRange}€`
 | 
			
		||||
                                : "Included"}
 | 
			
		||||
                        </li>
 | 
			
		||||
                      ))}
 | 
			
		||||
                    </ul>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div>
 | 
			
		||||
                    <h4 className="font-semibold">Extras</h4>
 | 
			
		||||
                    <ul className="pl-4 list-disc">
 | 
			
		||||
                      {type.extras.map((ext) => (
 | 
			
		||||
                        <li key={ext.id}>
 | 
			
		||||
                          {ext.extra?.name}:{" "}
 | 
			
		||||
                          {ext.price !== null
 | 
			
		||||
                            ? `${ext.price}€`
 | 
			
		||||
                            : ext.pricePercent
 | 
			
		||||
                              ? `+${ext.pricePercent}%`
 | 
			
		||||
                              : ext.priceRange
 | 
			
		||||
                                ? `${ext.priceRange}€`
 | 
			
		||||
                                : "Included"}
 | 
			
		||||
                        </li>
 | 
			
		||||
                      ))}
 | 
			
		||||
                    </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 >
 | 
			
		||||
          ))}
 | 
			
		||||
        </SortableContext>
 | 
			
		||||
      </DndContext>
 | 
			
		||||
    </div>
 | 
			
		||||
                  </CardHeader>
 | 
			
		||||
                  <CardContent className="flex flex-col justify-start gap-4">
 | 
			
		||||
                    <div>
 | 
			
		||||
                      <h4 className="font-semibold">Options</h4>
 | 
			
		||||
                      <ul className="pl-4 list-disc">
 | 
			
		||||
                        {type.options.map((opt) => (
 | 
			
		||||
                          <li key={opt.id}>
 | 
			
		||||
                            {opt.option?.name}:{" "}
 | 
			
		||||
                            {opt.price !== null
 | 
			
		||||
                              ? `${opt.price}€`
 | 
			
		||||
                              : opt.pricePercent
 | 
			
		||||
                                ? `+${opt.pricePercent}%`
 | 
			
		||||
                                : opt.priceRange
 | 
			
		||||
                                  ? `${opt.priceRange}€`
 | 
			
		||||
                                  : "Included"}
 | 
			
		||||
                          </li>
 | 
			
		||||
                        ))}
 | 
			
		||||
                      </ul>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div>
 | 
			
		||||
                      <h4 className="font-semibold">Extras</h4>
 | 
			
		||||
                      <ul className="pl-4 list-disc">
 | 
			
		||||
                        {type.extras.map((ext) => (
 | 
			
		||||
                          <li key={ext.id}>
 | 
			
		||||
                            {ext.extra?.name}:{" "}
 | 
			
		||||
                            {ext.price !== null
 | 
			
		||||
                              ? `${ext.price}€`
 | 
			
		||||
                              : ext.pricePercent
 | 
			
		||||
                                ? `+${ext.pricePercent}%`
 | 
			
		||||
                                : ext.priceRange
 | 
			
		||||
                                  ? `${ext.priceRange}€`
 | 
			
		||||
                                  : "Included"}
 | 
			
		||||
                          </li>
 | 
			
		||||
                        ))}
 | 
			
		||||
                      </ul>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </CardContent>
 | 
			
		||||
                  <CardFooter className="flex flex-col gap-2">
 | 
			
		||||
                    <Link
 | 
			
		||||
                      href={`/items/commissions/types/${type.id}/edit`}
 | 
			
		||||
                      className="w-full"
 | 
			
		||||
                    >
 | 
			
		||||
                      <Button variant="default" className="w-full flex items-center gap-2">
 | 
			
		||||
                        <PencilIcon className="h-4 w-4" />
 | 
			
		||||
                        Edit
 | 
			
		||||
                      </Button>
 | 
			
		||||
                    </Link>
 | 
			
		||||
                    <Button
 | 
			
		||||
                      variant="destructive"
 | 
			
		||||
                      className="w-full flex items-center gap-2"
 | 
			
		||||
                      onClick={() => {
 | 
			
		||||
                        setDeleteTargetId(type.id)
 | 
			
		||||
                        setDialogOpen(true)
 | 
			
		||||
                      }}
 | 
			
		||||
                    >
 | 
			
		||||
                      <TrashIcon className="h-4 w-4" />
 | 
			
		||||
                      Delete
 | 
			
		||||
                    </Button>
 | 
			
		||||
                  </CardFooter>
 | 
			
		||||
                </Card>
 | 
			
		||||
              </SortableItemCard >
 | 
			
		||||
            ))}
 | 
			
		||||
          </SortableContext>
 | 
			
		||||
        </DndContext>
 | 
			
		||||
      </div >
 | 
			
		||||
 | 
			
		||||
      <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
 | 
			
		||||
        <DialogContent>
 | 
			
		||||
          <DialogHeader>
 | 
			
		||||
            <DialogTitle>Delete this commission type?</DialogTitle>
 | 
			
		||||
          </DialogHeader>
 | 
			
		||||
          <p>This action cannot be undone. Are you sure you want to continue?</p>
 | 
			
		||||
          <DialogFooter>
 | 
			
		||||
            <Button variant="outline" onClick={() => setDialogOpen(false)}>
 | 
			
		||||
              Cancel
 | 
			
		||||
            </Button>
 | 
			
		||||
            <Button
 | 
			
		||||
              variant="destructive"
 | 
			
		||||
              disabled={isPending}
 | 
			
		||||
              onClick={confirmDelete}
 | 
			
		||||
            >
 | 
			
		||||
              Confirm Delete
 | 
			
		||||
            </Button>
 | 
			
		||||
          </DialogFooter>
 | 
			
		||||
        </DialogContent>
 | 
			
		||||
      </Dialog>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user