admin dashboard user managment ui; set users permission actions
This commit is contained in:
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user