admin dashboard user managment ui; set users permission actions

This commit is contained in:
2025-03-08 10:29:44 +01:00
parent 5c09688f1a
commit dd3c321350
19 changed files with 988 additions and 140 deletions
+4 -4
View File
@@ -22,10 +22,10 @@ const data = {
title: "Benutzer",
url: "/admin",
items: [
{
title: "Team Mitglieder",
url: "/admin/team",
},
// {
// title: "Team Mitglieder",
// url: "/admin/team",
// },
{
title: "Alle Benutzer",
url: "/admin/benutzer",
@@ -0,0 +1,112 @@
"use client";
import { z } from "zod";
import { Checkbox } from "@/components/ui/checkbox";
import { cn } from "@/lib/utils";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
hasPermission,
roleDefinitionsArray,
} from "@/lib/validation/permissions";
import React from "react";
import { setUserPermissions } from "@/server/actions/user";
import { toast } from "sonner";
const formSchema = z.object({
permission: z.number().min(1),
});
const dynamicRoles = roleDefinitionsArray.filter(
({ role: { name } }) => name !== "User",
);
export type UserPermissionsFormProps = {
server_permissions: number;
userId: string;
};
function UserPermissionsForm({
server_permissions,
userId,
cb,
}: UserPermissionsFormProps & {
cb?: () => void;
}) {
// const userRoles = getUserPermissions(permissions);
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
permission: server_permissions,
},
});
// 2. Define a submit handler.
async function onSubmit(values: z.infer<typeof formSchema>) {
const result = await setUserPermissions(userId, values.permission);
if (result.success) toast.success("Benutzerrechte gespeichert");
else toast.error("Fehler beim Speichern der Benutzerrechte");
cb?.();
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-2">
<FormField
control={form.control}
name="permission"
render={({ field }) => (
<FormItem>
<FormLabel>Benutzerrechte</FormLabel>
<FormControl>
<ul className="space-y-1">
{dynamicRoles.map(({ key, role }) => {
const hasRole = hasPermission(field.value, Number(key));
return (
<li
key={key}
className={cn(
"flex w-full items-center justify-between rounded-md bg-muted px-2 py-1",
Object.values(role.color).join(" "),
)}
>
<span>{role.name}</span>
<Checkbox
id={key}
checked={hasRole}
onCheckedChange={() => {
const newPermissionFlag = hasRole
? field.value - Number(key)
: field.value + Number(key);
field.onChange(newPermissionFlag);
}}
/>
</li>
);
})}
</ul>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{form.formState.isDirty && (
<Button type="submit" className="w-full">
Speichern
</Button>
)}
</form>
</Form>
);
}
export default UserPermissionsForm;
@@ -0,0 +1,35 @@
"use client";
import React from "react";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Settings } from "lucide-react";
import { Button } from "@/components/ui/button";
import UserPermissionsForm, {
UserPermissionsFormProps,
} from "./user-permissions-form";
function UserPermissionsPopover(props: UserPermissionsFormProps) {
const [open, setOpen] = React.useState(false);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
size={"icon"}
variant={"outline"}
className="size-max gap-1 bg-muted p-px text-muted-foreground"
>
<Settings className="size-3" />
</Button>
</PopoverTrigger>
<PopoverContent side="left" align="start">
<UserPermissionsForm {...props} cb={() => setOpen(false)} />
</PopoverContent>
</Popover>
);
}
export default UserPermissionsPopover;
+113
View File
@@ -0,0 +1,113 @@
"use client";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { getUserPermissions } from "@/lib/validation/permissions";
import { ColumnDef } from "@tanstack/react-table";
import { ArrowUpDown, Copy, Mail } from "lucide-react";
import { User } from "next-auth";
import UserPermissionsPopover from "../_components/user-permissions-popover";
import CopyToClip from "@/components/copy-to-clip";
export const columns: ColumnDef<User>[] = [
{
accessorKey: "name",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Name
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
},
{
accessorKey: "email",
cell: ({ row }) => {
const email = String(row.getValue("email"));
return (
<div className="flex items-center gap-2">
<CopyToClip text={email} targetName="Email" />
<span>{email}</span>
</div>
);
},
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<Mail className="size-4" />
<span>Email</span>
<ArrowUpDown className="size-4" />
</Button>
);
},
},
{
accessorKey: "role",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Benutzerrechte
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const roles = getUserPermissions(Number(row.getValue("role")));
const userId = String(row.getValue("id"));
return (
<div className="flex items-center gap-2">
<UserPermissionsPopover
server_permissions={Number(row.getValue("role"))}
userId={userId}
/>
{roles
.filter(({ name }) => name !== "User")
.map((role) => (
<Badge
key={role.name}
variant={"outline"}
className={`${Object.values(role.color).join(" ")}`}
>
{role.name}
</Badge>
))}
</div>
);
},
},
{
accessorKey: "id",
header: () => <div className="text-end">Benutzer ID</div>,
cell: ({ row }) => {
return (
<div className="flex items-center justify-end gap-2">
<CopyToClip
text={row.getValue("id")}
targetName="Benutzer ID"
buttonProps={{
size: "default",
variant: "outline",
}}
>
<Copy className="size-4" />
<span>Kopieren</span>
</CopyToClip>
</div>
);
},
},
];
@@ -10,7 +10,7 @@ async function Page() {
return (
<>
<div className="flex w-full items-center justify-between">
<h1 className="text-2xl font-bold">Team Mitglieder</h1>
<h1 className="text-2xl font-bold">Benutzer Manager</h1>
<Button variant={"outline"}>Mitglied einladen</Button>
</div>
-50
View File
@@ -1,50 +0,0 @@
"use client";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { getUserPermissions } from "@/lib/validation/permissions";
import { ColumnDef } from "@tanstack/react-table";
import { Plus } from "lucide-react";
import { User } from "next-auth";
export const columns: ColumnDef<User>[] = [
{
accessorKey: "name",
header: "Name",
},
{
accessorKey: "email",
header: "Email",
},
{
accessorKey: "role",
header: "Rollen",
cell: ({ row }) => {
const roles = getUserPermissions(Number(row.getValue("role")));
return (
<div className="flex items-center gap-2">
{roles
.filter(({ name }) => name !== "User")
.map((role) => (
<Badge
key={role.name}
variant={"outline"}
className={`${Object.values(role.color).join(" ")}`}
>
{role.name}
</Badge>
))}
<Button
size={"icon"}
variant={"outline"}
className="size-max gap-1 bg-muted p-px text-muted-foreground"
>
<Plus className="size-3" />
</Button>
</div>
);
},
},
];