Added Account Dublicate fucntion, improved default sorts
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
"use client";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getUser, markDuplicate } from "(app)/admin/user/action";
|
||||
import { Button } from "@repo/shared-components";
|
||||
import { Select } from "_components/ui/Select";
|
||||
import toast from "react-hot-toast";
|
||||
import { TriangleAlert } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
const DuplicateSchema = z.object({
|
||||
canonicalUserId: z.string().min(1, "Bitte Nutzer auswählen"),
|
||||
reason: z.string().max(500).optional(),
|
||||
});
|
||||
|
||||
export const DuplicateForm = ({ duplicateUserId }: { duplicateUserId: string }) => {
|
||||
const form = useForm<z.infer<typeof DuplicateSchema>>({
|
||||
resolver: zodResolver(DuplicateSchema),
|
||||
defaultValues: { canonicalUserId: "", reason: "" },
|
||||
});
|
||||
|
||||
const [search, setSearch] = useState("");
|
||||
const { data: users } = useQuery({
|
||||
queryKey: ["duplicate-search"],
|
||||
queryFn: async () =>
|
||||
getUser({
|
||||
OR: [
|
||||
{ firstname: { contains: search, mode: "insensitive" } },
|
||||
{ lastname: { contains: search, mode: "insensitive" } },
|
||||
{ publicId: { contains: search, mode: "insensitive" } },
|
||||
{ email: { contains: search, mode: "insensitive" } },
|
||||
],
|
||||
}),
|
||||
enabled: search.length > 0,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
className="flex flex-wrap gap-3"
|
||||
onSubmit={form.handleSubmit(async (values) => {
|
||||
try {
|
||||
// find selected canonical user by id to obtain publicId
|
||||
const canonical = (users || []).find((u) => u.id === values.canonicalUserId);
|
||||
if (!canonical) {
|
||||
toast.error("Bitte wähle einen Original-Account aus.");
|
||||
return;
|
||||
}
|
||||
await markDuplicate({
|
||||
duplicateUserId,
|
||||
canonicalPublicId: canonical.publicId,
|
||||
reason: values.reason,
|
||||
});
|
||||
toast.success("Duplikat verknüpft und Nutzer gesperrt.");
|
||||
} catch (e: unknown) {
|
||||
const message =
|
||||
typeof e === "object" && e && "message" in e
|
||||
? (e as { message?: string }).message || "Fehler beim Verknüpfen"
|
||||
: "Fehler beim Verknüpfen";
|
||||
toast.error(message);
|
||||
}
|
||||
})}
|
||||
>
|
||||
<div className="card bg-base-200 flex-1 basis-[800px] shadow-xl">
|
||||
<div className="card-body">
|
||||
<h2 className="card-title">
|
||||
<TriangleAlert /> Duplikat markieren & sperren
|
||||
</h2>
|
||||
<Select
|
||||
form={form}
|
||||
name="canonicalUserId"
|
||||
label="Original-Nutzer suchen & auswählen"
|
||||
onInputChange={(v) => setSearch(String(v))}
|
||||
options={
|
||||
users?.map((u) => ({
|
||||
label: `${u.firstname} ${u.lastname} (${u.publicId})`,
|
||||
value: u.id,
|
||||
})) || [{ label: "Kein Nutzer gefunden", value: "", disabled: true }]
|
||||
}
|
||||
/>
|
||||
<label className="floating-label w-full">
|
||||
<span className="flex items-center gap-2 text-lg">Grund (optional)</span>
|
||||
<input
|
||||
{...form.register("reason")}
|
||||
type="text"
|
||||
className="input input-bordered w-full"
|
||||
placeholder="Begründung/Audit-Hinweis"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card bg-base-200 flex-1 basis-[800px] shadow-xl">
|
||||
<div className="card-body">
|
||||
<div className="flex w-full gap-4">
|
||||
<Button
|
||||
isLoading={form.formState.isSubmitting}
|
||||
type="submit"
|
||||
className="btn btn-primary flex-1"
|
||||
>
|
||||
Als Duplikat verknüpfen & sperren
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
37
apps/hub/app/(app)/admin/user/[id]/duplicate/page.tsx
Normal file
37
apps/hub/app/(app)/admin/user/[id]/duplicate/page.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { prisma } from "@repo/db";
|
||||
import { DuplicateForm } from "./_components/DuplicateForm";
|
||||
import { PersonIcon } from "@radix-ui/react-icons";
|
||||
import Link from "next/link";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
|
||||
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id },
|
||||
select: { id: true, firstname: true, lastname: true, publicId: true },
|
||||
});
|
||||
if (!user) {
|
||||
return (
|
||||
<div className="card bg-base-200 shadow-xl">
|
||||
<div className="card-body">Nutzer nicht gefunden</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="my-3">
|
||||
<div className="text-left">
|
||||
<Link href={`/admin/user/${user.id}`} className="link-hover l-0 text-gray-500">
|
||||
<ArrowLeft className="mb-1 mr-1 inline h-4 w-4" />
|
||||
Zurück zum Nutzer
|
||||
</Link>
|
||||
</div>
|
||||
<p className="text-left text-2xl font-semibold">
|
||||
<PersonIcon className="mr-2 inline h-5 w-5" /> Duplikat für {user.firstname}{" "}
|
||||
{user.lastname} #{user.publicId}
|
||||
</p>
|
||||
</div>
|
||||
<DuplicateForm duplicateUserId={user.id} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user