Update role columns

This commit is contained in:
Sianida26 2024-01-28 00:04:45 +07:00
parent bf41292b28
commit 85daf8cfe7
7 changed files with 277 additions and 76 deletions

View File

@ -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>,

View File

@ -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;

View File

@ -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>
);

View 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;

View File

@ -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.",
};
}
}
}

View 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;
}
}

View 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}</>;
}