Skip to content

Commit

Permalink
Authority views and actions update (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
haiphucnguyen authored Dec 4, 2024
1 parent ac37231 commit 838bb68
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 198 deletions.
22 changes: 0 additions & 22 deletions src/app/portal/settings/authorities/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
"use client";

import { Plus } from "lucide-react";
import { useRouter } from "next/navigation";
import React from "react";

import { SimpleContentView } from "@/components/admin-panel/simple-content-view";
import { AuthoritiesView } from "@/components/authorities/authority-list";
import { Heading } from "@/components/heading";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { usePagePermission } from "@/hooks/use-page-permission";
import { PermissionUtils } from "@/types/resources";

const breadcrumbItems = [
{ title: "Dashboard", link: "/portal" },
Expand All @@ -19,23 +12,8 @@ const breadcrumbItems = [
];

const AuthoritiesPage = () => {
const router = useRouter();
const permissionLevel = usePagePermission();

return (
<SimpleContentView title="Authorities" breadcrumbItems={breadcrumbItems}>
<div className="flex flex-row justify-between">
<Heading title="Authorities" description="Manage authorities" />
{PermissionUtils.canWrite(permissionLevel) && (
<Button
onClick={() => router.push("/portal/settings/authorities/new/edit")}
>
<Plus />
New Authority
</Button>
)}
</div>
<Separator />
<AuthoritiesView />
</SimpleContentView>
);
Expand Down
9 changes: 8 additions & 1 deletion src/components/authorities/authority-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ const AuthorityForm: React.FC<NewAuthorityFormProps> = ({
);
}

const isSystemRole = authorityEntity?.systemRole;

return (
<div className="flex flex-col">
<Form {...form}>
Expand All @@ -110,7 +112,11 @@ const AuthorityForm: React.FC<NewAuthorityFormProps> = ({
<FormItem>
<FormLabel>Authority Name</FormLabel>
<FormControl>
<Input placeholder="Enter authority name" {...field} />
<Input
placeholder="Enter authority name"
{...field}
disabled={isSystemRole}
/>
</FormControl>
<FormMessage />
</FormItem>
Expand Down Expand Up @@ -149,6 +155,7 @@ const AuthorityForm: React.FC<NewAuthorityFormProps> = ({
<Select
onValueChange={field.onChange}
defaultValue={field.value || "NONE"}
disabled={isSystemRole}
>
<SelectTrigger>
<SelectValue placeholder="Select permission" />
Expand Down
240 changes: 135 additions & 105 deletions src/components/authorities/authority-list.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,47 @@
"use client";

import { Ellipsis, Plus, Shield, Trash } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import React, { useEffect, useState } from "react";

import { Heading } from "@/components/heading";
import PaginationExt from "@/components/shared/pagination-ext";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; // Add a badge for visual distinction
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { getAuthorities } from "@/lib/actions/authorities.action";
import { obfuscate } from "@/lib/endecode";
import { AuthorityDTO } from "@/types/authorities";
import PaginationExt from "@/components/shared/pagination-ext";
import { PermissionUtils } from "@/types/resources";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Ellipsis, Trash, Shield } from "lucide-react";
import { Separator } from "@/components/ui/separator";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { usePagePermission } from "@/hooks/use-page-permission";
import {
deleteAuthority,
getAuthorities,
} from "@/lib/actions/authorities.action";
import { obfuscate } from "@/lib/endecode";
import { AuthorityDTO } from "@/types/authorities";
import { PermissionUtils } from "@/types/resources";

export function AuthoritiesView() {
const router = useRouter();
const permissionLevel = usePagePermission();

const [authorities, setAuthorities] = useState<Array<AuthorityDTO>>([]);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(0);
Expand All @@ -41,125 +50,146 @@ export function AuthoritiesView() {
useState<AuthorityDTO | null>(null);
const [isDialogOpen, setIsDialogOpen] = useState(false);

const permissionLevel = usePagePermission();
const fetchAuthorities = async () => {
getAuthorities(currentPage).then((pageableResult) => {
setAuthorities(pageableResult.content);
setTotalPages(pageableResult.totalPages);
setTotalElements(pageableResult.totalElements);
});
};

useEffect(() => {
const fetchAuthorities = async () => {
getAuthorities().then((pageableResult) => {
setAuthorities(pageableResult.content);
setTotalPages(pageableResult.totalPages);
setTotalElements(pageableResult.totalElements);
});
};

fetchAuthorities();
}, []);
}, [currentPage]);

function handleDeleteClick(authority: AuthorityDTO) {
setSelectedAuthority(authority);
setIsDialogOpen(true);
}

function confirmDeleteAuthority() {
async function confirmDeleteAuthority() {
if (selectedAuthority) {
console.log(`Delete ${JSON.stringify(selectedAuthority)}`);
// Add actual delete logic here (e.g., API call)
await deleteAuthority(selectedAuthority.name);
setSelectedAuthority(null);
fetchAuthorities();
}
setIsDialogOpen(false);
setSelectedAuthority(null);
}

return (
<div className="flex flex-col md:flex-row md:space-x-4 items-start">
<div className="md:flex-1 flex flex-row flex-wrap w-full gap-4 pt-2">
{authorities?.map((authority) => (
<div
className={`w-full md:w-[24rem] flex flex-row gap-4 border px-4 py-4 rounded-2xl relative ${
authority.systemRole
? "bg-gray-100 border-gray-300"
: "bg-white border-gray-200"
}`}
key={authority.name}
<div className="grid grid-cols-1 gap-4">
<div className="flex flex-row justify-between">
<Heading
title={`Authorities (${totalElements})`}
description="Manage authorities"
/>
{PermissionUtils.canWrite(permissionLevel) && (
<Button
onClick={() => router.push("/portal/settings/authorities/new/edit")}
>
<div className="flex flex-col">
<div className="flex items-center gap-2">
<Button variant="link" className="px-0 text-xl">
<Link
href={`/portal/settings/authorities/${obfuscate(authority.name)}`}
>
{authority.descriptiveName} ({authority.usersCount})
</Link>
</Button>
{authority.systemRole && (
<Badge
variant="outline"
className="text-blue-500 border-blue-500"
>
<Shield className="w-4 h-4 mr-1" />
System Role
</Badge>
)}
<Plus />
New Authority
</Button>
)}
</div>
<Separator />
<div className="flex flex-col md:flex-row md:space-x-4 items-start">
<div className="md:flex-1 flex flex-row flex-wrap w-full gap-4 pt-2">
{authorities?.map((authority) => (
<div
className={`w-full md:w-[24rem] flex flex-row gap-4 px-4 py-4 rounded-2xl relative
${
authority.systemRole
? "bg-gray-100 border-gray-300 dark:bg-gray-800 dark:border-gray-700"
: "bg-gray-50 border-gray-200 dark:bg-gray-900 dark:border-gray-600"
}`}
key={authority.name}
>
<div className="flex flex-col">
<div className="flex items-center gap-2">
<Button variant="link" className="px-0 text-xl">
<Link
href={`/portal/settings/authorities/${obfuscate(authority.name)}`}
>
{authority.descriptiveName} ({authority.usersCount})
</Link>
</Button>
{authority.systemRole && (
<Badge
variant="outline"
className="text-blue-500 border-blue-500 dark:text-blue-300 dark:border-blue-600"
>
<Shield className="w-4 h-4 mr-1" />
System Role
</Badge>
)}
</div>
<div>{authority.description}</div>
</div>
<div>{authority.description}</div>

{PermissionUtils.canAccess(permissionLevel) &&
!authority.systemRole && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Ellipsis className="cursor-pointer absolute top-2 right-2 text-gray-400" />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[14rem]">
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => handleDeleteClick(authority)}
>
<Trash /> Remove authority
</DropdownMenuItem>
</TooltipTrigger>
<TooltipContent>
<p>
This action will delete the role and unassign all
users from this role.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
{PermissionUtils.canAccess(permissionLevel) &&
!authority.systemRole && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Ellipsis className="cursor-pointer absolute top-2 right-2 text-gray-400" />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[14rem] w-full">
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => handleDeleteClick(authority)}
>
<Trash /> Remove authority
</DropdownMenuItem>
</TooltipTrigger>
<TooltipContent>
<p>
This action will delete the role and unassign all
users from this role.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
))}
<PaginationExt
currentPage={currentPage}
totalPages={totalPages}
onPageChange={(page) => setCurrentPage(page)}
/>
</div>
))}
<PaginationExt
currentPage={currentPage}
totalPages={totalPages}
onPageChange={(page) => setCurrentPage(page)}
/>
</div>

{/* Confirmation Dialog */}
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Confirm Deletion</DialogTitle>
</DialogHeader>
<p>
Are you sure you want to delete{" "}
<strong>{selectedAuthority?.descriptiveName}</strong>? This action
cannot be undone.
</p>
<DialogFooter>
<Button variant="secondary" onClick={() => setIsDialogOpen(false)}>
Cancel
</Button>
<Button variant="destructive" onClick={confirmDeleteAuthority}>
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Confirm Deletion</DialogTitle>
</DialogHeader>
<p>
Are you sure you want to delete{" "}
<strong>{selectedAuthority?.descriptiveName}</strong>? This action
cannot be undone.
</p>
<DialogFooter>
<Button
variant="secondary"
onClick={() => setIsDialogOpen(false)}
>
Cancel
</Button>
<Button variant="destructive" onClick={confirmDeleteAuthority}>
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</div>
);
}
Loading

0 comments on commit 838bb68

Please sign in to comment.