Improve staff management UI and add sorting functionality

- Remove WIP badge from staff sidebar navigation
- Make action buttons consistent between Customers and Staff pages
  - Edit button: icon + text with gray border
  - Masquerade button: icon + text with indigo border
  - Verify email button: icon-only with colored border (green/amber)
- Add sortable columns to Staff list (name and role)
- Include migrations for tenant manager role removal

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-17 19:29:13 -05:00
parent a80b35a806
commit 92019aac7e
68 changed files with 1827 additions and 788 deletions

View File

@@ -456,7 +456,7 @@ const AppContent: React.FC = () => {
const isBusinessSubdomain = !isRootDomain() && !isPlatformDomain && currentSubdomain !== 'api' && currentHostname !== baseDomain;
const isPlatformUser = ['superuser', 'platform_manager', 'platform_support'].includes(user.role);
const isBusinessUser = ['owner', 'manager', 'staff', 'resource'].includes(user.role);
const isBusinessUser = ['owner', 'staff', 'resource'].includes(user.role);
const isCustomer = user.role === 'customer';
// RULE: Platform users on business subdomains should be redirected to platform subdomain
@@ -510,6 +510,15 @@ const AppContent: React.FC = () => {
// Helper to check access based on roles
const hasAccess = (allowedRoles: string[]) => allowedRoles.includes(user.role);
// Helper to check permission-based access (owner always has access, staff uses effective_permissions)
const canAccess = (permissionKey: string): boolean => {
if (user.role === 'owner') return true;
if (user.role === 'staff') {
return user.effective_permissions?.[permissionKey] === true;
}
return false;
};
if (isPlatformUser) {
return (
<Suspense fallback={<LoadingScreen />}>
@@ -658,8 +667,8 @@ const AppContent: React.FC = () => {
);
}
// Business users (owner, manager, staff, resource)
if (['owner', 'manager', 'staff', 'resource'].includes(user.role)) {
// Business users (owner, staff, resource)
if (['owner', 'staff', 'resource'].includes(user.role)) {
// Check if email verification is required
if (!user.email_verified) {
return (
@@ -799,7 +808,7 @@ const AppContent: React.FC = () => {
<Route
path="/dashboard/automations/marketplace"
element={
hasAccess(['owner', 'manager']) ? (
canAccess('can_access_automations') ? (
<AutomationMarketplace />
) : (
<Navigate to="/dashboard" />
@@ -809,7 +818,7 @@ const AppContent: React.FC = () => {
<Route
path="/dashboard/automations/my-automations"
element={
hasAccess(['owner', 'manager']) ? (
canAccess('can_access_automations') ? (
<MyAutomations />
) : (
<Navigate to="/dashboard" />
@@ -819,7 +828,7 @@ const AppContent: React.FC = () => {
<Route
path="/dashboard/automations/create"
element={
hasAccess(['owner', 'manager']) ? (
canAccess('can_access_automations') ? (
<CreateAutomation />
) : (
<Navigate to="/dashboard" />
@@ -829,7 +838,7 @@ const AppContent: React.FC = () => {
<Route
path="/dashboard/tasks"
element={
hasAccess(['owner', 'manager']) ? (
canAccess('can_access_tasks') ? (
<Tasks />
) : (
<Navigate to="/dashboard" />
@@ -841,7 +850,7 @@ const AppContent: React.FC = () => {
<Route
path="/dashboard/customers"
element={
hasAccess(['owner', 'manager']) ? (
canAccess('can_access_customers') ? (
<Customers onMasquerade={handleMasquerade} effectiveUser={user} />
) : (
<Navigate to="/dashboard" />
@@ -851,7 +860,7 @@ const AppContent: React.FC = () => {
<Route
path="/dashboard/services"
element={
hasAccess(['owner', 'manager']) ? (
canAccess('can_access_services') ? (
<Services />
) : (
<Navigate to="/dashboard" />
@@ -861,7 +870,7 @@ const AppContent: React.FC = () => {
<Route
path="/dashboard/resources"
element={
hasAccess(['owner', 'manager']) ? (
canAccess('can_access_resources') ? (
<Resources onMasquerade={handleMasquerade} effectiveUser={user} />
) : (
<Navigate to="/dashboard" />
@@ -871,7 +880,7 @@ const AppContent: React.FC = () => {
<Route
path="/dashboard/staff"
element={
hasAccess(['owner', 'manager']) ? (
canAccess('can_access_staff') ? (
<Staff onMasquerade={handleMasquerade} effectiveUser={user} />
) : (
<Navigate to="/dashboard" />
@@ -881,7 +890,7 @@ const AppContent: React.FC = () => {
<Route
path="/dashboard/time-blocks"
element={
hasAccess(['owner', 'manager']) ? (
canAccess('can_access_time_blocks') ? (
<TimeBlocks />
) : (
<Navigate to="/dashboard" />
@@ -891,7 +900,7 @@ const AppContent: React.FC = () => {
<Route
path="/dashboard/locations"
element={
hasAccess(['owner', 'manager']) ? (
canAccess('can_access_locations') ? (
<Locations />
) : (
<Navigate to="/dashboard" />
@@ -911,7 +920,7 @@ const AppContent: React.FC = () => {
<Route
path="/dashboard/contracts"
element={
hasAccess(['owner', 'manager']) && canUse('contracts') ? (
canAccess('can_access_contracts') && canUse('contracts') ? (
<Contracts />
) : (
<Navigate to="/dashboard" />
@@ -921,7 +930,7 @@ const AppContent: React.FC = () => {
<Route
path="/dashboard/contracts/templates"
element={
hasAccess(['owner', 'manager']) && canUse('contracts') ? (
canAccess('can_access_contracts') && canUse('contracts') ? (
<ContractTemplates />
) : (
<Navigate to="/dashboard" />
@@ -931,13 +940,13 @@ const AppContent: React.FC = () => {
<Route
path="/dashboard/payments"
element={
hasAccess(['owner', 'manager']) ? <Payments /> : <Navigate to="/dashboard" />
canAccess('can_access_payments') ? <Payments /> : <Navigate to="/dashboard" />
}
/>
<Route
path="/dashboard/messages"
element={
hasAccess(['owner', 'manager']) && user?.can_send_messages ? (
canAccess('can_access_messages') ? (
<Messages />
) : (
<Navigate to="/dashboard" />
@@ -947,7 +956,7 @@ const AppContent: React.FC = () => {
<Route
path="/dashboard/site-editor"
element={
hasAccess(['owner', 'manager']) ? (
canAccess('can_access_site_editor') ? (
<PageEditor />
) : (
<Navigate to="/dashboard" />
@@ -967,7 +976,7 @@ const AppContent: React.FC = () => {
<Route
path="/dashboard/gallery"
element={
hasAccess(['owner', 'manager']) ? (
canAccess('can_access_gallery') ? (
<MediaGalleryPage />
) : (
<Navigate to="/dashboard" />
@@ -975,7 +984,8 @@ const AppContent: React.FC = () => {
}
/>
{/* Settings Routes with Nested Layout */}
{hasAccess(['owner']) ? (
{/* Owners have full access, staff need can_access_settings permission */}
{canAccess('can_access_settings') ? (
<Route path="/dashboard/settings" element={<SettingsLayout />}>
<Route index element={<Navigate to="/dashboard/settings/general" replace />} />
<Route path="general" element={<GeneralSettings />} />