import { useState, useRef, useEffect } 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";


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

  );

}


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) => `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">

        <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) => {

    if (actionType === "edit") onEdit(row);

    else if (actionType === "delete") onDelete(row);

  };


  const fetchData = async () => {

    try {

      setLoading(true);

      const payload = {

        filterModel: [{ search_text: "", type: "contains", column_name: "" }],

        sortModel: { sort: "", column_name: "" },

        startRow: 0,

        endRow: 100,

        page: 1,

        records: 50,

      };


      const response = await apiService.fetchUserRoles(payload);

      const roles: UserRole[] = response?.data || [];


      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="ag-theme-alpine hidden md:block" style={{ height: 400, width: "100%" }}>

        {loading ? (

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

        ) : userRoleRowData.length > 0 ? (

          <AGGrid

            ref={gridRef}

            columnDefs={columnDefs}

            rowData={userRoleRowData}

            height={400}

            enableSorting={false}

            enableFiltering={false}

            enableResizing

            animateRows

            headerHeight={40}

            rowHeight={35}

          />

        ) : (

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

            No user roles found.

          </div>

        )}

      </div>


      <div className="flex justify-between p-4 bg-white border border-[#b8bcbf] text-sm font-['Roboto',sans-serif]">

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

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

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

        </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 loadPermissions = async () => {

    const response = await apiService.fetchPermissions();

    const data: PermissionArea[] = response?.data || [];

    setPermissionAreas(data);

    return data;

  };


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

    try {

      const areas = await loadPermissions();


      const initializedPermissions = areas.reduce(

        (acc: any, area) => ({

          ...acc,

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

            (permAcc, perm) => ({

              ...permAcc,

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

            }),

            {}

          ),

        }),

        {}

      );


      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 load role details:", error);

      toast.error("Failed to load permissions.");

    }

  };


  const handleCreate = async () => {

    try {

      const areas = await loadPermissions();


      const initializedPermissions = areas.reduce(

        (acc: any, area) => ({

          ...acc,

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

            (permAcc: any, perm) => ({

              ...permAcc,

              [perm.value]: false,

            }),

            {}

          ),

        }),

        {}

      );


      setUserRoleFormData({

        role_name: "",

        description: "",

        permissions: initializedPermissions,

        role_id: "",

      });


      setShowCreateForm(true);

    } catch (error) {

      toast.error("Failed to load permissions.");

    }

  };


  const handleSave = async () => {

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

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

      return;

    }


    try {

      if (editingRole) {

        await apiService.updateUserRole(editingRole.id, userRoleFormData);

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

      } else {

        await apiService.createUserRole(userRoleFormData);

        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.");

    }

  };


  const handleDelete = (role: UserRole) => {

    setDeletingRole(role);

    setShowDeleteDialog(true);

  };


  const confirmDeleteRole = async () => {

    if (!deletingRole) return;

    try {

      await apiService.deleteUserRole(deletingRole.id);

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

      setShowDeleteDialog(false);

      setDeletingRole(null);

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

    } catch {

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

    }

  };


  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">

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

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

          </h3>

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

            Back to Roles

          </Button>

        </div>


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

          <Card>

            <CardContent className="p-6 space-y-4">

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

              <Input

                value={userRoleFormData.role_name}

                onChange={(e) =>

                  setUserRoleFormData({ ...userRoleFormData, role_name: e.target.value })

                }

                placeholder="Enter role name"

              />

              <Textarea

                value={userRoleFormData.description}

                onChange={(e) =>

                  setUserRoleFormData({ ...userRoleFormData, description: e.target.value })

                }

                placeholder="Enter description"

                rows={3}

              />

            </CardContent>

          </Card>


          <Card>

            <CardContent className="p-6">

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

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

                {permissionAreas.length > 0 ? (

                  permissionAreas.map((area) => (

                    <PermissionSection

                      key={area.key}

                      title={area.label}

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

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

                      onPermissionChange={(permission, value) =>

                        setUserRoleFormData((prev) => ({

                          ...prev,

                          permissions: {

                            ...prev.permissions,

                            [area.key]: {

                              ...prev.permissions?.[area.key],

                              [permission]: value,

                            },

                          },

                        }))

                      }

                    />

                  ))

                ) : (

                  <p className="text-sm text-gray-500">No permissions available.</p>

                )}

              </div>

            </CardContent>

          </Card>


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

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

              Cancel

            </Button>

            <Button

              onClick={handleSave}

              className="bg-black text-white hover:bg-gray-800"

            >

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

            </Button>

          </div>

        </div>

      </div>

    );

  }


  return (

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

      <div className="flex justify-end">

        <Button

          onClick={handleCreate}

          className="bg-black 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 “{deletingRole?.role_name}”? This action cannot

              be undone.

            </DialogDescription>

          </DialogHeader>

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

            <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