|
|
|
|
@@ -1,5 +1,6 @@
|
|
|
|
|
import React from 'react';
|
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
|
import { ChevronDown, ChevronRight } from 'lucide-react';
|
|
|
|
|
|
|
|
|
|
export interface PermissionConfig {
|
|
|
|
|
key: string;
|
|
|
|
|
@@ -8,20 +9,134 @@ export interface PermissionConfig {
|
|
|
|
|
hintKey: string;
|
|
|
|
|
hintDefault: string;
|
|
|
|
|
defaultValue: boolean;
|
|
|
|
|
roles: ('manager' | 'staff')[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Business Settings sub-permissions
|
|
|
|
|
export const SETTINGS_PERMISSION_CONFIGS: PermissionConfig[] = [
|
|
|
|
|
{
|
|
|
|
|
key: 'can_access_settings_general',
|
|
|
|
|
labelKey: 'staff.canAccessSettingsGeneral',
|
|
|
|
|
labelDefault: 'General Settings',
|
|
|
|
|
hintKey: 'staff.canAccessSettingsGeneralHint',
|
|
|
|
|
hintDefault: 'Business name, timezone, and basic configuration',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_access_settings_business_hours',
|
|
|
|
|
labelKey: 'staff.canAccessSettingsBusinessHours',
|
|
|
|
|
labelDefault: 'Business Hours',
|
|
|
|
|
hintKey: 'staff.canAccessSettingsBusinessHoursHint',
|
|
|
|
|
hintDefault: 'Set regular operating hours',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_access_settings_branding',
|
|
|
|
|
labelKey: 'staff.canAccessSettingsBranding',
|
|
|
|
|
labelDefault: 'Branding',
|
|
|
|
|
hintKey: 'staff.canAccessSettingsBrandingHint',
|
|
|
|
|
hintDefault: 'Logo, colors, and visual identity',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_access_settings_booking',
|
|
|
|
|
labelKey: 'staff.canAccessSettingsBooking',
|
|
|
|
|
labelDefault: 'Booking Settings',
|
|
|
|
|
hintKey: 'staff.canAccessSettingsBookingHint',
|
|
|
|
|
hintDefault: 'Booking policies and rules',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_access_settings_communication',
|
|
|
|
|
labelKey: 'staff.canAccessSettingsCommunication',
|
|
|
|
|
labelDefault: 'Communication',
|
|
|
|
|
hintKey: 'staff.canAccessSettingsCommunicationHint',
|
|
|
|
|
hintDefault: 'Notification preferences and reminders',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_access_settings_embed_widget',
|
|
|
|
|
labelKey: 'staff.canAccessSettingsEmbedWidget',
|
|
|
|
|
labelDefault: 'Embed Widget',
|
|
|
|
|
hintKey: 'staff.canAccessSettingsEmbedWidgetHint',
|
|
|
|
|
hintDefault: 'Configure booking widget for websites',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_access_settings_email_templates',
|
|
|
|
|
labelKey: 'staff.canAccessSettingsEmailTemplates',
|
|
|
|
|
labelDefault: 'Email Templates',
|
|
|
|
|
hintKey: 'staff.canAccessSettingsEmailTemplatesHint',
|
|
|
|
|
hintDefault: 'Customize automated emails',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_access_settings_staff_roles',
|
|
|
|
|
labelKey: 'staff.canAccessSettingsStaffRoles',
|
|
|
|
|
labelDefault: 'Staff Roles',
|
|
|
|
|
hintKey: 'staff.canAccessSettingsStaffRolesHint',
|
|
|
|
|
hintDefault: 'Create and manage permission roles',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_access_settings_resource_types',
|
|
|
|
|
labelKey: 'staff.canAccessSettingsResourceTypes',
|
|
|
|
|
labelDefault: 'Resource Types',
|
|
|
|
|
hintKey: 'staff.canAccessSettingsResourceTypesHint',
|
|
|
|
|
hintDefault: 'Configure resource categories',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_access_settings_api',
|
|
|
|
|
labelKey: 'staff.canAccessSettingsApi',
|
|
|
|
|
labelDefault: 'API & Integrations',
|
|
|
|
|
hintKey: 'staff.canAccessSettingsApiHint',
|
|
|
|
|
hintDefault: 'Manage API tokens and webhooks',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_access_settings_custom_domains',
|
|
|
|
|
labelKey: 'staff.canAccessSettingsCustomDomains',
|
|
|
|
|
labelDefault: 'Custom Domains',
|
|
|
|
|
hintKey: 'staff.canAccessSettingsCustomDomainsHint',
|
|
|
|
|
hintDefault: 'Configure custom domain settings',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_access_settings_authentication',
|
|
|
|
|
labelKey: 'staff.canAccessSettingsAuthentication',
|
|
|
|
|
labelDefault: 'Authentication',
|
|
|
|
|
hintKey: 'staff.canAccessSettingsAuthenticationHint',
|
|
|
|
|
hintDefault: 'OAuth and social login configuration',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_access_settings_email',
|
|
|
|
|
labelKey: 'staff.canAccessSettingsEmail',
|
|
|
|
|
labelDefault: 'Email Setup',
|
|
|
|
|
hintKey: 'staff.canAccessSettingsEmailHint',
|
|
|
|
|
hintDefault: 'Configure email addresses for tickets',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_access_settings_sms_calling',
|
|
|
|
|
labelKey: 'staff.canAccessSettingsSmsCalling',
|
|
|
|
|
labelDefault: 'SMS & Calling',
|
|
|
|
|
hintKey: 'staff.canAccessSettingsSmsCallingHint',
|
|
|
|
|
hintDefault: 'Manage credits and phone numbers',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// Define all available permissions in one place
|
|
|
|
|
// All permissions are now available to staff (via staff roles)
|
|
|
|
|
export const PERMISSION_CONFIGS: PermissionConfig[] = [
|
|
|
|
|
// Manager-only permissions
|
|
|
|
|
{
|
|
|
|
|
key: 'can_invite_staff',
|
|
|
|
|
labelKey: 'staff.canInviteStaff',
|
|
|
|
|
labelDefault: 'Can invite new staff members',
|
|
|
|
|
hintKey: 'staff.canInviteStaffHint',
|
|
|
|
|
hintDefault: 'Allow this manager to send invitations to new staff members',
|
|
|
|
|
hintDefault: 'Allow this staff member to send invitations to new staff members',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
roles: ['manager'],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_manage_resources',
|
|
|
|
|
@@ -29,8 +144,7 @@ export const PERMISSION_CONFIGS: PermissionConfig[] = [
|
|
|
|
|
labelDefault: 'Can manage resources',
|
|
|
|
|
hintKey: 'staff.canManageResourcesHint',
|
|
|
|
|
hintDefault: 'Create, edit, and delete bookable resources',
|
|
|
|
|
defaultValue: true,
|
|
|
|
|
roles: ['manager'],
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_manage_services',
|
|
|
|
|
@@ -38,8 +152,7 @@ export const PERMISSION_CONFIGS: PermissionConfig[] = [
|
|
|
|
|
labelDefault: 'Can manage services',
|
|
|
|
|
hintKey: 'staff.canManageServicesHint',
|
|
|
|
|
hintDefault: 'Create, edit, and delete service offerings',
|
|
|
|
|
defaultValue: true,
|
|
|
|
|
roles: ['manager'],
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_view_reports',
|
|
|
|
|
@@ -47,17 +160,7 @@ export const PERMISSION_CONFIGS: PermissionConfig[] = [
|
|
|
|
|
labelDefault: 'Can view reports',
|
|
|
|
|
hintKey: 'staff.canViewReportsHint',
|
|
|
|
|
hintDefault: 'Access business analytics and financial reports',
|
|
|
|
|
defaultValue: true,
|
|
|
|
|
roles: ['manager'],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_access_settings',
|
|
|
|
|
labelKey: 'staff.canAccessSettings',
|
|
|
|
|
labelDefault: 'Can access business settings',
|
|
|
|
|
hintKey: 'staff.canAccessSettingsHint',
|
|
|
|
|
hintDefault: 'Modify business profile, branding, and configuration',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
roles: ['manager'],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_refund_payments',
|
|
|
|
|
@@ -66,7 +169,6 @@ export const PERMISSION_CONFIGS: PermissionConfig[] = [
|
|
|
|
|
hintKey: 'staff.canRefundPaymentsHint',
|
|
|
|
|
hintDefault: 'Process refunds for customer payments',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
roles: ['manager'],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_send_messages',
|
|
|
|
|
@@ -74,10 +176,8 @@ export const PERMISSION_CONFIGS: PermissionConfig[] = [
|
|
|
|
|
labelDefault: 'Can send broadcast messages',
|
|
|
|
|
hintKey: 'staff.canSendMessagesHint',
|
|
|
|
|
hintDefault: 'Send messages to groups of staff and customers',
|
|
|
|
|
defaultValue: true,
|
|
|
|
|
roles: ['manager'],
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
// Staff-only permissions
|
|
|
|
|
{
|
|
|
|
|
key: 'can_view_all_schedules',
|
|
|
|
|
labelKey: 'staff.canViewAllSchedules',
|
|
|
|
|
@@ -85,7 +185,6 @@ export const PERMISSION_CONFIGS: PermissionConfig[] = [
|
|
|
|
|
hintKey: 'staff.canViewAllSchedulesHint',
|
|
|
|
|
hintDefault: 'View schedules of other staff members (otherwise only their own)',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
roles: ['staff'],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_manage_own_appointments',
|
|
|
|
|
@@ -94,112 +193,132 @@ export const PERMISSION_CONFIGS: PermissionConfig[] = [
|
|
|
|
|
hintKey: 'staff.canManageOwnAppointmentsHint',
|
|
|
|
|
hintDefault: 'Create, reschedule, and cancel their own appointments',
|
|
|
|
|
defaultValue: true,
|
|
|
|
|
roles: ['staff'],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'can_self_approve_time_off',
|
|
|
|
|
labelKey: 'staff.canSelfApproveTimeOff',
|
|
|
|
|
labelDefault: 'Can self-approve time off',
|
|
|
|
|
hintKey: 'staff.canSelfApproveTimeOffHint',
|
|
|
|
|
hintDefault: 'Add time off without requiring manager/owner approval',
|
|
|
|
|
hintDefault: 'Add time off without requiring owner approval',
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
roles: ['staff'],
|
|
|
|
|
},
|
|
|
|
|
// Shared permissions (both manager and staff)
|
|
|
|
|
{
|
|
|
|
|
key: 'can_access_tickets',
|
|
|
|
|
labelKey: 'staff.canAccessTickets',
|
|
|
|
|
labelDefault: 'Can access support tickets',
|
|
|
|
|
hintKey: 'staff.canAccessTicketsHint',
|
|
|
|
|
hintDefault: 'View and manage customer support tickets',
|
|
|
|
|
defaultValue: true, // Default for managers; staff will override to false
|
|
|
|
|
roles: ['manager', 'staff'],
|
|
|
|
|
defaultValue: false,
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// Get default permissions for a role
|
|
|
|
|
export const getDefaultPermissions = (role: 'manager' | 'staff'): Record<string, boolean> => {
|
|
|
|
|
// Get default permissions for staff
|
|
|
|
|
export const getDefaultPermissions = (): Record<string, boolean> => {
|
|
|
|
|
const defaults: Record<string, boolean> = {};
|
|
|
|
|
PERMISSION_CONFIGS.forEach((config) => {
|
|
|
|
|
if (config.roles.includes(role)) {
|
|
|
|
|
// Staff members have ticket access disabled by default
|
|
|
|
|
if (role === 'staff' && config.key === 'can_access_tickets') {
|
|
|
|
|
defaults[config.key] = false;
|
|
|
|
|
} else {
|
|
|
|
|
defaults[config.key] = config.defaultValue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
defaults[config.key] = config.defaultValue;
|
|
|
|
|
});
|
|
|
|
|
SETTINGS_PERMISSION_CONFIGS.forEach((config) => {
|
|
|
|
|
defaults[config.key] = config.defaultValue;
|
|
|
|
|
});
|
|
|
|
|
defaults['can_access_settings'] = false;
|
|
|
|
|
return defaults;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
interface StaffPermissionsProps {
|
|
|
|
|
role: 'manager' | 'staff';
|
|
|
|
|
role: 'staff';
|
|
|
|
|
permissions: Record<string, boolean>;
|
|
|
|
|
onChange: (permissions: Record<string, boolean>) => void;
|
|
|
|
|
variant?: 'invite' | 'edit';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const StaffPermissions: React.FC<StaffPermissionsProps> = ({
|
|
|
|
|
role,
|
|
|
|
|
permissions,
|
|
|
|
|
onChange,
|
|
|
|
|
variant = 'edit',
|
|
|
|
|
}) => {
|
|
|
|
|
const { t } = useTranslation();
|
|
|
|
|
|
|
|
|
|
// Filter permissions for this role
|
|
|
|
|
const rolePermissions = PERMISSION_CONFIGS.filter((config) =>
|
|
|
|
|
config.roles.includes(role)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleToggle = (key: string, checked: boolean) => {
|
|
|
|
|
onChange({ ...permissions, [key]: checked });
|
|
|
|
|
};
|
|
|
|
|
const [settingsExpanded, setSettingsExpanded] = useState(false);
|
|
|
|
|
|
|
|
|
|
// Get the current value, falling back to default
|
|
|
|
|
const getValue = (config: PermissionConfig): boolean => {
|
|
|
|
|
if (permissions[config.key] !== undefined) {
|
|
|
|
|
return permissions[config.key];
|
|
|
|
|
const getValue = (key: string, defaultValue: boolean = false): boolean => {
|
|
|
|
|
if (permissions[key] !== undefined) {
|
|
|
|
|
return permissions[key];
|
|
|
|
|
}
|
|
|
|
|
// Staff have ticket access disabled by default
|
|
|
|
|
if (role === 'staff' && config.key === 'can_access_tickets') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return config.defaultValue;
|
|
|
|
|
return defaultValue;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Different styling for manager vs staff permissions
|
|
|
|
|
const isManagerPermission = (config: PermissionConfig) =>
|
|
|
|
|
config.roles.includes('manager') && !config.roles.includes('staff');
|
|
|
|
|
const hasSettingsAccess = getValue('can_access_settings', false);
|
|
|
|
|
|
|
|
|
|
const getPermissionStyle = (config: PermissionConfig) => {
|
|
|
|
|
if (isManagerPermission(config) || role === 'manager') {
|
|
|
|
|
return 'bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800 hover:bg-blue-100 dark:hover:bg-blue-900/30';
|
|
|
|
|
// Auto-expand settings section if any settings permissions are enabled
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (hasSettingsAccess) {
|
|
|
|
|
const hasAnySettingEnabled = SETTINGS_PERMISSION_CONFIGS.some(
|
|
|
|
|
(config) => getValue(config.key, false)
|
|
|
|
|
);
|
|
|
|
|
if (hasAnySettingEnabled) {
|
|
|
|
|
setSettingsExpanded(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 'bg-gray-50 dark:bg-gray-700/50 border-gray-200 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700';
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handleToggle = (key: string, checked: boolean) => {
|
|
|
|
|
const newPermissions = { ...permissions, [key]: checked };
|
|
|
|
|
|
|
|
|
|
// If turning off main settings access, turn off all sub-settings
|
|
|
|
|
if (key === 'can_access_settings' && !checked) {
|
|
|
|
|
SETTINGS_PERMISSION_CONFIGS.forEach((config) => {
|
|
|
|
|
newPermissions[config.key] = false;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onChange(newPermissions);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (rolePermissions.length === 0) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const handleSettingsMainToggle = (checked: boolean) => {
|
|
|
|
|
const newPermissions = { ...permissions, can_access_settings: checked };
|
|
|
|
|
|
|
|
|
|
// If turning off, disable all sub-settings
|
|
|
|
|
if (!checked) {
|
|
|
|
|
SETTINGS_PERMISSION_CONFIGS.forEach((config) => {
|
|
|
|
|
newPermissions[config.key] = false;
|
|
|
|
|
});
|
|
|
|
|
setSettingsExpanded(false);
|
|
|
|
|
} else {
|
|
|
|
|
// If turning on, expand the section
|
|
|
|
|
setSettingsExpanded(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onChange(newPermissions);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSelectAllSettings = (selectAll: boolean) => {
|
|
|
|
|
const newPermissions = { ...permissions };
|
|
|
|
|
SETTINGS_PERMISSION_CONFIGS.forEach((config) => {
|
|
|
|
|
newPermissions[config.key] = selectAll;
|
|
|
|
|
});
|
|
|
|
|
onChange(newPermissions);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Count how many settings sub-permissions are enabled
|
|
|
|
|
const enabledSettingsCount = SETTINGS_PERMISSION_CONFIGS.filter((config) =>
|
|
|
|
|
getValue(config.key, false)
|
|
|
|
|
).length;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
<h4 className="text-sm font-semibold text-gray-700 dark:text-gray-300">
|
|
|
|
|
{role === 'manager'
|
|
|
|
|
? t('staff.managerPermissions', 'Manager Permissions')
|
|
|
|
|
: t('staff.staffPermissions', 'Staff Permissions')}
|
|
|
|
|
{t('staff.staffPermissions', 'Staff Permissions')}
|
|
|
|
|
</h4>
|
|
|
|
|
|
|
|
|
|
{rolePermissions.map((config) => (
|
|
|
|
|
{/* Regular permissions */}
|
|
|
|
|
{PERMISSION_CONFIGS.map((config) => (
|
|
|
|
|
<label
|
|
|
|
|
key={config.key}
|
|
|
|
|
className={`flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${getPermissionStyle(config)}`}
|
|
|
|
|
className="flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors bg-gray-50 dark:bg-gray-700/50 border-gray-200 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
checked={getValue(config)}
|
|
|
|
|
checked={getValue(config.key, config.defaultValue)}
|
|
|
|
|
onChange={(e) => handleToggle(config.key, e.target.checked)}
|
|
|
|
|
className="w-4 h-4 mt-0.5 rounded border-gray-300 text-brand-600 focus:ring-brand-500"
|
|
|
|
|
/>
|
|
|
|
|
@@ -213,6 +332,113 @@ const StaffPermissions: React.FC<StaffPermissionsProps> = ({
|
|
|
|
|
</div>
|
|
|
|
|
</label>
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
|
|
{/* Business Settings Section */}
|
|
|
|
|
<div className="border rounded-lg border-gray-200 dark:border-gray-600 overflow-hidden">
|
|
|
|
|
{/* Main Business Settings Toggle */}
|
|
|
|
|
<div
|
|
|
|
|
className={`flex items-start gap-3 p-3 cursor-pointer transition-colors ${
|
|
|
|
|
hasSettingsAccess
|
|
|
|
|
? 'bg-brand-50 dark:bg-brand-900/20'
|
|
|
|
|
: 'bg-gray-50 dark:bg-gray-700/50 hover:bg-gray-100 dark:hover:bg-gray-700'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
checked={hasSettingsAccess}
|
|
|
|
|
onChange={(e) => handleSettingsMainToggle(e.target.checked)}
|
|
|
|
|
className="w-4 h-4 mt-0.5 rounded border-gray-300 text-brand-600 focus:ring-brand-500"
|
|
|
|
|
/>
|
|
|
|
|
<div
|
|
|
|
|
className="flex-1"
|
|
|
|
|
onClick={() => hasSettingsAccess && setSettingsExpanded(!settingsExpanded)}
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<div>
|
|
|
|
|
<span className="text-sm font-medium text-gray-900 dark:text-white">
|
|
|
|
|
{t('staff.canAccessSettings', 'Can access business settings')}
|
|
|
|
|
</span>
|
|
|
|
|
{hasSettingsAccess && enabledSettingsCount > 0 && (
|
|
|
|
|
<span className="ml-2 text-xs text-brand-600 dark:text-brand-400">
|
|
|
|
|
({enabledSettingsCount}/{SETTINGS_PERMISSION_CONFIGS.length} enabled)
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{hasSettingsAccess && (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
className="p-1 hover:bg-gray-200 dark:hover:bg-gray-600 rounded"
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
setSettingsExpanded(!settingsExpanded);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{settingsExpanded ? (
|
|
|
|
|
<ChevronDown size={16} className="text-gray-500" />
|
|
|
|
|
) : (
|
|
|
|
|
<ChevronRight size={16} className="text-gray-500" />
|
|
|
|
|
)}
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
|
|
|
|
{t(
|
|
|
|
|
'staff.canAccessSettingsHint',
|
|
|
|
|
'Access to business settings pages (select specific pages below)'
|
|
|
|
|
)}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Sub-permissions (collapsible) */}
|
|
|
|
|
{hasSettingsAccess && settingsExpanded && (
|
|
|
|
|
<div className="border-t border-gray-200 dark:border-gray-600 bg-gray-25 dark:bg-gray-800/50">
|
|
|
|
|
{/* Select All / None buttons */}
|
|
|
|
|
<div className="px-3 py-2 border-b border-gray-200 dark:border-gray-600 flex gap-2">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => handleSelectAllSettings(true)}
|
|
|
|
|
className="text-xs text-brand-600 hover:text-brand-700 dark:text-brand-400 dark:hover:text-brand-300 font-medium"
|
|
|
|
|
>
|
|
|
|
|
{t('staff.selectAll', 'Select All')}
|
|
|
|
|
</button>
|
|
|
|
|
<span className="text-gray-300 dark:text-gray-600">|</span>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => handleSelectAllSettings(false)}
|
|
|
|
|
className="text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300 font-medium"
|
|
|
|
|
>
|
|
|
|
|
{t('staff.selectNone', 'Select None')}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Individual settings permissions */}
|
|
|
|
|
<div className="divide-y divide-gray-100 dark:divide-gray-700">
|
|
|
|
|
{SETTINGS_PERMISSION_CONFIGS.map((config) => (
|
|
|
|
|
<label
|
|
|
|
|
key={config.key}
|
|
|
|
|
className="flex items-start gap-3 px-3 py-2.5 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"
|
|
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
checked={getValue(config.key, config.defaultValue)}
|
|
|
|
|
onChange={(e) => handleToggle(config.key, e.target.checked)}
|
|
|
|
|
className="w-4 h-4 mt-0.5 rounded border-gray-300 text-brand-600 focus:ring-brand-500"
|
|
|
|
|
/>
|
|
|
|
|
<div>
|
|
|
|
|
<span className="text-sm font-medium text-gray-900 dark:text-white">
|
|
|
|
|
{t(config.labelKey, config.labelDefault)}
|
|
|
|
|
</span>
|
|
|
|
|
<p className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
|
|
|
|
{t(config.hintKey, config.hintDefault)}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</label>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|