import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { User } from '../types'; import { useCreateResource, useResources } from '../hooks/useBusiness'; import { useStaff, useToggleStaffActive, useUpdateStaff, StaffMember } from '../hooks/useStaff'; import { useInvitations, useCreateInvitation, useCancelInvitation, useResendInvitation, StaffInvitation, CreateInvitationData, } from '../hooks/useInvitations'; import { Plus, User as UserIcon, Shield, Briefcase, Calendar, X, Mail, Clock, Loader2, Send, Trash2, RefreshCw, Pencil, ChevronDown, ChevronRight, UserX, Power, } from 'lucide-react'; import Portal from '../components/Portal'; import StaffPermissions from '../components/StaffPermissions'; interface StaffProps { onMasquerade: (user: User) => void; effectiveUser: User; } const Staff: React.FC = ({ onMasquerade, effectiveUser }) => { const { t } = useTranslation(); const { data: staffMembers = [], isLoading, error } = useStaff(); const { data: resources = [] } = useResources(); const { data: invitations = [], isLoading: invitationsLoading } = useInvitations(); const createResourceMutation = useCreateResource(); const createInvitationMutation = useCreateInvitation(); const cancelInvitationMutation = useCancelInvitation(); const resendInvitationMutation = useResendInvitation(); const toggleActiveMutation = useToggleStaffActive(); const updateStaffMutation = useUpdateStaff(); const [isInviteModalOpen, setIsInviteModalOpen] = useState(false); const [inviteEmail, setInviteEmail] = useState(''); const [inviteRole, setInviteRole] = useState<'TENANT_MANAGER' | 'TENANT_STAFF'>('TENANT_STAFF'); const [createBookableResource, setCreateBookableResource] = useState(false); const [resourceName, setResourceName] = useState(''); const [invitePermissions, setInvitePermissions] = useState>({}); const [inviteError, setInviteError] = useState(''); const [inviteSuccess, setInviteSuccess] = useState(''); const [showInactiveStaff, setShowInactiveStaff] = useState(false); // Edit modal state const [isEditModalOpen, setIsEditModalOpen] = useState(false); const [editingStaff, setEditingStaff] = useState(null); const [editPermissions, setEditPermissions] = useState>({}); const [editError, setEditError] = useState(''); const [editSuccess, setEditSuccess] = useState(''); // Check if user can invite managers (only owners can) const canInviteManagers = effectiveUser.role === 'owner'; // Separate active and inactive staff const activeStaff = staffMembers.filter((s) => s.is_active); const inactiveStaff = staffMembers.filter((s) => !s.is_active); // Helper to check if a user is already linked to a resource const getLinkedResource = (userId: string) => { return resources.find((r: any) => r.user_id === parseInt(userId)); }; const handleMakeBookable = (user: any) => { if (confirm(`Create a bookable resource for ${user.name || user.username}?`)) { createResourceMutation.mutate({ name: user.name || user.username, type: 'STAFF', user_id: user.id, }); } }; const handleInviteSubmit = async (e: React.FormEvent) => { e.preventDefault(); setInviteError(''); setInviteSuccess(''); if (!inviteEmail.trim()) { setInviteError(t('staff.emailRequired', 'Email is required')); return; } try { const invitationData: CreateInvitationData = { email: inviteEmail.trim().toLowerCase(), role: inviteRole, create_bookable_resource: createBookableResource, resource_name: resourceName.trim(), permissions: invitePermissions, }; await createInvitationMutation.mutateAsync(invitationData); setInviteSuccess(t('staff.invitationSent', 'Invitation sent successfully!')); setInviteEmail(''); setCreateBookableResource(false); setResourceName(''); setInvitePermissions({}); // Close modal after short delay setTimeout(() => { setIsInviteModalOpen(false); setInviteSuccess(''); }, 1500); } catch (err: any) { setInviteError(err.response?.data?.error || t('staff.invitationFailed', 'Failed to send invitation')); } }; const handleCancelInvitation = async (invitation: StaffInvitation) => { if (confirm(t('staff.confirmCancelInvitation', `Cancel invitation to ${invitation.email}?`))) { try { await cancelInvitationMutation.mutateAsync(invitation.id); } catch (err: any) { alert(err.response?.data?.error || t('staff.cancelFailed', 'Failed to cancel invitation')); } } }; const handleResendInvitation = async (invitation: StaffInvitation) => { try { await resendInvitationMutation.mutateAsync(invitation.id); alert(t('staff.invitationResent', 'Invitation resent successfully!')); } catch (err: any) { alert(err.response?.data?.error || t('staff.resendFailed', 'Failed to resend invitation')); } }; const openInviteModal = () => { setInviteEmail(''); setInviteRole('TENANT_STAFF'); setCreateBookableResource(false); setResourceName(''); setInvitePermissions({}); setInviteError(''); setInviteSuccess(''); setIsInviteModalOpen(true); }; if (isLoading) { return (
); } if (error) { return (

{t('staff.errorLoading')}: {(error as Error).message}

); } const handleToggleActive = async (user: any) => { const action = user.is_active ? 'deactivate' : 'reactivate'; if (confirm(t('staff.confirmToggleActive', `Are you sure you want to ${action} ${user.name}?`))) { try { await toggleActiveMutation.mutateAsync(user.id); } catch (err: any) { alert(err.response?.data?.error || t('staff.toggleFailed', `Failed to ${action} staff member`)); } } }; const openEditModal = (staff: StaffMember) => { setEditingStaff(staff); setEditPermissions(staff.permissions || {}); setEditError(''); setEditSuccess(''); setIsEditModalOpen(true); }; const closeEditModal = () => { setIsEditModalOpen(false); setEditingStaff(null); setEditPermissions({}); setEditError(''); setEditSuccess(''); }; const handleSaveStaffSettings = async () => { if (!editingStaff) return; setEditError(''); try { await updateStaffMutation.mutateAsync({ id: editingStaff.id, updates: { permissions: editPermissions }, }); setEditSuccess(t('staff.settingsSaved', 'Settings saved successfully')); setTimeout(() => { closeEditModal(); }, 1000); } catch (err: any) { setEditError(err.response?.data?.error || t('staff.saveFailed', 'Failed to save settings')); } }; const handleDeactivateFromModal = async () => { if (!editingStaff) return; const action = editingStaff.is_active ? 'deactivate' : 'reactivate'; if (confirm(t('staff.confirmToggleActive', `Are you sure you want to ${action} ${editingStaff.name}?`))) { try { await toggleActiveMutation.mutateAsync(editingStaff.id); closeEditModal(); } catch (err: any) { setEditError(err.response?.data?.error || t('staff.toggleFailed', `Failed to ${action} staff member`)); } } }; return (
{/* Header */}

{t('staff.title')}

{t('staff.description')}

{/* Pending Invitations */} {invitations.length > 0 && (

{t('staff.pendingInvitations', 'Pending Invitations')} ({invitations.length})

{invitations.map((invitation) => (
{invitation.email}
{invitation.role_display} • {t('staff.expires', 'Expires')}{' '} {new Date(invitation.expires_at).toLocaleDateString()}
))}
)} {/* Table */}
{activeStaff.map((user: any) => { const linkedResource = getLinkedResource(user.id); // Only owners can masquerade as staff (per backend permissions) const canMasquerade = effectiveUser.role === 'owner' && user.id !== effectiveUser.id; // Owners can deactivate anyone except themselves const canDeactivate = effectiveUser.role === 'owner' && user.id !== effectiveUser.id && user.role !== 'owner'; return ( ); })}
{t('staff.name')} {t('staff.role')} {t('staff.bookableResource')} {t('common.actions')}
{user.name ? user.name.charAt(0).toUpperCase() : user.email.charAt(0).toUpperCase()}
{user.name || user.email}
{user.email}
{user.role === 'owner' && } {user.role === 'manager' && } {user.role} {linkedResource ? ( {t('staff.yes')} ({linkedResource.name}) ) : ( )}
{canMasquerade && ( )}
{activeStaff.length === 0 && (

{t('staff.noStaffFound', 'No staff members found')}

{t('staff.inviteFirstStaff', 'Invite your first team member to get started')}

)}
{/* Inactive Staff Section */} {inactiveStaff.length > 0 && (
{showInactiveStaff && (
{inactiveStaff.map((user: any) => { const linkedResource = getLinkedResource(user.id); return ( ); })}
{user.name ? user.name.charAt(0).toUpperCase() : user.email.charAt(0).toUpperCase()}
{user.name || user.email}
{user.email}
{user.role === 'owner' && } {user.role === 'manager' && } {user.role} {linkedResource ? ( {linkedResource.name} ) : ( - )}
)}
)} {/* Invite Modal */} {isInviteModalOpen && (

{t('staff.inviteStaff')}

{t( 'staff.inviteDescription', "Enter the email address of the person you'd like to invite. They'll receive an email with instructions to join your team." )}

{/* Email Input */}
setInviteEmail(e.target.value)} placeholder={t('staff.emailPlaceholder', 'colleague@example.com')} required className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-500 focus:border-brand-500" />
{/* Role Selector */}

{inviteRole === 'TENANT_MANAGER' ? t('staff.managerRoleHint', 'Managers can manage staff, resources, and view reports') : t('staff.staffRoleHint', 'Staff members can manage their own schedule and appointments')}

{/* Permissions - Using shared component */} {inviteRole === 'TENANT_MANAGER' && ( )} {inviteRole === 'TENANT_STAFF' && ( )} {/* Make Bookable Option */}
{/* Resource Name (only shown if bookable is checked) */} {createBookableResource && (
setResourceName(e.target.value)} placeholder={t('staff.resourceNamePlaceholder', "Defaults to person's name")} className="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-500 focus:border-brand-500" />
)}
{/* Error Message */} {inviteError && (

{inviteError}

)} {/* Success Message */} {inviteSuccess && (

{inviteSuccess}

)} {/* Buttons */}
)} {/* Edit Staff Modal */} {isEditModalOpen && editingStaff && (

{t('staff.editStaff', 'Edit Staff Member')}

{/* Staff Info */}
{editingStaff.name.charAt(0).toUpperCase()}
{editingStaff.name}
{editingStaff.email}
{editingStaff.role === 'owner' && } {editingStaff.role === 'manager' && } {editingStaff.role}
{/* Permissions - Using shared component */} {editingStaff.role === 'manager' && ( )} {editingStaff.role === 'staff' && ( )} {/* No permissions for owners */} {editingStaff.role === 'owner' && (

{t('staff.ownerFullAccess', 'Owners have full access to all features and settings.')}

)} {/* Error Message */} {editError && (

{editError}

)} {/* Success Message */} {editSuccess && (

{editSuccess}

)} {/* Danger Zone - Deactivate (only for non-owners, and current user can't deactivate themselves) */} {editingStaff.role !== 'owner' && effectiveUser.id !== editingStaff.id && effectiveUser.role === 'owner' && (

{t('staff.dangerZone', 'Danger Zone')}

{editingStaff.is_active ? t('staff.deactivateAccount', 'Deactivate Account') : t('staff.reactivateAccount', 'Reactivate Account')}

{editingStaff.is_active ? t('staff.deactivateHint', 'Prevent this user from logging in while keeping their data') : t('staff.reactivateHint', 'Allow this user to log in again')}

)} {/* Action Buttons */}
{editingStaff.role !== 'owner' && ( )}
)}
); }; export default Staff;