VORKOUT-8 #13

Merged
vlad.dev merged 30 commits from VORKOUT-8 into master 2025-07-02 12:23:44 +05:00
7 changed files with 139 additions and 44 deletions
Showing only changes of commit e5dfdc3464 - Show all commits

View File

@ -109,6 +109,11 @@ const api = {
); );
return response.data; return response.data;
}, },
async getUserById(userId: number): Promise<User> {
const response = await base.get<User>(`/account/${userId}`);
return response.data;
},
}; };
export default api; export default api;

View File

@ -8,6 +8,9 @@ interface ContentDrawerProps {
closeDrawer: () => void; closeDrawer: () => void;
children: React.ReactNode; children: React.ReactNode;
type: 'create' | 'edit'; type: 'create' | 'edit';
login?: string;
name?: string;
email?: string;
} }
export default function ContentDrawer({ export default function ContentDrawer({
@ -15,6 +18,9 @@ export default function ContentDrawer({
closeDrawer, closeDrawer,
children, children,
type, type,
login,
name,
email,
}: ContentDrawerProps) { }: ContentDrawerProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const [width, setWidth] = useState<number | string>('30%'); const [width, setWidth] = useState<number | string>('30%');
@ -59,16 +65,18 @@ export default function ContentDrawer({
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flex: 1 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 12, flex: 1 }}>
<Avatar <Avatar
src="https://cdn-icons-png.flaticon.com/512/219/219986.png" src={
login ? `https://gamma.heado.ru/go/ava?name=${login}` : undefined
}
size={40} size={40}
style={{ flexShrink: 0 }} style={{ flexShrink: 0 }}
/> />
<div> <div>
<Typography.Text strong style={{ display: 'block' }}> <Typography.Text strong style={{ display: 'block' }}>
Александр Александров {name}
</Typography.Text> </Typography.Text>
<Typography.Text type="secondary" style={{ fontSize: 14 }}> <Typography.Text type="secondary" style={{ fontSize: 14 }}>
alexandralex@vorkout.ru {email}
</Typography.Text> </Typography.Text>
</div> </div>
</div> </div>
@ -152,7 +160,6 @@ export default function ContentDrawer({
placement="right" placement="right"
open={open} open={open}
width={width} width={width}
destroyOnClose={true}
closable={false} closable={false}
> >
{children} {children}

View File

@ -1,3 +1,4 @@
import { useUserSelector } from '@/store/userStore';
import { Divider, Menu, Tooltip } from 'antd'; import { Divider, Menu, Tooltip } from 'antd';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -13,6 +14,7 @@ export default function SiderMenu({
selectedKey, selectedKey,
hangleMenuClick, hangleMenuClick,
}: SiderMenuProps) { }: SiderMenuProps) {
const user = useUserSelector();
const { t } = useTranslation(); const { t } = useTranslation();
const collapseStyle = collapsed const collapseStyle = collapsed
? { fontSize: '12px' } ? { fontSize: '12px' }
@ -74,15 +76,17 @@ export default function SiderMenu({
label: t('settings'), label: t('settings'),
className: 'no-expand-icon', className: 'no-expand-icon',
children: [ children: [
{ user && (user.role === 'OWNER' || user.role === 'ADMIN')
key: '/accounts', ? {
label: !collapsed ? ( key: '/accounts',
<Tooltip title={t('accounts')}>{t('accounts')}</Tooltip> label: !collapsed ? (
) : ( <Tooltip title={t('accounts')}>{t('accounts')}</Tooltip>
t('accounts') ) : (
), t('accounts')
style: collapseStyle, ),
}, style: collapseStyle,
}
: undefined,
{ {
key: '/events-list', key: '/events-list',
label: !collapsed ? ( label: !collapsed ? (

View File

@ -11,6 +11,7 @@ import {
} from 'antd'; } from 'antd';
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useUserSelector } from '@/store/userStore';
const { Option } = Select; const { Option } = Select;
@ -25,6 +26,7 @@ const getBase64 = (file: FileType): Promise<string> =>
}); });
export default function UserCreate() { export default function UserCreate() {
const user = useUserSelector();
const { t } = useTranslation(); const { t } = useTranslation();
const [previewOpen, setPreviewOpen] = useState(false); const [previewOpen, setPreviewOpen] = useState(false);
const [previewImage, setPreviewImage] = useState(''); const [previewImage, setPreviewImage] = useState('');
@ -187,9 +189,11 @@ export default function UserCreate() {
rules={[{ required: true, message: t('roleMessage') }]} rules={[{ required: true, message: t('roleMessage') }]}
> >
<Select placeholder={t('roleMessage')}> <Select placeholder={t('roleMessage')}>
<Option value="Директор магазина">Директор магазина</Option> {user && user.role === 'OWNER' ? (
<Option value="Менеджер">Менеджер</Option> <Option value="ADMIN">{t('ADMIN')}</Option>
<Option value="Кассир">Кассир</Option> ) : undefined}
<Option value="EDITOR">{t('EDITOR')}</Option>
<Option value="VIEWER">{t('VIEWER')}</Option>
</Select> </Select>
</Form.Item> </Form.Item>
@ -199,10 +203,10 @@ export default function UserCreate() {
rules={[{ required: true, message: t('statusMessage') }]} rules={[{ required: true, message: t('statusMessage') }]}
> >
<Select placeholder={t('statusMessage')}> <Select placeholder={t('statusMessage')}>
<Option value="ACTIVE">Активен</Option> <Option value="ACTIVE">{t('ACTIVE')}</Option>
<Option value="DISABLED">Неактивен</Option> <Option value="DISABLED">{t('DISABLED')}</Option>
<Option value="BLOCKED">Заблокирован</Option> <Option value="BLOCKED">{t('BLOCKED')}</Option>
<Option value="DELETED">Удален</Option> <Option value="DELETED">{t('DELETED')}</Option>
</Select> </Select>
</Form.Item> </Form.Item>

View File

@ -1,25 +1,43 @@
import { UserService } from '@/services/userService';
import { useUserSelector } from '@/store/userStore';
import { User } from '@/types/user';
import { Button, Form, Input, Select } from 'antd'; import { Button, Form, Input, Select } from 'antd';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const { Option } = Select; const { Option } = Select;
export default function UserEdit() { interface UserEditProps {
userId?: number;
}
export default function UserEdit({ userId }: UserEditProps) {
const currentUser = useUserSelector();
const [form] = Form.useForm();
const { t } = useTranslation(); const { t } = useTranslation();
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
async function getUser() {
if (typeof userId === 'undefined') {
return;
}
const user = await UserService.getUserById(userId);
setUser(user);
form.setFieldsValue({ ...user });
}
getUser();
}, []);
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}> <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<Form <Form
form={form}
name="user-edit-form" name="user-edit-form"
layout="vertical" layout="vertical"
// onFinish={onFinish} // onFinish={onFinish}
initialValues={{ initialValues={{ ...user }}
name: 'Александр Александров',
login: 'alexandralex@vorkout.ru',
password: 'jKUUl776GHd',
email: 'alexandralex@vorkout.ru',
tenant: 'text',
role: 'Директор магазина',
status: 'Активен',
}}
style={{ flex: 1, display: 'flex', flexDirection: 'column' }} style={{ flex: 1, display: 'flex', flexDirection: 'column' }}
> >
<Form.Item <Form.Item
@ -71,9 +89,11 @@ export default function UserEdit() {
rules={[{ required: true, message: t('roleMessage') }]} rules={[{ required: true, message: t('roleMessage') }]}
> >
<Select placeholder={t('roleMessage')}> <Select placeholder={t('roleMessage')}>
<Option value="Директор магазина">Директор магазина</Option> {currentUser && currentUser.role === 'OWNER' ? (
<Option value="Менеджер">Менеджер</Option> <Option value="ADMIN">{t('ADMIN')}</Option>
<Option value="Кассир">Кассир</Option> ) : undefined}
<Option value="EDITOR">{t('EDITOR')}</Option>
<Option value="VIEWER">{t('VIEWER')}</Option>
</Select> </Select>
</Form.Item> </Form.Item>
@ -83,10 +103,10 @@ export default function UserEdit() {
rules={[{ required: true, message: t('statusMessage') }]} rules={[{ required: true, message: t('statusMessage') }]}
> >
<Select placeholder={t('statusMessage')}> <Select placeholder={t('statusMessage')}>
<Option value="ACTIVE">Активен</Option> <Option value="ACTIVE">{t('ACTIVE')}</Option>
<Option value="DISABLED">Неактивен</Option> <Option value="DISABLED">{t('DISABLED')}</Option>
<Option value="BLOCKED">Заблокирован</Option> <Option value="BLOCKED">{t('BLOCKED')}</Option>
<Option value="DELETED">Удален</Option> <Option value="DELETED">{t('DELETED')}</Option>
</Select> </Select>
</Form.Item> </Form.Item>

View File

@ -7,13 +7,28 @@ import UserCreate from '@/components/UserCreate';
import { Avatar, Table } from 'antd'; import { Avatar, Table } from 'antd';
import { TableProps } from 'antd/lib'; import { TableProps } from 'antd/lib';
import { UserService } from '@/services/userService'; import { UserService } from '@/services/userService';
import UserEdit from '@/components/UserEdit';
export default function AccountsPage() { export default function AccountsPage() {
const { t } = useTranslation(); const { t } = useTranslation();
const [open, setOpen] = useState(false); const [openCreate, setOpenCreate] = useState(false);
const showDrawer = () => setOpen(true); const [activeAccount, setActiveAccount] = useState<
const closeDrawer = () => setOpen(false); { login: string; id: number; name: string; email: string } | undefined
>(undefined);
const showCreateDrawer = () => setOpenCreate(true);
const closeCreateDrawer = () => {
setActiveAccount(undefined);
setOpenCreate(false);
};
const [openEdit, setOpenEdit] = useState(false);
const showEditDrawer = () => setOpenEdit(true);
const closeEditDrawer = () => {
setActiveAccount(undefined);
setOpenEdit(false);
};
const [accounts, setAccounts] = useState<AllUserResponse>({ const [accounts, setAccounts] = useState<AllUserResponse>({
amountCount: 0, amountCount: 0,
@ -49,7 +64,23 @@ export default function AccountsPage() {
dataIndex: 'nameLogin', dataIndex: 'nameLogin',
key: 'nameLogin', key: 'nameLogin',
render: (text, record) => ( render: (text, record) => (
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}> <div
onClick={() => {
setActiveAccount({
login: record.login,
id: record.id,
name: record.name,
email: record.email || '',
});
showEditDrawer();
}}
style={{
display: 'flex',
alignItems: 'center',
gap: '16px',
cursor: 'pointer',
}}
>
<div <div
style={{ style={{
height: '32px', height: '32px',
@ -124,7 +155,7 @@ export default function AccountsPage() {
width: '18px', width: '18px',
cursor: 'pointer', cursor: 'pointer',
}} }}
onClick={showDrawer} onClick={showCreateDrawer}
/> />
} }
/> />
@ -140,9 +171,23 @@ export default function AccountsPage() {
rowKey={'id'} rowKey={'id'}
/> />
<ContentDrawer open={open} closeDrawer={closeDrawer} type="create"> <ContentDrawer
open={openCreate}
closeDrawer={closeCreateDrawer}
type="create"
>
<UserCreate /> <UserCreate />
</ContentDrawer> </ContentDrawer>
<ContentDrawer
login={activeAccount?.login}
name={activeAccount?.name}
email={activeAccount?.email}
open={openEdit}
closeDrawer={closeEditDrawer}
type="edit"
>
<UserEdit userId={activeAccount?.id} />
</ContentDrawer>
</> </>
); );
} }

View File

@ -9,8 +9,18 @@ export class UserService {
return user; return user;
} }
static async getUsers(page: number = 1, limit: number = 10): Promise<AllUserResponse> { static async getUsers(
page: number = 1,
limit: number = 10
): Promise<AllUserResponse> {
console.log('getUsers');
const allUsers = api.getUsers(page, limit); const allUsers = api.getUsers(page, limit);
return allUsers; return allUsers;
} }
static async getUserById(userId: number): Promise<User> {
console.log('getUserById');
const user = api.getUserById(userId);
return user;
}
} }