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, useReactTable,
} from "@tanstack/react-table"; } from "@tanstack/react-table";
import React from "react"; import React from "react";
import columns from "./columns";
import CrudPermissions from "@/features/auth/types/CrudPermissions"; import CrudPermissions from "@/features/auth/types/CrudPermissions";
import getRoles from "@/features/dashboard/roles/data/getRoles";
import createColumns from "./columns";
interface Props { interface Props {
permissions: Partial<CrudPermissions>, permissions: Partial<CrudPermissions>,
roles: Awaited<ReturnType<typeof getRoles>>
} }
export default function RolesTable(props: Props) { export default function RolesTable(props: Props) {
const table = useReactTable({ const table = useReactTable({
data: [], data: props.roles,
columns, columns: createColumns({
permissions: props.permissions
}),
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
defaultColumn: { defaultColumn: {
cell: (props) => <Text>{props.getValue() as React.ReactNode}</Text>, 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 { createColumnHelper } from "@tanstack/react-table";
import Link from "next/link";
import { StringifyOptions } from "querystring"; import { StringifyOptions } from "querystring";
import { TbEye, TbPencil, TbTrash } from "react-icons/tb";
export interface RoleRow { export interface RoleRow {
id: string, id: string;
code: string, code: string;
name: string, name: string;
permissionCount: number, isActive: boolean;
userCount: number, permissionCount: number;
userCount: number;
} }
const columnHelper = createColumnHelper<RoleRow>() interface ColumnOptions {
permissions: Partial<CrudPermissions>;
actions?: {
detail?: () => void;
edit?: () => void;
delete?: () => void;
};
}
const createColumns = (options: ColumnOptions) => {
const columnHelper = createColumnHelper<RoleRow>();
const columns = [ const columns = [
columnHelper.accessor("id", { columnHelper.accessor("id", {
id: "sequence", id: "sequence",
header: "#", header: "#",
cell: props => props.row.index + 1, cell: (props) => props.row.index + 1,
}), }),
columnHelper.accessor("code", { columnHelper.accessor("code", {
header: 'Code', header: "Code",
}), }),
columnHelper.accessor("name", { columnHelper.accessor("name", {
header: "Name" header: "Name",
}),
columnHelper.accessor("isActive", {
header: "Status",
cell: (props) =>
props.getValue() ? (
<Badge color="green">Enabled</Badge>
) : (
<Badge color="orange">Disabled</Badge>
),
}), }),
columnHelper.accessor("permissionCount", { columnHelper.accessor("permissionCount", {
header: "Permissions" header: "Permissions",
}), }),
columnHelper.accessor("userCount", { columnHelper.accessor("userCount", {
header: "Users" header: "Users",
}), }),
columnHelper.display({ columnHelper.display({
id: "Actions", id: "Actions",
header: "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>
),
}),
];
export default columns; return columns;
};
export default createColumns;

View File

@ -9,6 +9,8 @@ import Link from "next/link";
import { CreateModal } from "./_modals"; import { CreateModal } from "./_modals";
import FormModal from "./_modals/FormModal"; import FormModal from "./_modals/FormModal";
import CreateButton from "./_components/CreateButton/CreateButton"; import CreateButton from "./_components/CreateButton/CreateButton";
import getRoles from "@/features/dashboard/roles/data/getRoles";
import checkMultiplePermissions from "@/features/auth/tools/checkMultiplePermissions";
interface Props { interface Props {
searchParams: { searchParams: {
@ -20,20 +22,25 @@ interface Props {
} }
export default async function RolesPage({searchParams}: 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 ( return (
<Stack> <Stack>
<Title order={1}>Roles</Title> <Title order={1}>Roles</Title>
<Card> <Card>
<Flex justify="flex-end"> <Flex justify="flex-end">
{ allowCreate && <CreateButton />} { permissions.create && <CreateButton />}
</Flex> </Flex>
<RolesTable permissions={{}} /> <RolesTable permissions={permissions} roles={roles} />
</Card> </Card>
</Stack> </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 checkPermission from "@/features/auth/tools/checkPermission";
import roleFormDataSchema, { RoleFormData } from "../formSchemas/RoleFormData"; import roleFormDataSchema, { RoleFormData } from "../formSchemas/RoleFormData";
import { unauthorized } from "@/BaseError"; import { unauthorized } from "@/BaseError";
@ -6,63 +7,64 @@ import mapObjectToFirstValue from "@/utils/mapObjectToFirstValue";
import prisma from "@/db"; import prisma from "@/db";
import { revalidatePath } from "next/cache"; import { revalidatePath } from "next/cache";
/**
* 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) { export default async function upsertRole(data: RoleFormData) {
const isInsert = !data.id;
const isInsert = !!data.id; // Authorization check
const permissionType = isInsert ? "role.create" : "role.update";
if (isInsert && !await checkPermission("role.create")){ if (!await checkPermission(permissionType)) {
return unauthorized();
}
if (!isInsert && !await checkPermission("role.update")){
return unauthorized(); return unauthorized();
} }
// Validate form data
const validatedFields = roleFormDataSchema.safeParse(data); const validatedFields = roleFormDataSchema.safeParse(data);
if (!validatedFields.success) { if (!validatedFields.success) {
return { return {
success: false, success: false,
message: "Invalid Form Data", message: "Invalid Form Data",
errors: mapObjectToFirstValue(validatedFields.error.flatten().fieldErrors) errors: mapObjectToFirstValue(validatedFields.error.flatten().fieldErrors),
} as const };
} }
// Update user data in the database
try { try {
if (isInsert){ const roleData = {
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
},
})
} else {
await prisma.role.create({
data: {
code: validatedFields.data.code, code: validatedFields.data.code,
description: validatedFields.data.description, description: validatedFields.data.description,
name: validatedFields.data.name, name: validatedFields.data.name,
isActive: validatedFields.data.isActive isActive: validatedFields.data.isActive,
} };
})
// Database operation
if (isInsert) {
await prisma.role.create({ data: roleData });
} else {
await prisma.role.update({
where: { id: validatedFields.data.id! },
data: roleData,
});
} }
// Revalidate the cache // Revalidate the cache
revalidatePath("."); revalidatePath(".");
// Return success message
return { return {
success: true, 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) { } catch (error) {
// Consider handling specific database errors here console.error('Error updating role data', error);
console.error('Error updating user data', error);
return { return {
success: false, 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}</>;
}