Nn

import { useState, useRef, useEffect, type JSX } from "react";


import { type ColDef } from "ag-grid-community";


import { Button } from "../../../Shared/Components/Button/Button";


import { Input } from "../../../Shared/Components/Input/Input";


import { Card, CardContent } from "../../../Shared/Components/Card/Card";


import {

  Dialog,

  DialogContent,

  DialogDescription,

  DialogHeader,

  DialogTitle,

} from "../../../Shared/Components/Dialog/Dialog";


import {

  Collapsible,

  CollapsibleContent,

  CollapsibleTrigger,

} from "../../../Shared/Components/Collapsible/Collapsible";


import { toast } from "sonner";


import { Switch } from "../../../Shared/Components/Switch/Switch";


import { Textarea } from "../../../Shared/Components/Textarea/Textarea";


import { ChevronDown, ChevronRight } from "lucide-react";


import AGGrid, {

  type AGGridRef,

} from "../../../Shared/Components/Ag-grid/Ag-grid";


import ActionsRenderer from "../../../Shared/Components/CellRenderer/CellRenderer";

import { apiService } from "../../../Services/apiService";


// Use environment variable for API base URL


const BASE_URL =

  import.meta.env.VITE_API_BASE_URL || "http://localhost:5000/api";


interface Permission {

  label: string;


  value: string;


  field: string;

}


interface PermissionArea {

  label: string;


  key: string;


  children: Permission[];

}


interface UserRole {

  id: string;


  role_id: string;


  role_name: string;


  description: string;


  last_updated_time?: string;


  last_updated_by?: string;


  active?: boolean;


  permissions?: Record<string, Record<string, boolean>>;

}


function PermissionToggle({

  label,


  description,


  checked,


  onCheckedChange,

}: {

  label: string;


  description: string;


  checked: boolean;


  onCheckedChange: (checked: boolean) => void;

}) {

  return (

    <div className="flex items-center justify-between py-2">

      <div className="flex-1">

        <div className="flex items-center gap-3">

          <Switch checked={checked} onCheckedChange={onCheckedChange} />


          <div>

            <p className="text-sm font-medium">{label}</p>


            <p className="text-xs text-gray-500">{description}</p>

          </div>

        </div>

      </div>

    </div>

  );

}


function PermissionSection({

  title,


  permissions,


  permissionKeys,


  onPermissionChange,

}: {

  title: string;


  permissions: Record<string, boolean>;


  permissionKeys: string[];


  onPermissionChange: (permission: string, value: boolean) => void;

}) {

  const [isOpen, setIsOpen] = useState(true);


  const permissionDescriptions = (key: string) => {

    return `Can ${key.toLowerCase()} ${title.toLowerCase()}`;

  };


  return (

    <Collapsible open={isOpen} onOpenChange={setIsOpen}>

      <CollapsibleTrigger className="flex items-center justify-between w-full p-3 bg-gray-50 hover:bg-gray-100 rounded-lg transition-colors">

        <h4 className="font-medium">{title}</h4>


        {isOpen ? (

          <ChevronDown className="w-4 h-4" />

        ) : (

          <ChevronRight className="w-4 h-4" />

        )}

      </CollapsibleTrigger>


      <CollapsibleContent className="px-3 pb-3">

        <div className="space-y-2 pt-2">

          {permissionKeys.map((key) => (

            <PermissionToggle

              key={key}

              label={key.charAt(0).toUpperCase() + key.slice(1)}

              description={permissionDescriptions(key)}

              checked={permissions[key] || false}

              onCheckedChange={(checked) => onPermissionChange(key, checked)}

            />

          ))}

        </div>

      </CollapsibleContent>

    </Collapsible>

  );

}


type UserRolesGridProps = {

  onEdit: (role: UserRole) => void;


  onDelete: (role: UserRole) => void;


  refreshTrigger: number;

};


function UserRolesGrid({

  onEdit,

  onDelete,

  refreshTrigger,

}: UserRolesGridProps) {

  const gridRef = useRef<AGGridRef>(null);


  const [columnDefs, setColumnDefs] = useState<ColDef[]>([]);


  const [userRoleRowData, setUserRoleRowData] = useState<UserRole[]>([]);


  const [loading, setLoading] = useState(true);


  const handleActionClick = (actionType: string, row: UserRole) => {

    switch (actionType) {

      case "edit":

        onEdit(row);


        break;


      case "delete":

        onDelete(row);


        break;


      default:

        console.warn("Unhandled action:", actionType);

    }

  };


  const payload = {

    filterModel: [

      {

        search_text: "",

        type: "contains",

        column_name: "",

      },

    ],

    sortModel: {

      sort: "",

      column_name: "",

    },

    startRow: 0,

    endRow: 100,

    page: 1,

    records: 50,

  };


  const fetchData = async () => {

    try {

      setLoading(true);


      const response = await apiService.fetchUserRoles(payload);


      if (!response) throw new Error("Failed to fetch roles");


      const roles: UserRole[] = await response.json();


      const columns: ColDef[] = [

        {

          headerName: "Role Name",

          field: "role_name",

          sortable: true,

          resizable: true,

        },


        {

          headerName: "Description",

          field: "description",

          sortable: true,

          resizable: true,

        },


        {

          headerName: "Actions",


          field: "actions",


          width: 100,


          cellRendererFramework: ActionsRenderer,


          cellRendererParams: {

            actions: [

              {

                action: "edit",

                tooltip: "Edit",

                type: "edit",

                class: "fa fa-pencil",

              },


              {

                action: "delete",

                tooltip: "Delete",

                type: "delete",

                class: "fa fa-trash",

              },

            ],


            onActionClick: handleActionClick,

          },


          suppressMenu: true,


          sortable: false,


          filter: false,


          resizable: false,

        },

      ];


      setColumnDefs(columns);


      setUserRoleRowData(roles);

    } catch (error) {

      console.error("Error fetching roles:", error);


      toast.error("Failed to fetch roles from server");

    } finally {

      setLoading(false);

    }

  };


  useEffect(() => {

    fetchData();

  }, [refreshTrigger]);


  return (

    <div className="space-y-4">

      <div className="hidden md:block">

        <div className="ag-theme-alpine" style={{ height: 400, width: "100%" }}>

          {loading ? (

            <div className="flex justify-center items-center h-full">

              <div className="text-gray-500">Loading...</div>

            </div>

          ) : userRoleRowData.length > 0 ? (

            <AGGrid

              ref={gridRef}

              columnDefs={columnDefs}

              cacheBlockSize={100}

              maxBlocksInCache={5}

              rowData={userRoleRowData}

              height={400}

              enableSorting={false}

              enableFiltering={false}

              enableResizing={true}

              animateRows={true}

              headerHeight={40}

              rowHeight={35}

              domLayout="normal"

            />

          ) : (

            <div className="flex justify-center items-center h-full text-gray-500">

              No user roles found.

            </div>

          )}

        </div>

      </div>


      <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between p-4 bg-white border border-[#b8bcbf]">

        <div className="flex items-center space-x-6 text-sm font-['Roboto',sans-serif]">

          <div className="flex items-center gap-1">

            <span className="font-normal">Total Roles</span>


            <span>:</span>


            <span className="font-medium">{userRoleRowData.length}</span>

          </div>

        </div>

      </div>

    </div>

  );

}


export default function UserRoles() {

  const [showCreateForm, setShowCreateForm] = useState(false);


  const [editingRole, setEditingRole] = useState<UserRole | null>(null);


  const [showDeleteDialog, setShowDeleteDialog] = useState(false);


  const [deletingRole, setDeletingRole] = useState<UserRole | null>(null);


  const [permissionAreas, setPermissionAreas] = useState<PermissionArea[]>([]);


  const [userRoleFormData, setUserRoleFormData] = useState<

    Omit<UserRole, "id">

  >({

    role_id: "",


    description: "",


    active: true,


    role_name: "",


    permissions: {},

  });


  const [refreshTrigger, setRefreshTrigger] = useState(0);


  const handleEdit = async (role: UserRole) => {

    try {

      const response = await fetch(`${BASE_URL}/permissions`);


      if (!response.ok) throw new Error("Failed to fetch permissions");


      const permissionAreas: PermissionArea[] = await response.json();


      setPermissionAreas(permissionAreas);


      const initializedPermissions = permissionAreas.reduce(

        (acc: any, area) => ({

          ...acc,


          [area.key]: area.children.reduce(

            (permAcc, perm) => ({

              ...permAcc,


              [perm.value]: role.permissions?.[area.key]?.[perm.value] || false,

            }),


            {} as Record<string, boolean>

          ),

        }),


        {} as Record<string, Record<string, boolean>>

      );


      setUserRoleFormData({

        role_name: role.role_name,


        description: role.description,


        permissions: initializedPermissions,


        role_id: role.role_id,

      });


      setEditingRole(role);


      setShowCreateForm(true);

    } catch (error) {

      console.error("Failed to edit role:", error);


      toast.error("Failed to load role details. Please try again.");

    }

  };


  const handleCreate = async () => {

    try {

      const response = await fetch(`${BASE_URL}/permissions`);


      if (!response.ok) throw new Error("Failed to fetch permissions");


      const permissionAreas: PermissionArea[] = await response.json();


      setPermissionAreas(permissionAreas);


      const initializedPermissions = permissionAreas.reduce(

        (acc: any, area) => ({

          ...acc,


          [area.key]: area.children.reduce(

            (permAcc: any, perm) => ({

              ...permAcc,


              [perm.value]: false,

            }),


            {} as Record<string, boolean>

          ),

        }),


        {} as Record<string, Record<string, boolean>>

      );


      setUserRoleFormData({

        role_name: "",


        description: "",


        permissions: initializedPermissions,


        role_id: "",

      });


      setShowCreateForm(true);

    } catch (error) {

      console.error("Failed to initialize form:", error);


      toast.error("Failed to load permissions. Please try again.");

    }

  };


  const handlePermissionChange = (

    section: string,

    permission: string,

    value: boolean

  ) => {

    setUserRoleFormData((prev) => ({

      ...prev,


      permissions: {

        ...(prev.permissions ?? {}),


        [section]: {

          ...(prev.permissions?.[section] ?? {}),


          [permission]: value,

        },

      },

    }));

  };


  const handleDelete = (role: UserRole) => {

    setDeletingRole(role);


    setShowDeleteDialog(true);

  };


  const confirmDeleteRole = async () => {

    if (!deletingRole) return;


    try {

      const response = await fetch(`${BASE_URL}/roles/${deletingRole.id}`, {

        method: "DELETE",

      });


      if (!response.ok) throw new Error("Failed to delete role");


      toast.success(`Role "${deletingRole.role_name}" deleted successfully.`);


      setShowDeleteDialog(false);


      setDeletingRole(null);


      setRefreshTrigger((prev) => prev + 1);

    } catch (error) {

      console.error("Error deleting role:", error);


      toast.error("Failed to delete role.");

    }

  };


  const handleSave = async () => {

    if (!userRoleFormData.role_name.trim()) {

      toast.error("Role name is required");


      return;

    }


    try {

      if (editingRole) {

        const response = await fetch(`${BASE_URL}/roles/${editingRole.id}`, {

          method: "PUT",


          headers: { "Content-Type": "application/json" },


          body: JSON.stringify(userRoleFormData),

        });


        if (!response.ok) throw new Error("Failed to update role");


        toast.success("User role updated successfully");

      } else {

        const response = await fetch(`${BASE_URL}/roles`, {

          method: "POST",


          headers: { "Content-Type": "application/json" },


          body: JSON.stringify(userRoleFormData),

        });


        if (!response.ok) throw new Error("Failed to create role");


        toast.success("User role created successfully");

      }


      handleCancel();


      setRefreshTrigger((prev) => prev + 1);

    } catch (error) {

      console.error("Error saving user role:", error);


      toast.error("Failed to save user role. Please try again.");

    }

  };


  const handleCancel = () => {

    setUserRoleFormData({

      role_name: "",


      description: "",


      permissions: {},


      role_id: "",

    });


    setShowCreateForm(false);


    setEditingRole(null);


    setPermissionAreas([]);

  };


  if (showCreateForm) {

    return (

      <div className="space-y-4">

        <div className="flex justify-between items-center">

          <div>

            <h3 className="text-lg font-medium">

              {editingRole ? "Edit User Role" : "Create New User Role"}

            </h3>


            <p className="text-sm text-gray-600 mt-1">

              {editingRole

                ? "Update the role information and permissions below."

                : "Fill in the role information and configure permissions below."}

            </p>

          </div>


          <Button variant="outline" onClick={handleCancel}>

            Back to Roles

          </Button>

        </div>


        <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">

          <div className="space-y-6">

            <Card>

              <CardContent className="p-6">

                <h4 className="font-medium mb-4">Role Information</h4>


                <div className="space-y-4">

                  <div>

                    <label className="text-sm font-medium text-black mb-2 block">

                      Role Name *

                    </label>


                    <Input

                      value={userRoleFormData.role_name}

                      onChange={(e) =>

                        setUserRoleFormData({

                          ...userRoleFormData,


                          role_name: e.target.value,

                        })

                      }

                      placeholder="Enter role name"

                      className="w-full"

                      required

                    />

                  </div>


                  <div>

                    <label className="text-sm font-medium text-black mb-2 block">

                      Description

                    </label>


                    <Textarea

                      value={userRoleFormData.description}

                      onChange={(e) =>

                        setUserRoleFormData({

                          ...userRoleFormData,


                          description: e.target.value,

                        })

                      }

                      placeholder="Enter role description"

                      className="w-full min-h-[80px]"

                      rows={3}

                    />

                  </div>

                </div>

              </CardContent>

            </Card>

          </div>


          <div className="space-y-6">

            <Card>

              <CardContent className="p-6">

                <h4 className="font-medium mb-4">Access Permissions</h4>


                <div className="space-y-3">

                  {permissionAreas.map((area) => (

                    <PermissionSection

                      key={area.key}

                      title={area.label}

                      permissions={

                        userRoleFormData.permissions

                          ? userRoleFormData.permissions[area.key] || {}

                          : {}

                      }

                      permissionKeys={area.children.map((perm) => perm.value)}

                      onPermissionChange={(permission, value) =>

                        handlePermissionChange(area.key, permission, value)

                      }

                    />

                  ))}


                  {!permissionAreas.length && (

                    <p className="text-sm text-gray-500">

                      No permission areas available. Please try again.

                    </p>

                  )}

                </div>

              </CardContent>

            </Card>


            <div className="flex justify-end gap-3">

              <Button variant="outline" onClick={handleCancel}>

                Cancel

              </Button>


              <Button

                onClick={handleSave}

                className="bg-[#000000] text-white hover:bg-gray-800"

              >

                {editingRole ? "Update Role" : "Create Role"}

              </Button>

            </div>

          </div>

        </div>

      </div>

    );

  }


  return (

    <div className="space-y-4">

      <div className="flex justify-end">

        <Button

          onClick={handleCreate}

          className="bg-[#000000] text-white hover:bg-gray-800 h-[34px] px-4 rounded-[5px]"

        >

          Add Role +

        </Button>

      </div>


      <UserRolesGrid

        onEdit={handleEdit}

        onDelete={handleDelete}

        refreshTrigger={refreshTrigger}

      />


      <Dialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>

        <DialogContent className="max-w-md">

          <DialogHeader>

            <DialogTitle>Delete Role</DialogTitle>


            <DialogDescription>

              Are you sure you want to delete the role "

              {deletingRole?.role_name}"? This action cannot be undone.

            </DialogDescription>

          </DialogHeader>


          <div className="py-4">

            <p className="text-sm text-gray-600">

              This will permanently remove the role from the system. Users

              assigned to this role may lose their permissions.

            </p>

          </div>


          <div className="flex justify-end gap-3">

            <Button

              variant="outline"

              onClick={() => setShowDeleteDialog(false)}

            >

              Cancel

            </Button>


            <Button

              onClick={confirmDeleteRole}

              className="bg-red-600 hover:bg-red-700 text-white"

            >

              Delete Role

            </Button>

          </div>

        </DialogContent>

      </Dialog>

    </div>

  );

}


Comments

Popular posts from this blog

Homesit

Login.js