Implement roles and permissions

This commit is contained in:
gpt-engineer-app[bot]
2025-10-11 16:57:48 +00:00
parent ff1312ae50
commit 2f6983aa41
4 changed files with 524 additions and 0 deletions

View File

@@ -0,0 +1,228 @@
import React, { useState } from 'react';
import { Card } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import { Checkbox } from '@/components/ui/checkbox';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Plus, Edit, Trash2, Users, Shield } from 'lucide-react';
import { EntityType, Role, PERMISSIONS } from '@/types/roles';
import { useRolesPermissions } from '@/hooks/useRolesPermissions';
interface RoleManagementProps {
entityType: EntityType;
}
const RoleManagement: React.FC<RoleManagementProps> = ({ entityType }) => {
const { roles, getRolesByEntity, createRole, updateRole, deleteRole } = useRolesPermissions();
const [isCreateOpen, setIsCreateOpen] = useState(false);
const [editingRole, setEditingRole] = useState<Role | null>(null);
const [formData, setFormData] = useState({
name: '',
description: '',
permissions: [] as string[],
});
const entityRoles = getRolesByEntity(entityType);
const availablePermissions = PERMISSIONS[entityType] || [];
const getEntityTitle = () => {
const titles: Record<EntityType, string> = {
admin: 'Admin System',
hotel: 'Hotel Management',
restaurant: 'Restaurant Management',
commerce: 'Commerce Management',
};
return titles[entityType];
};
const handleSubmit = () => {
if (editingRole) {
updateRole(editingRole.id, formData);
setEditingRole(null);
} else {
createRole({
...formData,
entityType,
});
}
setIsCreateOpen(false);
setFormData({ name: '', description: '', permissions: [] });
};
const handleEdit = (role: Role) => {
setEditingRole(role);
setFormData({
name: role.name,
description: role.description,
permissions: role.permissions,
});
setIsCreateOpen(true);
};
const handleDelete = (roleId: string) => {
if (confirm('Are you sure you want to delete this role?')) {
deleteRole(roleId);
}
};
const togglePermission = (permissionId: string) => {
setFormData(prev => ({
...prev,
permissions: prev.permissions.includes(permissionId)
? prev.permissions.filter(p => p !== permissionId)
: [...prev.permissions, permissionId],
}));
};
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Shield className="w-6 h-6 text-primary" />
<div>
<h2 className="text-2xl font-bold">{getEntityTitle()}</h2>
<p className="text-muted-foreground">Manage roles and permissions</p>
</div>
</div>
<Dialog open={isCreateOpen} onOpenChange={setIsCreateOpen}>
<DialogTrigger asChild>
<Button onClick={() => {
setEditingRole(null);
setFormData({ name: '', description: '', permissions: [] });
}}>
<Plus className="w-4 h-4 mr-2" />
New Role
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{editingRole ? 'Edit Role' : 'Create New Role'}</DialogTitle>
<DialogDescription>
Define role name, description, and permissions
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div>
<Label>Role Name</Label>
<Input
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="e.g., Manager, Operator"
/>
</div>
<div>
<Label>Description</Label>
<Textarea
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
placeholder="Brief description of this role"
/>
</div>
<div>
<Label className="mb-3 block">Permissions</Label>
<div className="space-y-4">
{Object.entries(
availablePermissions.reduce((acc, perm) => {
if (!acc[perm.module]) acc[perm.module] = [];
acc[perm.module].push(perm);
return acc;
}, {} as Record<string, typeof availablePermissions>)
).map(([module, perms]) => (
<div key={module} className="border rounded-lg p-4">
<h4 className="font-semibold mb-3">{module}</h4>
<div className="space-y-2">
{perms.map((perm) => (
<div key={perm.id} className="flex items-center space-x-2">
<Checkbox
id={perm.id}
checked={formData.permissions.includes(perm.id)}
onCheckedChange={() => togglePermission(perm.id)}
/>
<Label htmlFor={perm.id} className="flex-1 cursor-pointer">
<div className="font-medium">{perm.name}</div>
<div className="text-sm text-muted-foreground">{perm.description}</div>
</Label>
</div>
))}
</div>
</div>
))}
</div>
</div>
<div className="flex justify-end gap-2 pt-4">
<Button variant="outline" onClick={() => setIsCreateOpen(false)}>
Cancel
</Button>
<Button onClick={handleSubmit} disabled={!formData.name || formData.permissions.length === 0}>
{editingRole ? 'Update' : 'Create'} Role
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</div>
<div className="grid gap-4">
{entityRoles.map((role) => (
<Card key={role.id} className="p-6">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<h3 className="text-lg font-semibold">{role.name}</h3>
{role.isSystem && (
<Badge variant="secondary">System Role</Badge>
)}
<div className="flex items-center gap-1 text-muted-foreground">
<Users className="w-4 h-4" />
<span className="text-sm">{role.userCount} users</span>
</div>
</div>
<p className="text-muted-foreground mb-4">{role.description}</p>
<div className="flex flex-wrap gap-2">
{role.permissions.map((permId) => {
const perm = availablePermissions.find(p => p.id === permId);
return perm ? (
<Badge key={permId} variant="outline">
{perm.name}
</Badge>
) : null;
})}
</div>
</div>
<div className="flex gap-2">
<Button
variant="ghost"
size="icon"
onClick={() => handleEdit(role)}
>
<Edit className="w-4 h-4" />
</Button>
{!role.isSystem && (
<Button
variant="ghost"
size="icon"
onClick={() => handleDelete(role.id)}
>
<Trash2 className="w-4 h-4" />
</Button>
)}
</div>
</div>
</Card>
))}
</div>
</div>
);
};
export default RoleManagement;

View File

@@ -0,0 +1,172 @@
import { useState, useEffect } from 'react';
import { Role, EntityType, PERMISSIONS, UserRole } from '@/types/roles';
import { useToast } from '@/hooks/use-toast';
const MOCK_ROLES: Role[] = [
{
id: 'admin-super',
name: 'Super Admin',
entityType: 'admin',
description: 'Full system access',
permissions: PERMISSIONS.admin.map(p => p.id),
userCount: 2,
isSystem: true,
},
{
id: 'admin-moderator',
name: 'Moderator',
entityType: 'admin',
description: 'Content and user management',
permissions: ['admin.users.read', 'admin.content.write'],
userCount: 5,
},
{
id: 'hotel-manager',
name: 'Hotel Manager',
entityType: 'hotel',
description: 'Full hotel management access',
permissions: PERMISSIONS.hotel.map(p => p.id),
userCount: 8,
isSystem: true,
},
{
id: 'hotel-receptionist',
name: 'Receptionist',
entityType: 'hotel',
description: 'Handle bookings and guests',
permissions: ['hotel.bookings.read', 'hotel.bookings.write', 'hotel.guests.read'],
userCount: 15,
},
{
id: 'restaurant-manager',
name: 'Restaurant Manager',
entityType: 'restaurant',
description: 'Full restaurant management',
permissions: PERMISSIONS.restaurant.map(p => p.id),
userCount: 6,
isSystem: true,
},
{
id: 'restaurant-waiter',
name: 'Waiter',
entityType: 'restaurant',
description: 'Take and manage orders',
permissions: ['restaurant.orders.read', 'restaurant.orders.write', 'restaurant.reservations.write'],
userCount: 20,
},
{
id: 'commerce-admin',
name: 'Commerce Admin',
entityType: 'commerce',
description: 'Full commerce access',
permissions: PERMISSIONS.commerce.map(p => p.id),
userCount: 4,
isSystem: true,
},
{
id: 'commerce-operator',
name: 'Store Operator',
entityType: 'commerce',
description: 'Manage products and orders',
permissions: ['commerce.products.read', 'commerce.orders.read', 'commerce.orders.write'],
userCount: 12,
},
];
export const useRolesPermissions = () => {
const [roles, setRoles] = useState<Role[]>(MOCK_ROLES);
const [userRoles, setUserRoles] = useState<UserRole[]>([]);
const [loading, setLoading] = useState(false);
const { toast } = useToast();
const getRolesByEntity = (entityType: EntityType): Role[] => {
return roles.filter(role => role.entityType === entityType);
};
const createRole = (role: Omit<Role, 'id' | 'userCount'>): void => {
const newRole: Role = {
...role,
id: `${role.entityType}-${Date.now()}`,
userCount: 0,
};
setRoles([...roles, newRole]);
toast({
title: 'Role Created',
description: `${role.name} has been created successfully.`,
});
};
const updateRole = (roleId: string, updates: Partial<Role>): void => {
setRoles(roles.map(role =>
role.id === roleId ? { ...role, ...updates } : role
));
toast({
title: 'Role Updated',
description: 'Role has been updated successfully.',
});
};
const deleteRole = (roleId: string): void => {
const role = roles.find(r => r.id === roleId);
if (role?.isSystem) {
toast({
title: 'Cannot Delete',
description: 'System roles cannot be deleted.',
variant: 'destructive',
});
return;
}
setRoles(roles.filter(role => role.id !== roleId));
toast({
title: 'Role Deleted',
description: 'Role has been deleted successfully.',
});
};
const assignUserRole = (userId: string, roleId: string, entityId?: string): void => {
const newUserRole: UserRole = {
userId,
roleId,
entityId,
assignedAt: new Date().toISOString(),
assignedBy: 'current-user',
};
setUserRoles([...userRoles, newUserRole]);
const role = roles.find(r => r.id === roleId);
if (role) {
updateRole(roleId, { userCount: role.userCount + 1 });
}
toast({
title: 'Role Assigned',
description: 'User role has been assigned successfully.',
});
};
const removeUserRole = (userId: string, roleId: string): void => {
setUserRoles(userRoles.filter(ur => !(ur.userId === userId && ur.roleId === roleId)));
const role = roles.find(r => r.id === roleId);
if (role && role.userCount > 0) {
updateRole(roleId, { userCount: role.userCount - 1 });
}
toast({
title: 'Role Removed',
description: 'User role has been removed successfully.',
});
};
return {
roles,
userRoles,
loading,
getRolesByEntity,
createRole,
updateRole,
deleteRole,
assignUserRole,
removeUserRole,
};
};

View File

@@ -0,0 +1,61 @@
import React, { useState } from 'react';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import RoleManagement from '@/components/roles/RoleManagement';
import { Shield, Building2, Utensils, Store } from 'lucide-react';
const RolesPermissions = () => {
const [activeTab, setActiveTab] = useState('admin');
return (
<div className="min-h-screen bg-background p-6">
<div className="max-w-7xl mx-auto">
<div className="flex items-center gap-3 mb-6">
<Shield className="w-8 h-8 text-primary" />
<div>
<h1 className="text-3xl font-bold">Roles & Permissions</h1>
<p className="text-muted-foreground">Manage access control for all entity types</p>
</div>
</div>
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
<TabsList className="grid w-full grid-cols-4 lg:w-auto">
<TabsTrigger value="admin" className="flex items-center gap-2">
<Shield className="w-4 h-4" />
Admin
</TabsTrigger>
<TabsTrigger value="hotel" className="flex items-center gap-2">
<Building2 className="w-4 h-4" />
Hotel
</TabsTrigger>
<TabsTrigger value="restaurant" className="flex items-center gap-2">
<Utensils className="w-4 h-4" />
Restaurant
</TabsTrigger>
<TabsTrigger value="commerce" className="flex items-center gap-2">
<Store className="w-4 h-4" />
Commerce
</TabsTrigger>
</TabsList>
<TabsContent value="admin">
<RoleManagement entityType="admin" />
</TabsContent>
<TabsContent value="hotel">
<RoleManagement entityType="hotel" />
</TabsContent>
<TabsContent value="restaurant">
<RoleManagement entityType="restaurant" />
</TabsContent>
<TabsContent value="commerce">
<RoleManagement entityType="commerce" />
</TabsContent>
</Tabs>
</div>
</div>
);
};
export default RolesPermissions;

63
src/types/roles.ts Normal file
View File

@@ -0,0 +1,63 @@
export type EntityType = 'admin' | 'hotel' | 'restaurant' | 'commerce';
export type Permission = {
id: string;
name: string;
description: string;
module: string;
};
export type Role = {
id: string;
name: string;
entityType: EntityType;
description: string;
permissions: string[];
userCount: number;
isSystem?: boolean;
};
export type UserRole = {
userId: string;
roleId: string;
entityId?: string;
assignedAt: string;
assignedBy: string;
};
export const PERMISSIONS: Record<string, Permission[]> = {
admin: [
{ id: 'admin.users.read', name: 'View Users', description: 'View user information', module: 'User Management' },
{ id: 'admin.users.write', name: 'Manage Users', description: 'Create and edit users', module: 'User Management' },
{ id: 'admin.users.delete', name: 'Delete Users', description: 'Delete user accounts', module: 'User Management' },
{ id: 'admin.roles.read', name: 'View Roles', description: 'View roles and permissions', module: 'Role Management' },
{ id: 'admin.roles.write', name: 'Manage Roles', description: 'Create and edit roles', module: 'Role Management' },
{ id: 'admin.content.write', name: 'Manage Content', description: 'Create and edit content', module: 'Content Management' },
{ id: 'admin.finance.read', name: 'View Financial Data', description: 'View financial reports', module: 'Financial' },
{ id: 'admin.settings.write', name: 'Manage Settings', description: 'Configure system settings', module: 'Settings' },
],
hotel: [
{ id: 'hotel.bookings.read', name: 'View Bookings', description: 'View hotel bookings', module: 'Bookings' },
{ id: 'hotel.bookings.write', name: 'Manage Bookings', description: 'Create and modify bookings', module: 'Bookings' },
{ id: 'hotel.rooms.write', name: 'Manage Rooms', description: 'Manage room inventory', module: 'Rooms' },
{ id: 'hotel.guests.read', name: 'View Guests', description: 'View guest information', module: 'Guests' },
{ id: 'hotel.staff.write', name: 'Manage Staff', description: 'Manage hotel staff', module: 'Staff' },
{ id: 'hotel.reports.read', name: 'View Reports', description: 'View hotel reports', module: 'Reports' },
],
restaurant: [
{ id: 'restaurant.orders.read', name: 'View Orders', description: 'View restaurant orders', module: 'Orders' },
{ id: 'restaurant.orders.write', name: 'Manage Orders', description: 'Create and modify orders', module: 'Orders' },
{ id: 'restaurant.menu.write', name: 'Manage Menu', description: 'Update menu items', module: 'Menu' },
{ id: 'restaurant.reservations.write', name: 'Manage Reservations', description: 'Handle table reservations', module: 'Reservations' },
{ id: 'restaurant.staff.write', name: 'Manage Staff', description: 'Manage restaurant staff', module: 'Staff' },
{ id: 'restaurant.inventory.read', name: 'View Inventory', description: 'View inventory levels', module: 'Inventory' },
],
commerce: [
{ id: 'commerce.products.read', name: 'View Products', description: 'View product catalog', module: 'Products' },
{ id: 'commerce.products.write', name: 'Manage Products', description: 'Create and edit products', module: 'Products' },
{ id: 'commerce.orders.read', name: 'View Orders', description: 'View customer orders', module: 'Orders' },
{ id: 'commerce.orders.write', name: 'Manage Orders', description: 'Process and manage orders', module: 'Orders' },
{ id: 'commerce.inventory.write', name: 'Manage Inventory', description: 'Update inventory levels', module: 'Inventory' },
{ id: 'commerce.promotions.write', name: 'Manage Promotions', description: 'Create promotions and discounts', module: 'Marketing' },
],
};