refactor(teams): update user deletion to use user_id instead of id refactor(knowledge): optimize data grid locale handling with useCallback style(knowledge): adjust similarity display format in test chunks refactor(knowledge): improve document selection logic and typing
301 lines
9.3 KiB
TypeScript
301 lines
9.3 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import {
|
|
Box,
|
|
Card,
|
|
CardContent,
|
|
Typography,
|
|
Button,
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableContainer,
|
|
TableHead,
|
|
TableRow,
|
|
Paper,
|
|
Chip,
|
|
IconButton,
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogActions,
|
|
TextField,
|
|
Alert,
|
|
Divider,
|
|
Stack
|
|
} from '@mui/material';
|
|
import {
|
|
PersonAdd as PersonAddIcon,
|
|
Person as PersonIcon,
|
|
Group as GroupIcon,
|
|
Delete as DeleteIcon,
|
|
Check as CheckIcon,
|
|
Close as CloseIcon,
|
|
ExitToApp as ExitToAppIcon
|
|
} from '@mui/icons-material';
|
|
import { useTeamSetting } from '@/hooks/setting-hooks';
|
|
import { useDialog } from '@/hooks/useDialog';
|
|
|
|
interface TenantUser {
|
|
id: string;
|
|
nickname: string;
|
|
email: string;
|
|
role: string;
|
|
update_date: string;
|
|
status: string;
|
|
}
|
|
|
|
interface JoinedTenant {
|
|
tenant_id: string;
|
|
nickname: string;
|
|
email: string;
|
|
role: string;
|
|
update_date: string;
|
|
}
|
|
|
|
const TENANT_ROLES = {
|
|
Owner: 'owner',
|
|
Invite: 'invite',
|
|
Normal: 'normal',
|
|
} as const
|
|
|
|
type TenantRole = (typeof TENANT_ROLES)[keyof typeof TENANT_ROLES]
|
|
|
|
function TeamsSetting() {
|
|
const { t } = useTranslation();
|
|
const {
|
|
userInfo,
|
|
tenantInfo,
|
|
tenantList,
|
|
tenantUsers,
|
|
loading,
|
|
error,
|
|
inviteUser,
|
|
deleteUser,
|
|
agreeTenant,
|
|
quitTenant
|
|
} = useTeamSetting();
|
|
|
|
const [inviteDialogOpen, setInviteDialogOpen] = useState(false);
|
|
const [inviteEmail, setInviteEmail] = useState('');
|
|
|
|
const handleInviteUser = async () => {
|
|
if (!inviteEmail.trim()) return;
|
|
|
|
const result = await inviteUser(inviteEmail);
|
|
if (result?.success) {
|
|
setInviteDialogOpen(false);
|
|
setInviteEmail('');
|
|
}
|
|
};
|
|
|
|
const dialog = useDialog();
|
|
|
|
const handleDeleteUser = async (userId: string) => {
|
|
dialog.confirm({
|
|
content: t('setting.sureDelete'),
|
|
onConfirm: async () => {
|
|
await deleteUser(userId);
|
|
}
|
|
});
|
|
};
|
|
|
|
const handleAgreeTenant = async (tenantId: string) => {
|
|
await agreeTenant(tenantId);
|
|
};
|
|
|
|
const handleQuitTenant = async (tenantId: string) => {
|
|
await quitTenant(tenantId);
|
|
};
|
|
|
|
const formatDate = (dateString: string) => {
|
|
return new Date(dateString).toLocaleString('zh-CN');
|
|
};
|
|
|
|
const getRoleColor = (role: TenantRole) => {
|
|
if (!role) return 'primary';
|
|
// "error" | "primary" | "success" | "info" | "warning" | "secondary" | "default"
|
|
if (role === 'owner') return 'primary';
|
|
if (role === 'normal') return 'success';
|
|
if (role === 'invite') return 'warning';
|
|
return 'primary';
|
|
};
|
|
|
|
return (
|
|
<Box sx={{ p: 3, maxWidth: 1200, mx: 'auto' }}>
|
|
{/* 工作空间卡片 */}
|
|
<Card sx={{ mb: 3 }}>
|
|
<CardContent>
|
|
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
|
<Typography variant="h6" component="div">
|
|
{userInfo?.nickname} {t('setting.workspace')}
|
|
</Typography>
|
|
<Button
|
|
variant="contained"
|
|
startIcon={<PersonAddIcon />}
|
|
onClick={() => setInviteDialogOpen(true)}
|
|
>
|
|
{t('setting.invite')}
|
|
</Button>
|
|
</Stack>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 团队成员表格 */}
|
|
<Card sx={{ mb: 3 }}>
|
|
<CardContent>
|
|
<Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 2 }}>
|
|
<PersonIcon color="primary" />
|
|
<Typography variant="h6">{t('setting.teamMembers')}</Typography>
|
|
</Stack>
|
|
|
|
<TableContainer component={Paper} variant="outlined">
|
|
<Table>
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('common.name')}</TableCell>
|
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('setting.email')}</TableCell>
|
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('setting.role')}</TableCell>
|
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('setting.updateDate')}</TableCell>
|
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('common.action')}</TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{tenantUsers.map((user) => (
|
|
<TableRow key={user.id}>
|
|
<TableCell>{user.nickname}</TableCell>
|
|
<TableCell>{user.email}</TableCell>
|
|
<TableCell>
|
|
<Chip
|
|
label={user.role}
|
|
color={getRoleColor(user.role)}
|
|
size="small"
|
|
/>
|
|
</TableCell>
|
|
<TableCell>{formatDate(user.update_date)}</TableCell>
|
|
<TableCell>
|
|
{user.id !== userInfo?.id && (
|
|
<IconButton
|
|
size="small"
|
|
color="error"
|
|
onClick={() => handleDeleteUser(user.user_id)}
|
|
>
|
|
<DeleteIcon />
|
|
</IconButton>
|
|
)}
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 加入的团队表格 */}
|
|
<Card>
|
|
<CardContent>
|
|
<Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 2 }}>
|
|
<GroupIcon color="primary" />
|
|
<Typography variant="h6">{t('setting.joinedTeams')}</Typography>
|
|
</Stack>
|
|
|
|
<TableContainer component={Paper} variant="outlined">
|
|
<Table>
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('common.name')}</TableCell>
|
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('setting.email')}</TableCell>
|
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('setting.updateDate')}</TableCell>
|
|
<TableCell sx={{ fontWeight: 'bold' }}>{t('common.action')}</TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{tenantList.map((tenant) => (
|
|
<TableRow key={tenant.tenant_id}>
|
|
<TableCell>{tenant.nickname}</TableCell>
|
|
<TableCell>{tenant.email}</TableCell>
|
|
<TableCell>{formatDate(tenant.update_date)}</TableCell>
|
|
<TableCell>
|
|
{tenant.role === TENANT_ROLES.Invite ? (
|
|
<Stack direction="row" spacing={1}>
|
|
<Button
|
|
size="small"
|
|
variant="contained"
|
|
color="success"
|
|
startIcon={<CheckIcon />}
|
|
onClick={() => handleAgreeTenant(tenant.tenant_id)}
|
|
>
|
|
{t('setting.agree')}
|
|
</Button>
|
|
<Button
|
|
size="small"
|
|
variant="outlined"
|
|
color="error"
|
|
startIcon={<CloseIcon />}
|
|
onClick={() => handleQuitTenant(tenant.tenant_id)}
|
|
>
|
|
{t('setting.refuse')}
|
|
</Button>
|
|
</Stack>
|
|
) : (
|
|
tenant.role !== TENANT_ROLES.Owner && (
|
|
<Button
|
|
size="small"
|
|
variant="outlined"
|
|
color="warning"
|
|
startIcon={<ExitToAppIcon />}
|
|
onClick={() => handleQuitTenant(tenant.tenant_id)}
|
|
>
|
|
{t('setting.quit')}
|
|
</Button>
|
|
)
|
|
)}
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* 邀请用户对话框 */}
|
|
<Dialog open={inviteDialogOpen} onClose={() => setInviteDialogOpen(false)} maxWidth="sm" fullWidth>
|
|
<DialogTitle>{t('setting.invite')}</DialogTitle>
|
|
<DialogContent>
|
|
{error && (
|
|
<Alert severity="error" sx={{ mb: 2 }}>
|
|
{error}
|
|
</Alert>
|
|
)}
|
|
<TextField
|
|
autoFocus
|
|
margin="dense"
|
|
label={t('setting.email')}
|
|
type="email"
|
|
fullWidth
|
|
variant="outlined"
|
|
value={inviteEmail}
|
|
onChange={(e) => setInviteEmail(e.target.value)}
|
|
placeholder={t('setting.emailPlaceholder')}
|
|
/>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={() => setInviteDialogOpen(false)}>
|
|
{t('common.cancel')}
|
|
</Button>
|
|
<Button
|
|
onClick={handleInviteUser}
|
|
variant="contained"
|
|
disabled={loading || !inviteEmail.trim()}
|
|
>
|
|
{loading ? t('setting.inviting') : t('setting.invite')}
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
export default TeamsSetting; |