error

import { useState, useRef, useEffect, useImperativeHandle, forwardRef } from "react";
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, RefreshCcw } from "lucide-react";
import { apiService } from "../../../Services/apiService";

const ROLES_STORAGE_KEY = 'user_management_roles_legacy';
const PERMISSIONS_STORAGE_KEY = 'user_management_permissions_legacy';

const buildDefaultPermissionAreas = () => [
  {
    label: "User Management",
    key: "userManagement",
    children: ["view","edit","create","delete"].map(a => ({ label: a.charAt(0).toUpperCase()+a.slice(1), value: a, field: `user_management_${a}` }))
  },
  {
    label: "Settings",
    key: "settings",
    children: ["view","edit","create","delete"].map(a => ({ label: a.charAt(0).toUpperCase()+a.slice(1), value: a, field: `settings_${a}` }))
  },
];

function normalizePermissionSchema(raw) {
  if (!raw) return buildDefaultPermissionAreas();
 
  // API returns: { status: "success", data: { permissions: [...] } }
  if (Array.isArray(raw)) {
    return raw.map((area) => {
      const key = area.key || area.area_key || area.module_key || (area.label ? area.label.toLowerCase().replace(/\s+/g,'_') : 'unknown');
      const childrenRaw = area.children || area.actions || area.permissions || [];
      const children = Array.isArray(childrenRaw)
        ? childrenRaw.map((c) => ({
            label: c.label || c.name || c.value || c.action || c.permission || '',
            value: c.value || c.action || c.permission || c.label?.toLowerCase() || c.name?.toLowerCase() || 'unknown',
            field: c.field || `${key}_${c.value || c.action || c.permission || c.label || c.name}`
          }))
        : Object.keys(childrenRaw).map(k => ({
            label: k.charAt(0).toUpperCase()+k.slice(1),
            value: k,
            field: `${key}_${k}`
          }));
      return { label: area.label || key, key, children };
    });
  }
 
  if (typeof raw === 'object') {
    return Object.keys(raw).map(areaKey => {
      const val = raw[areaKey];
      let children = [];
      if (Array.isArray(val)) {
        children = val.map(v => ({
          label: v.charAt(0).toUpperCase()+v.slice(1),
          value: v,
          field: `${areaKey}_${v}`
        }));
      } else if (typeof val === 'object') {
        children = Object.keys(val).map(v => ({
          label: v.charAt(0).toUpperCase()+v.slice(1),
          value: v,
          field: `${areaKey}_${v}`
        }));
      }
      return { label: areaKey.replace(/_/g,' ').replace(/\b\w/g,c=>c.toUpperCase()), key: areaKey, children };
    });
  }
  return buildDefaultPermissionAreas();
}

function PermissionToggle({
  label,
  description,
  checked,
  onCheckedChange,
}) {
  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,
}) {
  const [isOpen, setIsOpen] = useState(true);

  const permissionDescriptions = (key) => {
    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>
  );
}

const UserRoles = forwardRef(({ currentUser }, ref) => {
  const [showCreateForm, setShowCreateForm] = useState(false);
  const [editingRole, setEditingRole] = useState(null);
  const [showDeleteDialog, setShowDeleteDialog] = useState(false);
  const [deletingRole, setDeletingRole] = useState(null);
  const [permissionAreas, setPermissionAreas] = useState([]);
  const [userRoleFormData, setUserRoleFormData] = useState({
    role_id: "",
    description: "",
    active: true,
    role_name: "",
    permissions: {}
  });
  const [refreshTrigger, setRefreshTrigger] = useState(0);
  const [userRoleRowData, setUserRoleRowData] = useState([]);
  const [loading, setLoading] = useState(true);
  const fetchingRef = useRef(false);
  const lastFetchedUserIdRef = useRef(null);

  const fetchRolesFromAPI = async (force = false) => {
    if (!currentUser?.user_id) {
      console.debug('[UserRoles] No currentUser.user_id yet; skipping fetch');
      if (loading) setLoading(false);
      return;
    }
    if (fetchingRef.current) return;
    if (!force && lastFetchedUserIdRef.current === currentUser.user_id && userRoleRowData.length > 0) return;

    try {
      fetchingRef.current = true;
      setLoading(true);
      console.debug('[UserRoles] Fetching roles for user_id:', currentUser.user_id, 'force=', force);
      const payload = { user_id: currentUser.user_id };
      const res = await apiService.fetchUserRoles(payload);
      if (res.status === 'success' && res.data) {
        let roles = [];
        if (res.data.roles) roles = res.data.roles;
        else if (res.data.table_data?.body_content) roles = res.data.table_data.body_content;
        else if (Array.isArray(res.data)) roles = res.data;

        setUserRoleRowData(roles);
        lastFetchedUserIdRef.current = currentUser.user_id;
        console.debug('[UserRoles] Roles fetched count:', roles.length);
      } else {
        toast.error(res.message || 'Failed to fetch roles');
        console.warn('[UserRoles] Fetch roles failed:', res.message);
      }
    } catch (e) {
      console.error('Failed to fetch roles', e);
      toast.error('Error fetching roles');
    } finally {
      fetchingRef.current = false;
      setLoading(false);
    }
  };

  useImperativeHandle(ref, () => ({
    openCreateDialog: () => {
      handleCreate();
    }
  }));

  useEffect(() => {
    fetchRolesFromAPI();
  }, [refreshTrigger, currentUser?.user_id]);

  const loadPermissionAreas = async (targetRole) => {
    if (!currentUser?.user_id) {
      setPermissionAreas(buildDefaultPermissionAreas());
      return;
    }
    try {
      const payload = { user_id: currentUser.user_id };
      if (targetRole) payload.role_id = targetRole.role_id;
     
      const res = await apiService.fetchRoleConfig(payload);
     
      // FIXED: Properly access data.permissions from API response
      // API structure: { status: "success", data: { permissions: [...] } }
      let rawSchema = res?.data?.permissions;
     
      console.log('[loadPermissionAreas] API Response:', res);
      console.log('[loadPermissionAreas] Raw schema extracted:', rawSchema);
     
      if (!rawSchema) {
        // Fallback chain for different API response structures
        rawSchema = res?.data?.permission_areas || res?.data?.modules || res?.data;
       
        if (!rawSchema && res?.data?.table_data?.permission_areas) {
          rawSchema = res.data.table_data.permission_areas;
        }
      }
     
      const areas = normalizePermissionSchema(rawSchema);
      console.log('[loadPermissionAreas] Normalized areas:', areas);
     
      setPermissionAreas(areas);
      return areas;
    } catch (e) {
      console.warn('Permission schema fetch failed, using fallback:', e);
      const fallback = buildDefaultPermissionAreas();
      setPermissionAreas(fallback);
      return fallback;
    }
  };

  const handleEdit = async (role) => {
    try {
      const areas = await loadPermissionAreas(role) || buildDefaultPermissionAreas();
      const initializedPermissions = areas.reduce(
        (acc, 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 edit user role:", error);
      toast.error("Failed to load role details. Please try again.");
    }
  };

  const handleCreate = async () => {
    try {
      const areas = await loadPermissionAreas() || buildDefaultPermissionAreas();
      const initializedPermissions = areas.reduce(
        (acc, area) => ({
          ...acc,
          [area.key]: area.children.reduce(
            (permAcc, perm) => ({
              ...permAcc,
              [perm.value]: false,
            }),
            {}
          ),
        }),
        {}
      );
      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, permission, value) => {
    setUserRoleFormData((prev) => ({
      ...prev,
      permissions: {
        ...(prev.permissions ?? {}),
        [section]: {
          ...(prev.permissions?.[section] ?? {}),
          [permission]: value,
        },
      },
    }));
  };

  const handleDelete = (role) => {
    setDeletingRole(role);
    setShowDeleteDialog(true);
  };

  const confirmDeleteRole = async () => {
    if (!deletingRole || !currentUser?.user_id) return;
    try {
      const payload = { user_id: currentUser.user_id, delete_role_id: deletingRole.role_id };
      const res = await apiService.deleteRole(payload);
      if (res.status === 'success') {
        toast.success(`Role "${deletingRole.role_name}" deleted.`);
        setShowDeleteDialog(false);
        setDeletingRole(null);
        fetchRolesFromAPI(true);
      } else {
        toast.error(res.message || 'Failed to delete role');
      }
    } catch (e) {
      console.error('Delete role failed', e);
      toast.error('Error deleting role');
    }
  };

  const handleSave = async () => {
    if (!userRoleFormData.role_name.trim()) {
      toast.error("Role name is required");
      return;
    }
    if (!currentUser?.user_id) {
      toast.error('User context missing');
      return;
    }

    try {
      const payloadBase = {
        user_id: currentUser.user_id,
        role_name: userRoleFormData.role_name,
        role_description: userRoleFormData.description,
        permissions: userRoleFormData.permissions
      };

      if (editingRole) {
        const editPayload = { ...payloadBase, role_id: editingRole.role_id };
        const res = await apiService.editSaveRole(editPayload);
        if (res.status === 'success') {
          toast.success('Role updated successfully');
          handleCancel();
          fetchRolesFromAPI(true);
        } else {
          toast.error(res.message || 'Failed to update role');
        }
      } else {
        const createPayload = { ...payloadBase };
        const res = await apiService.createRole(createPayload);
        if (res.status === 'success') {
          toast.success('Role created successfully');
          handleCancel();
          fetchRolesFromAPI(true);
        } else {
          toast.error(res.message || 'Failed to create role');
        }
      }
    } catch (e) {
      console.error('Save role failed', e);
      toast.error('Error saving 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">
          <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-[#00052e] text-white hover:bg-[#00052e]/90"
              >
                {editingRole ? "Update Role" : "Create Role"}
              </Button>
            </div>
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="space-y-4">
      <div className="flex items-center justify-between">
        <h3 className="text-lg font-medium">User Roles</h3>
        <div className="flex gap-2">
          <Button variant="outline" onClick={() => fetchRolesFromAPI(true)} disabled={loading} className="flex items-center gap-2">
            <RefreshCcw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
            <span className="hidden sm:inline">Refresh</span>
          </Button>
          <Button onClick={handleCreate} className="bg-[#00052e] text-white hover:bg-[#00052e]/90">Create Role</Button>
        </div>
      </div>
      <div className="border border-gray-200 rounded-lg overflow-hidden">
        <div className="overflow-x-auto">
          <table className="min-w-full divide-y divide-gray-200">
            <thead className="bg-gray-50">
              <tr>
                <th className="px-3 sm:px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role Name</th>
                <th className="px-3 sm:px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Description</th>
                <th className="px-3 sm:px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
              </tr>
            </thead>
            <tbody className="bg-white divide-y divide-gray-200">
              {loading ? (
                <tr>
                  <td colSpan={3} className="px-3 sm:px-6 py-8 text-center">
                    <div className="flex justify-center items-center">
                      <div className="text-gray-500">Loading...</div>
                    </div>
                  </td>
                </tr>
              ) : userRoleRowData.length === 0 ? (
                <tr>
                  <td colSpan={3} className="px-3 sm:px-6 py-8 text-center text-gray-500 text-sm">
                    No user roles found.
                  </td>
                </tr>
              ) : (
                userRoleRowData.map((role) => (
                  <tr key={role.id} className="hover:bg-gray-50">
                    <td className="px-3 sm:px-6 py-4 whitespace-nowrap text-xs sm:text-sm text-gray-900">
                      {role.role_name}
                    </td>
                    <td className="px-3 sm:px-6 py-4 text-xs sm:text-sm text-gray-900 max-w-48 sm:max-w-none truncate">
                      {role.description}
                    </td>
                    <td className="px-3 sm:px-6 py-4 whitespace-nowrap text-xs sm:text-sm font-medium">
                      <div className="flex gap-1 sm:gap-2">
                        <button
                          onClick={() => handleEdit(role)}
                          className="p-1 hover:bg-gray-100 rounded"
                          style={{ color: '#00052e' }}
                          title="Edit Role"
                        >
                          <svg className="w-3 h-3 sm:w-4 sm:h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
                          </svg>
                        </button>
                        <button
                          onClick={() => handleDelete(role)}
                          className="p-1 hover:bg-gray-100 rounded"
                          style={{ color: '#00052e' }}
                          title="Delete Role"
                        >
                          <svg className="w-3 h-3 sm:w-4 sm:h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
                          </svg>
                        </button>
                      </div>
                    </td>
                  </tr>
                ))
              )}
            </tbody>
          </table>
        </div>
      </div>

      <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between p-3 sm:p-4 bg-gray-50 border border-gray-200 rounded-lg">
        <div className="flex items-center space-x-6 text-xs sm:text-sm">
          <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>
      <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-[#00052e] hover:bg-[#00052e]/90 text-white"
            >
              Delete Role
            </Button>
          </div>
        </DialogContent>
      </Dialog>
    </div>
  );
});

UserRoles.displayName = 'UserRoles';

export default UserRoles;


Comments

Popular posts from this blog

Homesit

Login.js