Разработка админ-панели EventHubFrontAdmin v1.0 #1
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Card, Descriptions, Spin, Button, Form, Input, Select, Tag, message, Space, Avatar } from 'antd';
|
||||
import { UserOutlined } from '@ant-design/icons';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import { useUpdateAdmin } from '../../hooks/useAdmins';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const ProfilePage: React.FC = () => {
|
||||
const { user } = useAuthStore();
|
||||
const updateAdmin = useUpdateAdmin();
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
// Проверка, является ли текущий пользователь superadmin
|
||||
const isSuperadmin = user?.role === 'superadmin';
|
||||
|
||||
// Заполняем форму при входе в режим редактирования
|
||||
const startEditing = () => {
|
||||
if (!user) return;
|
||||
const clean = (val: any) => (val === '-' || val === 'undefined' ? undefined : val);
|
||||
form.setFieldsValue({
|
||||
nickname: clean(user.nickname),
|
||||
email: clean(user.email),
|
||||
timezone: clean(user.timezone),
|
||||
language: clean(user.language),
|
||||
phone: clean(user.phone),
|
||||
avatar_url: clean(user.avatar_url),
|
||||
preferences: user.preferences && !isBadValue(user.preferences) ? JSON.stringify(user.preferences) : '',
|
||||
});
|
||||
setEditing(true);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
if (!user || !isSuperadmin) return;
|
||||
form.validateFields().then((values) => {
|
||||
const payload = Object.fromEntries(
|
||||
Object.entries(values).filter(([_, v]) => v !== '' && v !== undefined && v !== null)
|
||||
);
|
||||
if (payload.preferences) {
|
||||
try {
|
||||
payload.preferences = JSON.parse(payload.preferences);
|
||||
} catch {
|
||||
message.error('Поле «Настройки» должно быть валидным JSON');
|
||||
return;
|
||||
}
|
||||
}
|
||||
updateAdmin.mutate(
|
||||
{ id: user.id, data: payload },
|
||||
{
|
||||
onSuccess: () => {
|
||||
message.success('Профиль обновлён');
|
||||
setEditing(false);
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const isBadValue = (val: any) =>
|
||||
val === '-' || val === 'undefined' || val === '' || val === null || val === undefined;
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
if (isBadValue(dateStr)) return '-';
|
||||
const d = dayjs(dateStr);
|
||||
return d.isValid() ? d.format('DD.MM.YYYY HH:mm') : dateStr;
|
||||
};
|
||||
|
||||
if (!user) return <Spin size="large" style={{ display: 'block', margin: '100px auto' }} />;
|
||||
|
||||
return (
|
||||
<Card title="Мой профиль">
|
||||
{!editing ? (
|
||||
<>
|
||||
<div style={{ marginBottom: 16, display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||
<Avatar size={64} icon={<UserOutlined />} src={isBadValue(user.avatar_url) ? undefined : user.avatar_url} />
|
||||
<div>
|
||||
<h3>{!isBadValue(user.nickname) ? user.nickname : user.email}</h3>
|
||||
<Tag
|
||||
color={
|
||||
user.role === 'superadmin'
|
||||
? 'red'
|
||||
: user.role === 'admin'
|
||||
? 'blue'
|
||||
: user.role === 'moderator'
|
||||
? 'purple'
|
||||
: 'cyan'
|
||||
}
|
||||
>
|
||||
{user.role}
|
||||
</Tag>
|
||||
</div>
|
||||
</div>
|
||||
<Descriptions bordered column={1}>
|
||||
<Descriptions.Item label="Email">{user.email}</Descriptions.Item>
|
||||
<Descriptions.Item label="Ник">{isBadValue(user.nickname) ? '-' : user.nickname}</Descriptions.Item>
|
||||
<Descriptions.Item label="Роль">
|
||||
<Tag
|
||||
color={
|
||||
user.role === 'superadmin'
|
||||
? 'red'
|
||||
: user.role === 'admin'
|
||||
? 'blue'
|
||||
: user.role === 'moderator'
|
||||
? 'purple'
|
||||
: 'cyan'
|
||||
}
|
||||
>
|
||||
{user.role}
|
||||
</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Статус">
|
||||
<Tag color={user.status === 'active' ? 'green' : 'red'}>{user.status}</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Часовой пояс">{isBadValue(user.timezone) ? '-' : user.timezone}</Descriptions.Item>
|
||||
<Descriptions.Item label="Язык">{isBadValue(user.language) ? '-' : user.language}</Descriptions.Item>
|
||||
<Descriptions.Item label="Телефон">{isBadValue(user.phone) ? '-' : user.phone}</Descriptions.Item>
|
||||
<Descriptions.Item label="Аватар URL">
|
||||
{isBadValue(user.avatar_url) ? '-' : <a href={user.avatar_url} target="_blank" rel="noreferrer">{user.avatar_url}</a>}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Настройки">
|
||||
{isBadValue(user.preferences) ? '-' : <pre>{JSON.stringify(user.preferences, null, 2)}</pre>}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Последний вход">{formatDate(user.last_login)}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
{isSuperadmin && (
|
||||
<Button type="primary" style={{ marginTop: 16 }} onClick={startEditing}>
|
||||
Редактировать
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Form form={form} layout="vertical" onFinish={handleSave}>
|
||||
<Form.Item label="Email" name="email">
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<Form.Item label="Ник" name="nickname">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Роль" name="role">
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<Form.Item label="Статус" name="status">
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<Form.Item name="timezone" label="Часовой пояс">
|
||||
<Select placeholder="Выберите пояс" allowClear>
|
||||
<Select.Option value="UTC">UTC</Select.Option>
|
||||
<Select.Option value="Europe/Moscow">Europe/Moscow (Москва)</Select.Option>
|
||||
<Select.Option value="Europe/London">Europe/London (Лондон)</Select.Option>
|
||||
<Select.Option value="Europe/Berlin">Europe/Berlin (Берлин)</Select.Option>
|
||||
<Select.Option value="America/New_York">America/New_York (Нью-Йорк)</Select.Option>
|
||||
<Select.Option value="Asia/Tokyo">Asia/Tokyo (Токио)</Select.Option>
|
||||
<Select.Option value="Asia/Shanghai">Asia/Shanghai (Шанхай)</Select.Option>
|
||||
<Select.Option value="Australia/Sydney">Australia/Sydney (Сидней)</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item name="language" label="Язык">
|
||||
<Select placeholder="Выберите язык" allowClear>
|
||||
<Select.Option value="ru">Русский</Select.Option>
|
||||
<Select.Option value="en">English</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item name="phone" label="Телефон">
|
||||
<Input placeholder="+7 (999) 123-45-67" />
|
||||
</Form.Item>
|
||||
<Form.Item name="avatar_url" label="URL аватара">
|
||||
<Input placeholder="https://example.com/avatar.jpg" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="preferences"
|
||||
label="Настройки (JSON)"
|
||||
rules={[
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (value && value.trim().length > 0) {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
} catch {
|
||||
return Promise.reject(new Error('Невалидный JSON'));
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input.TextArea rows={4} placeholder='{"key": "value"}' />
|
||||
</Form.Item>
|
||||
<Space>
|
||||
<Button type="primary" htmlType="submit" loading={updateAdmin.isPending}>
|
||||
Сохранить
|
||||
</Button>
|
||||
<Button onClick={() => setEditing(false)}>Отмена</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfilePage;
|
||||
Reference in New Issue
Block a user