Update role columns
This commit is contained in:
parent
bf41292b28
commit
85daf8cfe7
|
|
@ -6,17 +6,21 @@ import {
|
|||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import React from "react";
|
||||
import columns from "./columns";
|
||||
import CrudPermissions from "@/features/auth/types/CrudPermissions";
|
||||
import getRoles from "@/features/dashboard/roles/data/getRoles";
|
||||
import createColumns from "./columns";
|
||||
|
||||
interface Props {
|
||||
permissions: Partial<CrudPermissions>,
|
||||
roles: Awaited<ReturnType<typeof getRoles>>
|
||||
}
|
||||
|
||||
export default function RolesTable(props: Props) {
|
||||
const table = useReactTable({
|
||||
data: [],
|
||||
columns,
|
||||
data: props.roles,
|
||||
columns: createColumns({
|
||||
permissions: props.permissions
|
||||
}),
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
defaultColumn: {
|
||||
cell: (props) => <Text>{props.getValue() as React.ReactNode}</Text>,
|
||||
|
|
|
|||
|
|
@ -1,43 +1,105 @@
|
|||
import CrudPermissions from "@/features/auth/types/CrudPermissions";
|
||||
import createActionButtons from "@/features/dashboard/utils/createActionButtons";
|
||||
import { Badge, Flex, Tooltip, ActionIcon } from "@mantine/core";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import Link from "next/link";
|
||||
import { StringifyOptions } from "querystring";
|
||||
import { TbEye, TbPencil, TbTrash } from "react-icons/tb";
|
||||
|
||||
export interface RoleRow {
|
||||
id: string,
|
||||
code: string,
|
||||
name: string,
|
||||
permissionCount: number,
|
||||
userCount: number,
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
isActive: boolean;
|
||||
permissionCount: number;
|
||||
userCount: number;
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<RoleRow>()
|
||||
interface ColumnOptions {
|
||||
permissions: Partial<CrudPermissions>;
|
||||
actions?: {
|
||||
detail?: () => void;
|
||||
edit?: () => void;
|
||||
delete?: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
const columns = [
|
||||
columnHelper.accessor("id",{
|
||||
id: "sequence",
|
||||
header: "#",
|
||||
cell: props => props.row.index + 1,
|
||||
}),
|
||||
|
||||
columnHelper.accessor("code", {
|
||||
header: 'Code',
|
||||
}),
|
||||
const createColumns = (options: ColumnOptions) => {
|
||||
const columnHelper = createColumnHelper<RoleRow>();
|
||||
|
||||
columnHelper.accessor("name", {
|
||||
header: "Name"
|
||||
}),
|
||||
const columns = [
|
||||
columnHelper.accessor("id", {
|
||||
id: "sequence",
|
||||
header: "#",
|
||||
cell: (props) => props.row.index + 1,
|
||||
}),
|
||||
|
||||
columnHelper.accessor("permissionCount", {
|
||||
header: "Permissions"
|
||||
}),
|
||||
columnHelper.accessor("code", {
|
||||
header: "Code",
|
||||
}),
|
||||
|
||||
columnHelper.accessor("userCount", {
|
||||
header: "Users"
|
||||
}),
|
||||
columnHelper.accessor("name", {
|
||||
header: "Name",
|
||||
}),
|
||||
|
||||
columnHelper.display({
|
||||
id: "Actions",
|
||||
header: "Actions",
|
||||
})
|
||||
]
|
||||
columnHelper.accessor("isActive", {
|
||||
header: "Status",
|
||||
cell: (props) =>
|
||||
props.getValue() ? (
|
||||
<Badge color="green">Enabled</Badge>
|
||||
) : (
|
||||
<Badge color="orange">Disabled</Badge>
|
||||
),
|
||||
}),
|
||||
|
||||
export default columns;
|
||||
columnHelper.accessor("permissionCount", {
|
||||
header: "Permissions",
|
||||
}),
|
||||
|
||||
columnHelper.accessor("userCount", {
|
||||
header: "Users",
|
||||
}),
|
||||
|
||||
columnHelper.display({
|
||||
id: "Actions",
|
||||
header: "Actions",
|
||||
size: 10,
|
||||
meta: {
|
||||
className: "w-fit",
|
||||
},
|
||||
cell: () => (
|
||||
<Flex gap="xs">
|
||||
{
|
||||
createActionButtons([
|
||||
{
|
||||
label: "Detail",
|
||||
permission: options.permissions.read,
|
||||
action: options.actions?.detail,
|
||||
color: "green",
|
||||
icon: <TbEye />
|
||||
},
|
||||
{
|
||||
label: "Edit",
|
||||
permission: options.permissions.update,
|
||||
action: options.actions?.edit,
|
||||
color: "yellow",
|
||||
icon: <TbPencil />
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
permission: options.permissions.delete,
|
||||
action: options.actions?.delete,
|
||||
color: "red",
|
||||
icon: <TbTrash />
|
||||
}
|
||||
])
|
||||
}
|
||||
</Flex>
|
||||
),
|
||||
}),
|
||||
];
|
||||
|
||||
return columns;
|
||||
};
|
||||
|
||||
export default createColumns;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import Link from "next/link";
|
|||
import { CreateModal } from "./_modals";
|
||||
import FormModal from "./_modals/FormModal";
|
||||
import CreateButton from "./_components/CreateButton/CreateButton";
|
||||
import getRoles from "@/features/dashboard/roles/data/getRoles";
|
||||
import checkMultiplePermissions from "@/features/auth/tools/checkMultiplePermissions";
|
||||
|
||||
interface Props {
|
||||
searchParams: {
|
||||
|
|
@ -20,20 +22,25 @@ interface Props {
|
|||
}
|
||||
|
||||
export default async function RolesPage({searchParams}: Props) {
|
||||
if (!(await checkPermission("role.readAll"))) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
const allowCreate = await checkPermission("role.create")
|
||||
const permissions = await checkMultiplePermissions({
|
||||
create: "role.create",
|
||||
readAll: "role.readAll",
|
||||
read: 'role.read',
|
||||
update: 'role.update',
|
||||
delete: 'role.delete'
|
||||
})
|
||||
|
||||
const roles = await getRoles()
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={1}>Roles</Title>
|
||||
<Card>
|
||||
<Flex justify="flex-end">
|
||||
{ allowCreate && <CreateButton />}
|
||||
{ permissions.create && <CreateButton />}
|
||||
</Flex>
|
||||
<RolesTable permissions={{}} />
|
||||
<RolesTable permissions={permissions} roles={roles} />
|
||||
</Card>
|
||||
</Stack>
|
||||
);
|
||||
|
|
|
|||
31
src/features/auth/tools/checkMultiplePermissions.ts
Normal file
31
src/features/auth/tools/checkMultiplePermissions.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { getUserFromToken } from "../authUtils";
|
||||
import getCurrentUser from "./getCurrentUser";
|
||||
|
||||
type PermissionsInput = Record<string, string>;
|
||||
|
||||
type PermissionsOutput = Record<string, boolean>;
|
||||
|
||||
/**
|
||||
* Checks multiple permissions for the current user and returns an object indicating
|
||||
* whether each permission is granted.
|
||||
*
|
||||
* @param {PermissionsInput} permissions - An object with keys as permission names and values as the required roles/permissions.
|
||||
* @returns {Promise<PermissionsOutput>} An object with keys as permission names and boolean values indicating whether the permission is granted.
|
||||
*/
|
||||
async function checkMultiplePermissions<T extends Record<string, string>>(permissions: T): Promise<{ [K in keyof T]: boolean }> {
|
||||
const permissionResults: Partial<{ [K in keyof T]: boolean }> = {};
|
||||
const currentUser = await getCurrentUser();
|
||||
|
||||
for (const permissionKey in permissions) {
|
||||
if (permissions.hasOwnProperty(permissionKey)) {
|
||||
const requiredPermission = permissions[permissionKey];
|
||||
// const isPermissionGranted: boolean = currentUser ? currentUser.roles.includes(requiredPermission) : false;
|
||||
const isPermissionGranted = true;
|
||||
permissionResults[permissionKey] = isPermissionGranted;
|
||||
}
|
||||
}
|
||||
|
||||
return permissionResults as { [K in keyof T]: boolean };
|
||||
}
|
||||
|
||||
export default checkMultiplePermissions;
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
"use server"
|
||||
"use server";
|
||||
|
||||
import checkPermission from "@/features/auth/tools/checkPermission";
|
||||
import roleFormDataSchema, { RoleFormData } from "../formSchemas/RoleFormData";
|
||||
import { unauthorized } from "@/BaseError";
|
||||
|
|
@ -6,63 +7,64 @@ import mapObjectToFirstValue from "@/utils/mapObjectToFirstValue";
|
|||
import prisma from "@/db";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
export default async function upsertRole(data: RoleFormData){
|
||||
|
||||
const isInsert = !!data.id;
|
||||
/**
|
||||
* Upserts a role based on the provided RoleFormData.
|
||||
* If the role already exists (determined by `id`), it updates the role; otherwise, it creates a new role.
|
||||
* Authorization checks are performed based on whether it's a create or update operation.
|
||||
*
|
||||
* @param {RoleFormData} data - The data for creating or updating the role.
|
||||
* @returns {Promise<object>} An object containing the success status, message, and any errors.
|
||||
*/
|
||||
export default async function upsertRole(data: RoleFormData) {
|
||||
const isInsert = !data.id;
|
||||
|
||||
if (isInsert && !await checkPermission("role.create")){
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
if (!isInsert && !await checkPermission("role.update")){
|
||||
// Authorization check
|
||||
const permissionType = isInsert ? "role.create" : "role.update";
|
||||
if (!await checkPermission(permissionType)) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
// Validate form data
|
||||
const validatedFields = roleFormDataSchema.safeParse(data);
|
||||
if (!validatedFields.success){
|
||||
if (!validatedFields.success) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Invalid Form Data",
|
||||
errors: mapObjectToFirstValue(validatedFields.error.flatten().fieldErrors)
|
||||
} as const
|
||||
errors: mapObjectToFirstValue(validatedFields.error.flatten().fieldErrors),
|
||||
};
|
||||
}
|
||||
|
||||
// Update user data in the database
|
||||
try {
|
||||
if (isInsert){
|
||||
await prisma.role.update({
|
||||
where: { id: validatedFields.data.id!},
|
||||
data: {
|
||||
code: validatedFields.data.code,
|
||||
description: validatedFields.data.description,
|
||||
isActive: validatedFields.data.isActive,
|
||||
name: validatedFields.data.name
|
||||
},
|
||||
})
|
||||
const roleData = {
|
||||
code: validatedFields.data.code,
|
||||
description: validatedFields.data.description,
|
||||
name: validatedFields.data.name,
|
||||
isActive: validatedFields.data.isActive,
|
||||
};
|
||||
|
||||
// Database operation
|
||||
if (isInsert) {
|
||||
await prisma.role.create({ data: roleData });
|
||||
} else {
|
||||
await prisma.role.create({
|
||||
data: {
|
||||
code: validatedFields.data.code,
|
||||
description: validatedFields.data.description,
|
||||
name: validatedFields.data.name,
|
||||
isActive: validatedFields.data.isActive
|
||||
}
|
||||
})
|
||||
await prisma.role.update({
|
||||
where: { id: validatedFields.data.id! },
|
||||
data: roleData,
|
||||
});
|
||||
}
|
||||
|
||||
// Revalidate the cache
|
||||
revalidatePath(".");
|
||||
|
||||
// Return success message
|
||||
return {
|
||||
success: true,
|
||||
message: `Role ${validatedFields.data.name} has been successfully ${isInsert ? "Updated" : "Created"}`
|
||||
message: `Role ${validatedFields.data.name} has been successfully ${isInsert ? "created" : "updated"}.`,
|
||||
};
|
||||
} catch (error) {
|
||||
// Consider handling specific database errors here
|
||||
console.error('Error updating user data', error);
|
||||
console.error('Error updating role data', error);
|
||||
return {
|
||||
success: false,
|
||||
message: "Error updating user data"
|
||||
message: "Error updating role data.",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
45
src/features/dashboard/roles/data/getRoles.ts
Normal file
45
src/features/dashboard/roles/data/getRoles.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { unauthorized } from "@/BaseError";
|
||||
import prisma from "@/db";
|
||||
import checkPermission from "@/features/auth/tools/checkPermission";
|
||||
import "server-only";
|
||||
|
||||
/**
|
||||
* Retrieves all roles along with the count of associated permissions and users.
|
||||
* Authorization check is performed for the operation.
|
||||
*
|
||||
* @returns An array of role objects each including details and counts of related permissions and users.
|
||||
*/
|
||||
export default async function getRoles() {
|
||||
// Authorization check
|
||||
if (!await checkPermission("roles.getAll")) {
|
||||
return unauthorized();
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch roles from the database
|
||||
const roles = await prisma.role.findMany({
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
permissions: true,
|
||||
users: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Transform the data into the desired format
|
||||
return roles.map(({ id, code, name, description, isActive, _count }) => ({
|
||||
id,
|
||||
code,
|
||||
name,
|
||||
description,
|
||||
isActive,
|
||||
permissionCount: _count.permissions,
|
||||
userCount: _count.users
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error retrieving roles', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
50
src/features/dashboard/utils/createActionButtons.tsx
Normal file
50
src/features/dashboard/utils/createActionButtons.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import {
|
||||
ActionIcon,
|
||||
ActionIconVariant,
|
||||
MantineColor,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
|
||||
interface Action {
|
||||
label: string;
|
||||
action?: () => void | string;
|
||||
variant?: ActionIconVariant;
|
||||
permission?: boolean;
|
||||
icon: React.ReactNode;
|
||||
color: MantineColor;
|
||||
}
|
||||
|
||||
export default function createActionButtons(actions: Action[]) {
|
||||
const defaults = {
|
||||
variant: "light",
|
||||
};
|
||||
|
||||
const elements = actions.map((action, i) =>
|
||||
action.permission ? (
|
||||
<Tooltip label={action.label} key={i}>
|
||||
{typeof action.action === "string" || action.action === undefined ? (
|
||||
<ActionIcon
|
||||
variant={action.variant ?? defaults.variant}
|
||||
color={action.color}
|
||||
component={Link}
|
||||
href={action.action ?? "#"}
|
||||
>
|
||||
{action.icon}
|
||||
</ActionIcon>
|
||||
) : (
|
||||
<ActionIcon
|
||||
variant={action.variant ?? defaults.variant}
|
||||
color={action.color}
|
||||
onClick={action.action}
|
||||
>
|
||||
{action.icon}
|
||||
</ActionIcon>
|
||||
)}
|
||||
</Tooltip>
|
||||
) : null
|
||||
);
|
||||
|
||||
return <>{elements}</>;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user