import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Stack, Text, Button, TextInput, Table, ActionIcon, Menu, Badge, Loader, Group, Modal, Select, Tooltip, CloseButton, Avatar, Box, } from '@mantine/core'; import LocalIcon from '@app/components/shared/LocalIcon'; import { alert } from '@app/components/toast'; import { userManagementService, User } from '@app/services/userManagementService'; import { teamService, Team } from '@app/services/teamService'; import { Z_INDEX_OVER_CONFIG_MODAL } from '@app/styles/zIndex'; import { useAppConfig } from '@app/contexts/AppConfigContext'; import InviteMembersModal from '@app/components/shared/InviteMembersModal'; import { useLoginRequired } from '@app/hooks/useLoginRequired'; import LoginRequiredBanner from '@app/components/shared/config/LoginRequiredBanner'; import { useNavigate } from 'react-router-dom'; import UpdateSeatsButton from '@app/components/shared/UpdateSeatsButton'; import { useLicense } from '@app/contexts/LicenseContext'; import ChangeUserPasswordModal from '@app/components/shared/ChangeUserPasswordModal'; export default function PeopleSection() { const { t } = useTranslation(); const { config } = useAppConfig(); const { loginEnabled } = useLoginRequired(); const navigate = useNavigate(); const { licenseInfo: globalLicenseInfo } = useLicense(); const [users, setUsers] = useState([]); const [teams, setTeams] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [inviteModalOpened, setInviteModalOpened] = useState(false); const [editUserModalOpened, setEditUserModalOpened] = useState(false); const [changePasswordModalOpened, setChangePasswordModalOpened] = useState(false); const [passwordUser, setPasswordUser] = useState(null); const [selectedUser, setSelectedUser] = useState(null); const [processing, setProcessing] = useState(false); const [mailEnabled, setMailEnabled] = useState(false); // License information const [licenseInfo, setLicenseInfo] = useState<{ maxAllowedUsers: number; availableSlots: number; grandfatheredUserCount: number; licenseMaxUsers: number; premiumEnabled: boolean; totalUsers: number; } | null>(null); const hasNoSlots = licenseInfo ? licenseInfo.availableSlots === 0 : false; const handleAddMembersClick = () => { if (!loginEnabled) { return; } if (hasNoSlots) { navigate('/settings/adminPlan'); return; } setInviteModalOpened(true); }; const addMemberTooltip = !loginEnabled ? t('workspace.people.loginRequired', 'Enable login mode first') : hasNoSlots ? t('workspace.people.license.noSlotsAvailable', 'No user slots available') : null; // Form state for edit user modal const [editForm, setEditForm] = useState({ role: 'ROLE_USER', teamId: undefined as number | undefined, }); useEffect(() => { fetchData(); }, []); useEffect(() => { if (config) { console.log('[PeopleSection] Email invites enabled:', config.enableEmailInvites); } }, [config]); const fetchData = async () => { try { setLoading(true); if (loginEnabled) { const [adminData, teamsData] = await Promise.all([ userManagementService.getUsers(), teamService.getTeams(), ]); // Enrich users with session data const enrichedUsers = adminData.users.map(user => ({ ...user, isActive: adminData.userSessions[user.username] || false, lastRequest: adminData.userLastRequest[user.username] || undefined, })); setUsers(enrichedUsers); setTeams(teamsData); // Store license information setLicenseInfo({ maxAllowedUsers: adminData.maxAllowedUsers, availableSlots: adminData.availableSlots, grandfatheredUserCount: adminData.grandfatheredUserCount, licenseMaxUsers: adminData.licenseMaxUsers, premiumEnabled: adminData.premiumEnabled, totalUsers: adminData.totalUsers, }); setMailEnabled(adminData.mailEnabled); } else { // Provide example data when login is disabled const exampleUsers: User[] = [ { id: 1, username: 'admin', email: 'admin@example.com', enabled: true, roleName: 'ROLE_ADMIN', rolesAsString: 'ROLE_ADMIN', authenticationType: 'password', isActive: true, lastRequest: Date.now(), team: { id: 1, name: 'Engineering' } }, { id: 2, username: 'john.doe', email: 'john.doe@example.com', enabled: true, roleName: 'ROLE_USER', rolesAsString: 'ROLE_USER', authenticationType: 'password', isActive: false, lastRequest: Date.now() - 86400000, team: { id: 1, name: 'Engineering' } }, { id: 3, username: 'jane.smith', email: 'jane.smith@example.com', enabled: true, roleName: 'ROLE_USER', rolesAsString: 'ROLE_USER', authenticationType: 'oauth', isActive: true, lastRequest: Date.now(), team: { id: 2, name: 'Marketing' } }, { id: 4, username: 'bob.wilson', email: 'bob.wilson@example.com', enabled: false, roleName: 'ROLE_USER', rolesAsString: 'ROLE_USER', authenticationType: 'password', isActive: false, lastRequest: Date.now() - 604800000, team: undefined } ]; const exampleTeams: Team[] = [ { id: 1, name: 'Engineering', userCount: 3 }, { id: 2, name: 'Marketing', userCount: 2 } ]; setUsers(exampleUsers); setTeams(exampleTeams); setMailEnabled(false); // Example license information setLicenseInfo({ maxAllowedUsers: 10, availableSlots: 6, grandfatheredUserCount: 0, licenseMaxUsers: 5, premiumEnabled: true, totalUsers: 4, }); } } catch (error) { console.error('Failed to fetch people data:', error); alert({ alertType: 'error', title: 'Failed to load people data' }); } finally { setLoading(false); } }; const handleUpdateUserRole = async () => { if (!selectedUser) return; try { setProcessing(true); await userManagementService.updateUserRole({ username: selectedUser.username, role: editForm.role, teamId: editForm.teamId, }); alert({ alertType: 'success', title: t('workspace.people.editMember.success') }); closeEditModal(); fetchData(); } catch (error: any) { console.error('Failed to update user:', error); const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message || t('workspace.people.editMember.error'); alert({ alertType: 'error', title: errorMessage }); } finally { setProcessing(false); } }; const handleToggleEnabled = async (user: User) => { try { await userManagementService.toggleUserEnabled(user.username, !user.enabled); alert({ alertType: 'success', title: t('workspace.people.toggleEnabled.success') }); fetchData(); } catch (error: any) { console.error('Failed to toggle user status:', error); const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message || t('workspace.people.toggleEnabled.error'); alert({ alertType: 'error', title: errorMessage }); } }; const handleDeleteUser = async (user: User) => { const confirmMessage = t('workspace.people.confirmDelete', 'Are you sure you want to delete this user? This action cannot be undone.'); if (!window.confirm(`${confirmMessage}\n\nUser: ${user.username}`)) { return; } try { await userManagementService.deleteUser(user.username); alert({ alertType: 'success', title: t('workspace.people.deleteUserSuccess', 'User deleted successfully') }); fetchData(); } catch (error: any) { console.error('Failed to delete user:', error); const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message || t('workspace.people.deleteUserError', 'Failed to delete user'); alert({ alertType: 'error', title: errorMessage }); } }; const openEditModal = (user: User) => { setSelectedUser(user); setEditForm({ role: user.roleName, teamId: user.team?.id, }); setEditUserModalOpened(true); }; const openChangePasswordModal = (user: User) => { setPasswordUser(user); setChangePasswordModalOpened(true); }; const closeChangePasswordModal = () => { setChangePasswordModalOpened(false); setPasswordUser(null); }; const closeEditModal = () => { setEditUserModalOpened(false); setSelectedUser(null); setEditForm({ role: 'ROLE_USER', teamId: undefined, }); }; const filteredUsers = users.filter((user) => user.username.toLowerCase().includes(searchQuery.toLowerCase()) ); const roleOptions = [ { value: 'ROLE_ADMIN', label: t('workspace.people.admin'), description: t('workspace.people.roleDescriptions.admin', 'Can manage settings and invite members, with full administrative access.'), icon: 'admin-panel-settings' }, { value: 'ROLE_USER', label: t('workspace.people.member'), description: t('workspace.people.roleDescriptions.member', 'Can view and edit shared files, but cannot manage workspace settings or members.'), icon: 'person' }, ]; const renderRoleOption = ({ option }: { option: any }) => ( {option.label} {option.description} ); const teamOptions = teams.map((team) => ({ value: team.id.toString(), label: team.name, })); if (loading) { return ( {t('workspace.people.loading', 'Loading people...')} ); } return (
{t('workspace.people.title')} {t('workspace.people.description')}
{/* License Information - Compact */} {licenseInfo && ( {licenseInfo.totalUsers} / {licenseInfo.maxAllowedUsers} {t('workspace.people.license.users', 'users')} {licenseInfo.availableSlots === 0 && ( {t('workspace.people.license.noSlotsAvailable', 'No slots available')} )} {licenseInfo.grandfatheredUserCount > 0 && ( {t('workspace.people.license.grandfatheredShort', '{{count}} grandfathered', { count: licenseInfo.grandfatheredUserCount })} )} {licenseInfo.premiumEnabled && licenseInfo.licenseMaxUsers > 0 && ( +{licenseInfo.licenseMaxUsers} {t('workspace.people.license.fromLicense', 'from license')} )} {/* Enterprise Seat Management Button */} {globalLicenseInfo?.licenseType === 'ENTERPRISE' && ( <> )} )} {/* Header Actions */} } value={searchQuery} onChange={(e) => setSearchQuery(e.currentTarget.value)} style={{ maxWidth: 300 }} /> 0)} position="bottom" withArrow > {/* Members Table */} {t('workspace.people.user')} {t('workspace.people.role')} {t('workspace.people.team')} {filteredUsers.length === 0 ? ( {t('workspace.people.noMembersFound')} ) : ( filteredUsers.map((user) => ( {user.username.charAt(0).toUpperCase()} {user.username} {user.email && ( {user.email} )} {(user.rolesAsString || '').includes('ROLE_ADMIN') ? t('workspace.people.admin', 'Admin') : t('workspace.people.member', 'Member')} {user.team?.name ? ( {user.team.name} ) : ( )} {/* Info icon with tooltip */} Authentication: {user.authenticationType || 'Unknown'} Last Activity: {user.lastRequest && new Date(user.lastRequest).getFullYear() >= 1980 ? new Date(user.lastRequest).toLocaleString() :t('never', 'Never')} } multiline w={220} position="left" withArrow zIndex={Z_INDEX_OVER_CONFIG_MODAL + 10} > {/* Actions menu */} } onClick={() => openEditModal(user)} disabled={!loginEnabled} > {t('workspace.people.editRole')} } onClick={() => openChangePasswordModal(user)} disabled={!loginEnabled} > {t('workspace.people.changePassword.action', 'Change password')} : } onClick={() => handleToggleEnabled(user)} disabled={!loginEnabled} > {user.enabled ? t('workspace.people.disable') : t('workspace.people.enable')} } onClick={() => handleDeleteUser(user)} disabled={!loginEnabled}> {t('workspace.people.deleteUser')} )) )}
{/* Invite Members Modal (reusable) */} setInviteModalOpened(false)} onSuccess={fetchData} /> {/* Edit User Modal */} {/* Header with Icon */} {t('workspace.people.editMember.title')} {t('workspace.people.editMember.editing')} {selectedUser?.username} setEditForm({ ...editForm, teamId: value ? parseInt(value) : undefined })} clearable comboboxProps={{ withinPortal: true, zIndex: Z_INDEX_OVER_CONFIG_MODAL }} />
); }