theme-provider #20
@@ -39,6 +39,7 @@ const AdminRoles = () => {
|
|||||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||||
const [showPermissionsModal, setShowPermissionsModal] = useState(false);
|
const [showPermissionsModal, setShowPermissionsModal] = useState(false);
|
||||||
const [expandedModules, setExpandedModules] = useState({});
|
const [expandedModules, setExpandedModules] = useState({});
|
||||||
|
const [savingPermissions, setSavingPermissions] = useState(false);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
code: '',
|
code: '',
|
||||||
name: '',
|
name: '',
|
||||||
@@ -46,6 +47,15 @@ const AdminRoles = () => {
|
|||||||
permissions: []
|
permissions: []
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const formatRoleSlug = (value) => (
|
||||||
|
value
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.replace(/[^a-z0-9]+/g, '-')
|
||||||
|
.replace(/_+/g, '-')
|
||||||
|
.replace(/^_+|_+$/g, '')
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchRoles();
|
fetchRoles();
|
||||||
fetchPermissions();
|
fetchPermissions();
|
||||||
@@ -133,6 +143,7 @@ const AdminRoles = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSavePermissions = async () => {
|
const handleSavePermissions = async () => {
|
||||||
|
setSavingPermissions(true);
|
||||||
try {
|
try {
|
||||||
await api.put(`/admin/roles/${selectedRole.id}/permissions`, {
|
await api.put(`/admin/roles/${selectedRole.id}/permissions`, {
|
||||||
permission_codes: selectedPermissions
|
permission_codes: selectedPermissions
|
||||||
@@ -142,6 +153,8 @@ const AdminRoles = () => {
|
|||||||
fetchRoles();
|
fetchRoles();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to update permissions');
|
toast.error('Failed to update permissions');
|
||||||
|
} finally {
|
||||||
|
setSavingPermissions(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -155,6 +168,14 @@ const AdminRoles = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addPermissions = (permissionCodes) => {
|
||||||
|
setSelectedPermissions(prev => [...new Set([...prev, ...permissionCodes])]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removePermissions = (permissionCodes) => {
|
||||||
|
setSelectedPermissions(prev => prev.filter(code => !permissionCodes.includes(code)));
|
||||||
|
};
|
||||||
|
|
||||||
const toggleModule = (module) => {
|
const toggleModule = (module) => {
|
||||||
setExpandedModules(prev => ({
|
setExpandedModules(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
@@ -282,8 +303,28 @@ const AdminRoles = () => {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>Role Code *</Label>
|
<Label>Role Name *</Label>
|
||||||
|
<Input
|
||||||
|
placeholder="e.g., Content Editor, Finance Manager"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => {
|
||||||
|
const nextName = e.target.value;
|
||||||
|
setFormData(prev => {
|
||||||
|
const prevAuto = formatRoleSlug(prev.name);
|
||||||
|
const isAuto = !prev.code || prev.code === prevAuto;
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
name: nextName,
|
||||||
|
code: isAuto ? formatRoleSlug(nextName) : prev.code
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Role Slug *</Label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="e.g., content_editor, finance_manager"
|
placeholder="e.g., content_editor, finance_manager"
|
||||||
value={formData.code}
|
value={formData.code}
|
||||||
@@ -294,15 +335,6 @@ const AdminRoles = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<Label>Role Name *</Label>
|
|
||||||
<Input
|
|
||||||
placeholder="e.g., Content Editor, Finance Manager"
|
|
||||||
value={formData.name}
|
|
||||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>Description</Label>
|
<Label>Description</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
@@ -382,10 +414,6 @@ const AdminRoles = () => {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
|
||||||
<Label>Role Code</Label>
|
|
||||||
<Input value={selectedRole?.code || ''} disabled />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>Role Name *</Label>
|
<Label>Role Name *</Label>
|
||||||
@@ -395,6 +423,11 @@ const AdminRoles = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>Role Slug</Label>
|
||||||
|
<Input value={selectedRole?.code || ''} disabled />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>Description</Label>
|
<Label>Description</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
@@ -426,11 +459,20 @@ const AdminRoles = () => {
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="border rounded-lg p-4">
|
<div className="border rounded-lg p-4">
|
||||||
{Object.entries(groupedPermissions).map(([module, perms]) => (
|
{Object.entries(groupedPermissions).map(([module, perms]) => {
|
||||||
|
const moduleCodes = perms.map(perm => perm.code);
|
||||||
|
const selectedCount = moduleCodes.filter(code => selectedPermissions.includes(code)).length;
|
||||||
|
const hasPermissions = moduleCodes.length > 0;
|
||||||
|
const isAllSelected = hasPermissions && selectedCount === moduleCodes.length;
|
||||||
|
const isNoneSelected = selectedCount === 0;
|
||||||
|
|
||||||
|
return (
|
||||||
<div key={module} className="mb-6">
|
<div key={module} className="mb-6">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
onClick={() => toggleModule(module)}
|
onClick={() => toggleModule(module)}
|
||||||
className="flex items-center w-full text-left font-medium text-lg mb-3 hover:text-blue-600"
|
className="flex items-center text-left font-medium text-lg hover:text-blue-600"
|
||||||
>
|
>
|
||||||
{expandedModules[module] ? (
|
{expandedModules[module] ? (
|
||||||
<ChevronUp className="w-5 h-5 mr-2" />
|
<ChevronUp className="w-5 h-5 mr-2" />
|
||||||
@@ -439,6 +481,25 @@ const AdminRoles = () => {
|
|||||||
)}
|
)}
|
||||||
{module.charAt(0).toUpperCase() + module.slice(1)} ({perms.length})
|
{module.charAt(0).toUpperCase() + module.slice(1)} ({perms.length})
|
||||||
</button>
|
</button>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => addPermissions(moduleCodes)}
|
||||||
|
disabled={!hasPermissions || isAllSelected}
|
||||||
|
className="text-xs font-medium text-gray-500 hover:text-brand-purple disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
Select all
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => removePermissions(moduleCodes)}
|
||||||
|
disabled={!hasPermissions || isNoneSelected}
|
||||||
|
className="text-xs font-medium text-gray-500 hover:text-brand-purple disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
Deselect all
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{expandedModules[module] && (
|
{expandedModules[module] && (
|
||||||
<div className="space-y-3 ml-7">
|
<div className="space-y-3 ml-7">
|
||||||
{perms.map(perm => (
|
{perms.map(perm => (
|
||||||
@@ -459,15 +520,16 @@ const AdminRoles = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => setShowPermissionsModal(false)}>
|
<Button variant="outline" onClick={() => setShowPermissionsModal(false)}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleSavePermissions}>
|
<Button onClick={handleSavePermissions} disabled={savingPermissions}>
|
||||||
Save Permissions
|
{savingPermissions ? 'Saving...' : 'Save Permissions'}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@@ -491,6 +553,15 @@ const AdminRoles = () => {
|
|||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
|
|
||||||
|
{savingPermissions && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
|
||||||
|
<div className="bg-white rounded-xl shadow-lg px-6 py-5 text-center">
|
||||||
|
<div className="mx-auto h-10 w-10 animate-spin rounded-full border-4 border-[var(--neutral-800)] border-t-transparent" />
|
||||||
|
<p className="mt-4 text-sm font-medium text-gray-700">Saving permissions...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user