Files
Krow-workspace/src/pages/Permissions.jsx

677 lines
28 KiB
JavaScript

import React, { useState } from "react";
import { base44 } from "@/api/base44Client";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { Shield, Search, Save, Info, ChevronDown, ChevronRight, Users, Calendar, Package, DollarSign, FileText, Settings as SettingsIcon, BarChart3, MessageSquare, Briefcase, Building2 } from "lucide-react";
import PageHeader from "../components/common/PageHeader";
import { useToast } from "@/components/ui/use-toast";
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card";
// Role-specific permission sets
const ROLE_PERMISSIONS = {
admin: [
{
id: "system",
name: "System Administration",
icon: Shield,
color: "text-red-600",
permissions: [
{ id: "admin.1", name: "Manage All Users", description: "Create, edit, and delete user accounts" },
{ id: "admin.2", name: "Configure System Settings", description: "Modify platform-wide settings" },
{ id: "admin.3", name: "Manage Roles & Permissions", description: "Define and assign user roles" },
{ id: "admin.4", name: "View All Activity Logs", description: "Access complete audit trail" },
{ id: "admin.5", name: "Manage Integrations", description: "Configure external integrations" },
{ id: "admin.6", name: "Access All Data", description: "Unrestricted access to all system data" },
]
},
{
id: "enterprises",
name: "Enterprise Management",
icon: Building2,
color: "text-purple-600",
permissions: [
{ id: "admin.7", name: "Create Enterprises", description: "Onboard new enterprise clients" },
{ id: "admin.8", name: "Manage Sectors", description: "Create and configure sectors" },
{ id: "admin.9", name: "Manage Partners", description: "Onboard and manage partners" },
{ id: "admin.10", name: "Set Global Policies", description: "Define enterprise-wide policies" },
]
},
{
id: "vendors",
name: "Vendor Oversight",
icon: Package,
color: "text-amber-600",
permissions: [
{ id: "admin.11", name: "Approve/Suspend Vendors", description: "Control vendor status" },
{ id: "admin.12", name: "View All Vendor Performance", description: "Access all vendor scorecards" },
{ id: "admin.13", name: "Manage Vendor Rates", description: "Override and approve rate cards" },
]
},
{
id: "financial",
name: "Financial Administration",
icon: DollarSign,
color: "text-green-600",
permissions: [
{ id: "admin.14", name: "View All Financials", description: "Access all financial data" },
{ id: "admin.15", name: "Process All Payments", description: "Approve and process payments" },
{ id: "admin.16", name: "Manage Payroll", description: "Process workforce payroll" },
{ id: "admin.17", name: "Generate Financial Reports", description: "Create P&L and financial analytics" },
]
}
],
procurement: [
{
id: "vendors",
name: "Vendor Management",
icon: Package,
color: "text-purple-600",
permissions: [
{ id: "proc.1", name: "View All Vendors", description: "Access vendor directory" },
{ id: "proc.2", name: "Onboard New Vendors", description: "Add vendors to the platform" },
{ id: "proc.3", name: "Edit Vendor Details", description: "Modify vendor information" },
{ id: "proc.4", name: "Review Vendor Compliance", description: "Check COI, W9, certifications" },
{ id: "proc.5", name: "Approve/Suspend Vendors", description: "Change vendor approval status" },
{ id: "proc.6", name: "View Vendor Performance", description: "Access scorecards and KPIs" },
]
},
{
id: "rates",
name: "Rate Card Management",
icon: DollarSign,
color: "text-green-600",
permissions: [
{ id: "proc.7", name: "View All Rate Cards", description: "See vendor pricing" },
{ id: "proc.8", name: "Create Rate Cards", description: "Set up new rate cards" },
{ id: "proc.9", name: "Edit Rate Cards", description: "Modify existing rates" },
{ id: "proc.10", name: "Approve Rate Cards", description: "Approve vendor rates" },
{ id: "proc.11", name: "Set Markup Rules", description: "Define markup percentages" },
]
},
{
id: "orders",
name: "Order Management",
icon: Calendar,
color: "text-blue-600",
permissions: [
{ id: "proc.12", name: "View All Orders", description: "Access all orders across sectors" },
{ id: "proc.13", name: "Assign Vendors to Orders", description: "Match vendors with orders" },
{ id: "proc.14", name: "Monitor Order Fulfillment", description: "Track order completion" },
]
},
{
id: "reports",
name: "Analytics & Reports",
icon: BarChart3,
color: "text-indigo-600",
permissions: [
{ id: "proc.15", name: "View Vendor Analytics", description: "Access vendor performance data" },
{ id: "proc.16", name: "Generate Procurement Reports", description: "Create spend and compliance reports" },
{ id: "proc.17", name: "Export Data", description: "Download reports as CSV/PDF" },
]
}
],
operator: [
{
id: "events",
name: "Event Management",
icon: Calendar,
color: "text-blue-600",
permissions: [
{ id: "op.1", name: "View My Enterprise Events", description: "See events in my enterprise" },
{ id: "op.2", name: "Create Events", description: "Create new event orders" },
{ id: "op.3", name: "Edit Events", description: "Modify event details" },
{ id: "op.4", name: "Cancel Events", description: "Cancel event orders" },
{ id: "op.5", name: "Approve Events", description: "Approve event requests from sectors" },
{ id: "op.6", name: "View Event Financials", description: "See event costs and billing" },
]
},
{
id: "sectors",
name: "Sector Management",
icon: Building2,
color: "text-cyan-600",
permissions: [
{ id: "op.7", name: "View My Sectors", description: "See sectors under my enterprise" },
{ id: "op.8", name: "Manage Sector Settings", description: "Configure sector policies" },
{ id: "op.9", name: "Assign Vendors to Sectors", description: "Approve vendors for sectors" },
]
},
{
id: "workforce",
name: "Workforce Management",
icon: Users,
color: "text-emerald-600",
permissions: [
{ id: "op.10", name: "View Workforce", description: "See staff across my enterprise" },
{ id: "op.11", name: "Assign Staff to Events", description: "Schedule staff for events" },
{ id: "op.12", name: "Approve Timesheets", description: "Review and approve hours" },
{ id: "op.13", name: "View Staff Performance", description: "Access ratings and reviews" },
]
},
{
id: "reports",
name: "Reports",
icon: BarChart3,
color: "text-indigo-600",
permissions: [
{ id: "op.14", name: "View Enterprise Dashboards", description: "Access my enterprise analytics" },
{ id: "op.15", name: "Export Reports", description: "Download reports" },
]
}
],
sector: [
{
id: "events",
name: "Event Management",
icon: Calendar,
color: "text-blue-600",
permissions: [
{ id: "sec.1", name: "View My Sector Events", description: "See events at my location" },
{ id: "sec.2", name: "Create Event Requests", description: "Request new events" },
{ id: "sec.3", name: "Edit My Events", description: "Modify event details" },
{ id: "sec.4", name: "View Event Costs", description: "See event billing information" },
]
},
{
id: "workforce",
name: "Staff Management",
icon: Users,
color: "text-emerald-600",
permissions: [
{ id: "sec.5", name: "View My Location Staff", description: "See staff at my sector" },
{ id: "sec.6", name: "Schedule Staff", description: "Assign staff to shifts" },
{ id: "sec.7", name: "Approve Timesheets", description: "Review hours worked" },
{ id: "sec.8", name: "Rate Staff Performance", description: "Provide performance feedback" },
]
},
{
id: "vendors",
name: "Vendor Relations",
icon: Package,
color: "text-purple-600",
permissions: [
{ id: "sec.9", name: "View Approved Vendors", description: "See vendors available to my sector" },
{ id: "sec.10", name: "View Vendor Rates", description: "Access rate cards" },
{ id: "sec.11", name: "Request Vendor Services", description: "Submit staffing requests" },
]
}
],
client: [
{
id: "orders",
name: "Order Management",
icon: Calendar,
color: "text-blue-600",
permissions: [
{ id: "client.1", name: "View My Orders", description: "See my event orders" },
{ id: "client.2", name: "Create New Orders", description: "Request staffing for events" },
{ id: "client.3", name: "Edit My Orders", description: "Modify order details before confirmation" },
{ id: "client.4", name: "Cancel Orders", description: "Cancel pending or confirmed orders" },
{ id: "client.5", name: "View Order Status", description: "Track order fulfillment" },
]
},
{
id: "vendors",
name: "Vendor Selection",
icon: Package,
color: "text-purple-600",
permissions: [
{ id: "client.6", name: "View Available Vendors", description: "See vendors I can work with" },
{ id: "client.7", name: "View Vendor Rates", description: "See pricing for services" },
{ id: "client.8", name: "Request Specific Vendors", description: "Prefer specific vendors for orders" },
]
},
{
id: "workforce",
name: "Staff Review",
icon: Users,
color: "text-emerald-600",
permissions: [
{ id: "client.9", name: "View Assigned Staff", description: "See who's working my events" },
{ id: "client.10", name: "Rate Staff Performance", description: "Provide feedback on staff" },
{ id: "client.11", name: "Request Staff Changes", description: "Request staff replacements" },
]
},
{
id: "billing",
name: "Billing & Invoices",
icon: DollarSign,
color: "text-green-600",
permissions: [
{ id: "client.12", name: "View My Invoices", description: "Access invoices for my orders" },
{ id: "client.13", name: "Download Invoices", description: "Export invoice PDFs" },
{ id: "client.14", name: "View Spend Analytics", description: "See my spending trends" },
]
}
],
vendor: [
{
id: "orders",
name: "Order Fulfillment",
icon: Calendar,
color: "text-blue-600",
permissions: [
{ id: "vendor.1", name: "View My Orders", description: "See orders assigned to me" },
{ id: "vendor.2", name: "Accept/Decline Orders", description: "Respond to order requests" },
{ id: "vendor.3", name: "Update Order Status", description: "Mark orders as in progress/completed" },
{ id: "vendor.4", name: "View Order Details", description: "Access order requirements" },
]
},
{
id: "workforce",
name: "My Workforce",
icon: Users,
color: "text-emerald-600",
permissions: [
{ id: "vendor.5", name: "View My Staff", description: "See my workforce members" },
{ id: "vendor.6", name: "Add New Staff", description: "Onboard new workers" },
{ id: "vendor.7", name: "Edit Staff Details", description: "Update staff information" },
{ id: "vendor.8", name: "Assign Staff to Orders", description: "Schedule staff for orders" },
{ id: "vendor.9", name: "Manage Staff Compliance", description: "Track certifications and background checks" },
{ id: "vendor.10", name: "View Staff Performance", description: "See ratings and feedback" },
]
},
{
id: "rates",
name: "Rate Management",
icon: DollarSign,
color: "text-green-600",
permissions: [
{ id: "vendor.11", name: "View My Rate Cards", description: "See my approved rates" },
{ id: "vendor.12", name: "Submit Rate Proposals", description: "Propose new rates" },
{ id: "vendor.13", name: "View Rate History", description: "Track rate changes" },
]
},
{
id: "performance",
name: "Performance & Analytics",
icon: BarChart3,
color: "text-indigo-600",
permissions: [
{ id: "vendor.14", name: "View My Scorecard", description: "See my performance metrics" },
{ id: "vendor.15", name: "View Fill Rate", description: "Track order fulfillment rate" },
{ id: "vendor.16", name: "View Revenue Analytics", description: "See my earnings trends" },
]
},
{
id: "billing",
name: "Invoices & Payments",
icon: FileText,
color: "text-cyan-600",
permissions: [
{ id: "vendor.17", name: "View My Invoices", description: "Access invoices I've issued" },
{ id: "vendor.18", name: "Create Invoices", description: "Generate invoices for completed work" },
{ id: "vendor.19", name: "Track Payments", description: "Monitor payment status" },
]
}
],
workforce: [
{
id: "shifts",
name: "My Shifts",
icon: Calendar,
color: "text-blue-600",
permissions: [
{ id: "work.1", name: "View My Schedule", description: "See my upcoming shifts" },
{ id: "work.2", name: "Clock In/Out", description: "Record shift start and end times" },
{ id: "work.3", name: "Request Time Off", description: "Submit time off requests" },
{ id: "work.4", name: "View Shift History", description: "See past shifts worked" },
]
},
{
id: "profile",
name: "My Profile",
icon: Users,
color: "text-emerald-600",
permissions: [
{ id: "work.5", name: "View My Profile", description: "See my worker profile" },
{ id: "work.6", name: "Edit Contact Info", description: "Update phone/email" },
{ id: "work.7", name: "Update Availability", description: "Set my available days/times" },
{ id: "work.8", name: "Upload Certifications", description: "Add certificates and licenses" },
]
},
{
id: "earnings",
name: "Earnings & Payments",
icon: DollarSign,
color: "text-green-600",
permissions: [
{ id: "work.9", name: "View My Earnings", description: "See my pay and hours" },
{ id: "work.10", name: "View Timesheets", description: "Access my timesheet records" },
{ id: "work.11", name: "View Payment History", description: "See past payments" },
{ id: "work.12", name: "Download Pay Stubs", description: "Export payment records" },
]
},
{
id: "performance",
name: "My Performance",
icon: BarChart3,
color: "text-indigo-600",
permissions: [
{ id: "work.13", name: "View My Ratings", description: "See feedback from clients" },
{ id: "work.14", name: "View Performance Stats", description: "See my reliability metrics" },
{ id: "work.15", name: "View Badges/Achievements", description: "See earned badges" },
]
}
]
};
const ROLE_TEMPLATES = {
admin: {
name: "Administrator",
description: "Full system access",
color: "bg-red-100 text-red-700",
defaultPermissions: "all"
},
procurement: {
name: "Procurement Manager",
description: "Vendor and rate management",
color: "bg-purple-100 text-purple-700",
defaultPermissions: ["proc.1", "proc.2", "proc.3", "proc.4", "proc.5", "proc.6", "proc.7", "proc.8", "proc.9", "proc.10", "proc.12", "proc.13", "proc.15", "proc.16"]
},
operator: {
name: "Operator",
description: "Enterprise management",
color: "bg-blue-100 text-blue-700",
defaultPermissions: ["op.1", "op.2", "op.3", "op.5", "op.6", "op.7", "op.8", "op.10", "op.11", "op.12", "op.14"]
},
sector: {
name: "Sector Manager",
description: "Location-specific management",
color: "bg-cyan-100 text-cyan-700",
defaultPermissions: ["sec.1", "sec.2", "sec.3", "sec.4", "sec.5", "sec.6", "sec.7", "sec.9", "sec.10"]
},
client: {
name: "Client",
description: "Order creation and viewing",
color: "bg-green-100 text-green-700",
defaultPermissions: ["client.1", "client.2", "client.3", "client.5", "client.6", "client.7", "client.9", "client.12"]
},
vendor: {
name: "Vendor Partner",
description: "Vendor-specific access",
color: "bg-amber-100 text-amber-700",
defaultPermissions: ["vendor.1", "vendor.2", "vendor.3", "vendor.5", "vendor.6", "vendor.7", "vendor.8", "vendor.11", "vendor.14", "vendor.17", "vendor.18"]
},
workforce: {
name: "Workforce Member",
description: "Basic access for staff",
color: "bg-slate-100 text-slate-700",
defaultPermissions: ["work.1", "work.2", "work.4", "work.5", "work.6", "work.9", "work.10", "work.13"]
}
};
export default function Permissions() {
const [selectedRole, setSelectedRole] = useState("operator");
const [searchTerm, setSearchTerm] = useState("");
const [expandedCategories, setExpandedCategories] = useState({});
const [permissions, setPermissions] = useState({});
const [overrides, setOverrides] = useState({});
const { toast } = useToast();
const { data: user } = useQuery({
queryKey: ['current-user-permissions'],
queryFn: () => base44.auth.me(),
});
const userRole = user?.user_role || user?.role || "admin";
// Get role-specific permissions
const roleCategories = ROLE_PERMISSIONS[selectedRole] || [];
// Initialize permissions based on role template
React.useEffect(() => {
const template = ROLE_TEMPLATES[selectedRole];
if (template) {
const newPermissions = {};
if (template.defaultPermissions === "all") {
// Admin gets all permissions
roleCategories.forEach(category => {
category.permissions.forEach(perm => {
newPermissions[perm.id] = true;
});
});
} else {
// Other roles get specific permissions
template.defaultPermissions.forEach(permId => {
newPermissions[permId] = true;
});
}
setPermissions(newPermissions);
setOverrides({});
}
}, [selectedRole]);
const toggleCategory = (categoryId) => {
setExpandedCategories(prev => ({
...prev,
[categoryId]: !prev[categoryId]
}));
};
const handlePermissionChange = (permId, value) => {
setPermissions(prev => ({
...prev,
[permId]: value
}));
setOverrides(prev => ({
...prev,
[permId]: true
}));
};
const handleInherit = (permId) => {
const template = ROLE_TEMPLATES[selectedRole];
const shouldBeEnabled = template.defaultPermissions === "all" || template.defaultPermissions.includes(permId);
setPermissions(prev => ({
...prev,
[permId]: shouldBeEnabled
}));
setOverrides(prev => {
const newOverrides = { ...prev };
delete newOverrides[permId];
return newOverrides;
});
};
const handleSave = () => {
toast({
title: "Permissions Saved",
description: `Permissions for ${ROLE_TEMPLATES[selectedRole].name} role have been updated successfully.`,
});
};
const filteredCategories = roleCategories.map(category => ({
...category,
permissions: category.permissions.filter(perm =>
perm.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
perm.description.toLowerCase().includes(searchTerm.toLowerCase())
)
})).filter(category => category.permissions.length > 0);
// Only admins can access this page
if (userRole !== "admin") {
return (
<div className="p-8 text-center">
<Shield className="w-16 h-16 mx-auto text-red-500 mb-4" />
<h2 className="text-2xl font-bold text-slate-900 mb-2">Access Denied</h2>
<p className="text-slate-600">Only administrators can manage permissions.</p>
</div>
);
}
return (
<div className="p-4 md:p-8 bg-slate-50 min-h-screen">
<div className="max-w-6xl mx-auto">
<PageHeader
title="Permissions Management"
subtitle="Configure role-based access control - each role sees only relevant permissions"
/>
{/* Role Selector */}
<Card className="mb-6 border-slate-200 shadow-sm">
<CardHeader className="bg-gradient-to-br from-slate-50 to-white border-b border-slate-100">
<CardTitle className="text-lg">Select Role to Configure</CardTitle>
<p className="text-sm text-slate-500 mt-1">Permissions shown below are contextual to the selected role</p>
</CardHeader>
<CardContent className="p-6">
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
{Object.entries(ROLE_TEMPLATES).map(([roleKey, role]) => (
<button
key={roleKey}
onClick={() => setSelectedRole(roleKey)}
className={`p-4 rounded-xl border-2 transition-all text-left ${
selectedRole === roleKey
? 'border-[#0A39DF] bg-blue-50 shadow-md scale-105'
: 'border-slate-200 bg-white hover:border-slate-300 hover:shadow-sm'
}`}
>
<Badge className={role.color + " mb-2"}>{role.name}</Badge>
<p className="text-xs text-slate-600">{role.description}</p>
</button>
))}
</div>
</CardContent>
</Card>
{/* Search */}
<Card className="mb-6 border-slate-200 shadow-sm">
<CardContent className="p-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-slate-400" />
<Input
placeholder="Search permissions..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 border-slate-300"
/>
</div>
</CardContent>
</Card>
{/* Permission Categories */}
<div className="space-y-4">
{filteredCategories.map((category) => {
const Icon = category.icon;
const isExpanded = expandedCategories[category.id] !== false; // Default to expanded
return (
<Card key={category.id} className="border-slate-200 shadow-sm">
<CardHeader
className="bg-gradient-to-br from-slate-50 to-white border-b border-slate-100 cursor-pointer hover:bg-slate-100 transition-colors"
onClick={() => toggleCategory(category.id)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
{isExpanded ? (
<ChevronDown className="w-5 h-5 text-slate-400" />
) : (
<ChevronRight className="w-5 h-5 text-slate-400" />
)}
<Icon className={`w-5 h-5 ${category.color}`} />
<CardTitle className="text-base">{category.name}</CardTitle>
<Badge variant="outline" className="text-xs">
{category.permissions.filter(p => permissions[p.id]).length}/{category.permissions.length}
</Badge>
</div>
</div>
</CardHeader>
{isExpanded && (
<CardContent className="p-0">
<div className="divide-y divide-slate-100">
{category.permissions.map((perm) => {
const isOverridden = overrides[perm.id];
const isEnabled = permissions[perm.id];
return (
<div
key={perm.id}
className="flex items-center justify-between p-4 hover:bg-slate-50 transition-colors"
>
<div className="flex items-center gap-3 flex-1">
<span className="text-sm text-slate-500 font-mono w-16">{perm.id}</span>
<div className="flex items-center gap-2 flex-1">
<span className="text-sm font-medium text-slate-900">{perm.name}</span>
<HoverCard>
<HoverCardTrigger>
<Info className="w-4 h-4 text-slate-400 cursor-help" />
</HoverCardTrigger>
<HoverCardContent className="w-80">
<p className="text-sm text-slate-700">{perm.description}</p>
</HoverCardContent>
</HoverCard>
</div>
</div>
<div className="flex items-center gap-3">
<Button
variant="ghost"
size="sm"
onClick={() => handleInherit(perm.id)}
className={`text-xs ${
!isOverridden
? "text-blue-600 hover:text-blue-700 hover:bg-blue-50 font-semibold"
: "text-slate-500 hover:text-slate-700"
}`}
>
Inherit
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handlePermissionChange(perm.id, !isEnabled)}
className={`text-xs ${
isOverridden
? "text-[#0A39DF] hover:text-[#0A39DF]/90 hover:bg-blue-50 font-semibold"
: "text-slate-500 hover:text-slate-700"
}`}
>
Override
</Button>
<Checkbox
checked={isEnabled}
onCheckedChange={(checked) => handlePermissionChange(perm.id, checked)}
className="border-slate-300 data-[state=checked]:bg-[#0A39DF] data-[state=checked]:border-[#0A39DF]"
/>
</div>
</div>
);
})}
</div>
</CardContent>
)}
</Card>
);
})}
</div>
{/* Save Button */}
<div className="flex items-center justify-end gap-4 mt-8 p-6 bg-white rounded-xl border border-slate-200 shadow-sm">
<div className="text-sm text-slate-600">
{Object.keys(overrides).length} permission{Object.keys(overrides).length !== 1 ? 's' : ''} overridden
</div>
<Button onClick={handleSave} className="bg-[#0A39DF] hover:bg-[#0A39DF]/90 shadow-md">
<Save className="w-4 h-4 mr-2" />
Save Permissions
</Button>
</div>
</div>
</div>
);
}