This commit is contained in:
cracklesparkle
2024-10-09 16:51:37 +09:00
parent b88d83cd74
commit 974fc12b34
32 changed files with 3456 additions and 1671 deletions

View File

@ -1,8 +1,7 @@
import { Box, Typography } from '@mui/material'
import { DataGrid, GridColDef } from '@mui/x-data-grid'
import { GridColDef } from '@mui/x-data-grid'
import { useEffect, useState } from 'react'
import { IBoiler } from '../interfaces/fuel'
import { useBoilers } from '../hooks/swrHooks'
import { Badge, Flex, Table, Text } from '@mantine/core'
function Boilers() {
const [boilersPage, setBoilersPage] = useState(1)
@ -26,7 +25,7 @@ function Boilers() {
}, [])
const boilersColumns: GridColDef[] = [
{ field: 'id', headerName: 'ID', type: "number" },
{ field: 'id_object', headerName: 'ID', type: "number" },
{ field: 'boiler_name', headerName: 'Название', type: "string", flex: 1 },
{ field: 'boiler_code', headerName: 'Код', type: "string", flex: 1 },
{ field: 'id_city', headerName: 'Город', type: "string", flex: 1 },
@ -34,23 +33,52 @@ function Boilers() {
]
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%' }}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%', p: '16px' }}>
<Typography variant='h6' fontWeight='600'>
Котельные
</Typography>
<Flex direction='column' gap='sm' p='sm'>
<Text size="xl" fw={600}>
Котельные
</Text>
{boilers &&
<DataGrid
rows={boilers.map((boiler: IBoiler) => {
return { ...boiler, id: boiler.id_object }
})}
columns={boilersColumns}
/>
}
{boilers &&
<Table highlightOnHover>
<Table.Thead>
<Table.Tr>
{boilersColumns.map(column => (
<Table.Th key={column.field}>{column.headerName}</Table.Th>
))}
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{boilers.map((boiler: any) => (
<Table.Tr key={boiler.id_object}>
{boilersColumns.map(column => {
if (column.field === 'activity') {
return (
boiler.activity ? (
<Table.Td key={`${boiler.id_object}-${boiler[column.field]}`}>
<Badge fullWidth variant="light">
Активен
</Badge>
</Table.Td>
</Box>
</Box>
) : (
<Table.Td key={`${boiler.id_object}-${boiler[column.field]}`}>
<Badge color="gray" fullWidth variant="light">
Отключен
</Badge>
</Table.Td>
)
)
}
else return (
<Table.Td key={`${boiler.id_object}-${boiler[column.field]}`}>{boiler[column.field]}</Table.Td>
)
})}
</Table.Tr>
))}
</Table.Tbody>
</Table>
}
</Flex>
)
}

View File

@ -1,15 +1,54 @@
import { Box, Card, Typography } from "@mui/material";
import { Card, Flex, SimpleGrid, Text } from "@mantine/core";
import { IconBuildingFactory2, IconFiles, IconMap, IconReport, IconServer, IconShield, IconUsers } from "@tabler/icons-react";
import { ReactNode } from "react";
import { useNavigate } from "react-router-dom";
export default function Main() {
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', p: '16px' }}>
<Typography variant='h6' fontWeight='700'>
Последние файлы
</Typography>
const navigate = useNavigate()
<Card>
interface CustomCardProps {
link: string;
icon: ReactNode;
label: string;
}
const CustomCard = ({
link,
icon,
label
}: CustomCardProps) => {
return (
<Card
onClick={() => navigate(link)}
withBorder
style={{ cursor: 'pointer', userSelect: 'none' }}
>
<Flex mih='50'>
{icon}
</Flex>
<Text fw={500} size="lg" mt="md">
{label}
</Text>
</Card>
</Box>
)
}
return (
<Flex direction='column' gap='sm' p='sm'>
<Text size="xl" fw={700}>
Главная
</Text>
<SimpleGrid cols={{ xs: 1, md: 3 }}>
<CustomCard link="/user" icon={<IconUsers size='50' color="#6495ED" />} label="Пользователи" />
<CustomCard link="/role" icon={<IconShield size='50' color="#6495ED" />} label="Роли" />
<CustomCard link="/documents" icon={<IconFiles size='50' color="#6495ED" />} label="Документы" />
<CustomCard link="/reports" icon={<IconReport size='50' color="#6495ED" />} label="Отчеты" />
<CustomCard link="/servers" icon={<IconServer size='50' color="#6495ED" />} label="Серверы" />
<CustomCard link="/boilers" icon={<IconBuildingFactory2 size='50' color="#6495ED" />} label="Котельные" />
<CustomCard link="/map-test" icon={<IconMap size='50' color="#6495ED" />} label="ИКС" />
</SimpleGrid>
</Flex>
)
}

View File

@ -1,13 +1,16 @@
import { Error } from "@mui/icons-material";
import { Box, Typography } from "@mui/material";
import { Flex, Text } from "@mantine/core";
import { IconError404 } from "@tabler/icons-react";
export default function NotFound() {
return (
<>
<Box sx={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
<Error />
<Typography>Запрашиваемая страница не найдена.</Typography>
</Box>
</>
<Flex p='sm' gap='sm' align='center' justify='center'>
<Flex direction='column' gap='sm' align='center'>
<IconError404 size={100} />
<Text size="xl" fw={500} ta='center'>
Запрашиваемая страница не найдена.
</Text>
</Flex>
</Flex>
)
}

View File

@ -1,26 +1,25 @@
import { Fragment, useEffect, useState } from "react"
import { Autocomplete, Box, Button, CircularProgress, IconButton, TextField } from "@mui/material"
import { DataGrid } from "@mui/x-data-grid"
import { useEffect, useState } from "react"
import { useCities, useReport, useReportExport } from "../hooks/swrHooks"
import { useDebounce } from "@uidotdev/usehooks"
import { ICity } from "../interfaces/fuel"
import { Update } from "@mui/icons-material"
import { mutate } from "swr"
import { ActionIcon, Autocomplete, Badge, Button, CloseButton, Flex, Table } from "@mantine/core"
import { IconRefresh } from "@tabler/icons-react"
export default function Reports() {
const [download, setDownload] = useState(false)
const [search, setSearch] = useState<string | null>("")
const [search, setSearch] = useState<string | undefined>("")
const debouncedSearch = useDebounce(search, 500)
const [selectedOption, setSelectedOption] = useState<ICity | null>(null)
const { cities, isLoading } = useCities(10, 1, debouncedSearch)
const [selectedOption, setSelectedOption] = useState<number | null>(null)
const { cities } = useCities(10, 1, debouncedSearch)
const { report, isLoading: reportLoading } = useReport(selectedOption?.id)
const { report } = useReport(selectedOption)
const { reportExported } = useReportExport(selectedOption?.id, download)
const { reportExported } = useReportExport(selectedOption, download)
const refreshReport = async () => {
mutate(`/info/reports/${selectedOption?.id}?to_export=false`)
mutate(`/info/reports/${selectedOption}?to_export=false`)
}
useEffect(() => {
@ -41,9 +40,32 @@ export default function Reports() {
}
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', p: '16px' }}>
<Box sx={{ display: 'flex', gap: '16px' }}>
<Flex direction='column' gap='sm' p='sm'>
<Flex component="form" gap={'sm'}>
{/* <SearchableSelect /> */}
<Autocomplete
placeholder="Населенный пункт"
flex={'1'}
data={cities ? cities.map((item: ICity) => ({ label: item.name, value: item.id.toString() })) : []}
onSelect={(e) => console.log(e.currentTarget.value)}
onChange={(value) => setSearch(value)}
onOptionSubmit={(value) => setSelectedOption(Number(value))}
rightSection={
search !== '' && (
<CloseButton
size="sm"
onMouseDown={(event) => event.preventDefault()}
onClick={() => {
setSearch('')
setSelectedOption(null)
}}
aria-label="Clear value"
/>
)
}
value={search}
/>
{/* <Autocomplete
fullWidth
onInputChange={(_, value) => setSearch(value)}
onChange={(_, value) => setSelectedOption(value)}
@ -68,18 +90,78 @@ export default function Reports() {
}}
/>
)}
/>
/> */}
<IconButton onClick={() => refreshReport()}>
<Update />
</IconButton>
<ActionIcon size='auto' variant='transparent' onClick={() => refreshReport()}>
<IconRefresh />
</ActionIcon>
<Button onClick={() => exportReport()}>
<Button disabled={!selectedOption} onClick={() => exportReport()}>
Экспорт
</Button>
</Box>
</Flex>
<DataGrid
{report &&
<Table highlightOnHover>
<Table.Thead>
<Table.Tr>
{[
{ field: 'id', headerName: '№', width: 70 },
...Object.keys(report).map(key => ({
field: key,
headerName: key.charAt(0).toUpperCase() + key.slice(1),
width: 150
}))
].map(column => (
<Table.Th key={column.headerName}>{column.headerName}</Table.Th>
))}
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{[...new Set(Object.keys(report).flatMap(key => Object.keys(report[key])))].map(id => {
const row: any = { id: Number(id) };
Object.keys(report).forEach(key => {
row[key] = report[key][id];
});
return (<Table.Tr key={row.id}>
{[
{ field: 'id', headerName: '№', width: 70 },
...Object.keys(report).map(key => ({
field: key,
headerName: key.charAt(0).toUpperCase() + key.slice(1),
width: 150
}))
].map(column => {
if (column.field === 'Активность') {
return (
row['Активность'] ? (
<Table.Td key={`${row.id}-${column.headerName}`}>
<Badge fullWidth variant="light">
Активен
</Badge>
</Table.Td>
) : (
<Table.Td key={`${row.id}-${column.headerName}`}>
<Badge color="gray" fullWidth variant="light">
Отключен
</Badge>
</Table.Td>
)
)
}
return (
<Table.Td key={`${row.id}-${column.headerName}`}>{row[column.field]}</Table.Td>
)
})}
</Table.Tr>)
})}
</Table.Tbody>
</Table>
}
{/* <DataGrid
autoHeight
style={{ width: "100%" }}
loading={reportLoading}
@ -118,7 +200,7 @@ export default function Reports() {
onProcessRowUpdateError={() => {
}}
/>
</Box>
/> */}
</Flex>
)
}

View File

@ -1,15 +1,15 @@
import { useState } from 'react'
import { Box, Button, CircularProgress, Modal } from '@mui/material'
import { DataGrid, GridColDef } from '@mui/x-data-grid'
import { GridColDef } from '@mui/x-data-grid'
import { useRoles } from '../hooks/swrHooks'
import { CreateField } from '../interfaces/create'
import RoleService from '../services/RoleService'
import FormFields from '../components/FormFields'
import { Button, Flex, Loader, Modal, Table } from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
export default function Roles() {
const { roles, isError, isLoading } = useRoles()
const [open, setOpen] = useState(false)
const [opened, { open, close }] = useDisclosure(false);
const createFields: CreateField[] = [
{ key: 'name', headerName: 'Название', type: 'string', required: true, defaultValue: '' },
@ -23,43 +23,42 @@ export default function Roles() {
];
if (isError) return <div>Произошла ошибка при получении данных.</div>
if (isLoading) return <CircularProgress />
if (isLoading) return <Loader />
return (
<Box sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
gap: '16px',
flexGrow: 1,
p: '16px'
}}>
<Button onClick={() => setOpen(true)}>
<Flex direction='column' align='flex-start' gap='sm' p='sm'>
<Button onClick={open}>
Добавить роль
</Button>
<Modal
open={open}
onClose={() => setOpen(false)}
>
<Modal opened={opened} onClose={close} title="Создание роли" centered>
<FormFields
sx={{
position: 'absolute' as 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
boxShadow: 24,
p: 4,
}}
fields={createFields}
submitHandler={RoleService.createRole}
title="Создание роли"
/>
</Modal>
<DataGrid
<Table highlightOnHover>
<Table.Thead>
<Table.Tr>
{columns.map(column => (
<Table.Th key={column.field}>{column.headerName}</Table.Th>
))}
</Table.Tr>
</Table.Thead>
<Table.Tbody>{roles.map((role: any) => (
<Table.Tr
key={role.id}
//bg={selectedRows.includes(element.position) ? 'var(--mantine-color-blue-light)' : undefined}
>
{columns.map(column => (
<Table.Td key={column.field}>{role[column.field]}</Table.Td>
))}
</Table.Tr>
))}</Table.Tbody>
</Table>
{/* <DataGrid
autoHeight
style={{ width: "100%" }}
rows={roles}
@ -78,7 +77,7 @@ export default function Roles() {
onProcessRowUpdateError={() => {
}}
/>
</Box>
/> */}
</Flex>
)
}

View File

@ -1,67 +1,41 @@
import { Box, Tab, Tabs } from "@mui/material"
import { useState } from "react"
import ServersView from "../components/ServersView"
import ServerIpsView from "../components/ServerIpsView"
import ServerHardware from "../components/ServerHardware"
import ServerStorage from "../components/ServerStorages"
import { Flex, Tabs } from "@mantine/core"
export default function Servers() {
const [currentTab, setCurrentTab] = useState(0)
const handleTabChange = (newValue: number) => {
setCurrentTab(newValue);
}
interface TabPanelProps {
children?: React.ReactNode;
index: number;
value: number;
}
function CustomTabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && <Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>{children}</Box>}
</div>
);
}
const [currentTab, setCurrentTab] = useState<string | null>('0')
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '16px', height: '100%', p: '16px' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs value={currentTab} onChange={(_, value) =>
handleTabChange(value)
} aria-label="basic tabs example">
<Tab label="Серверы" />
<Tab label="IP-адреса" />
<Tab label="Hardware" />
<Tab label="Storages" />
<Flex gap='sm' p='sm' direction='column'>
<Flex gap='sm' direction='column'>
<Tabs value={currentTab} onChange={setCurrentTab}>
<Tabs.List>
<Tabs.Tab value="0">Серверы</Tabs.Tab>
<Tabs.Tab value="1">IP-адреса</Tabs.Tab>
<Tabs.Tab value="3">Hardware</Tabs.Tab>
<Tabs.Tab value="4">Storages</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="0" pt='sm'>
<ServersView />
</Tabs.Panel>
<Tabs.Panel value="1" pt='sm'>
<ServerIpsView />
</Tabs.Panel>
<Tabs.Panel value="2" pt='sm'>
<ServerHardware />
</Tabs.Panel>
<Tabs.Panel value="3" pt='sm'>
<ServerStorage />
</Tabs.Panel>
</Tabs>
</Box>
<CustomTabPanel value={currentTab} index={0}>
<ServersView />
</CustomTabPanel>
<CustomTabPanel value={currentTab} index={1}>
<ServerIpsView />
</CustomTabPanel>
<CustomTabPanel value={currentTab} index={2}>
<ServerHardware />
</CustomTabPanel>
<CustomTabPanel value={currentTab} index={3}>
<ServerStorage />
</CustomTabPanel>
</Flex>
{/* <BarChart
xAxis={[{ scaleType: 'band', data: ['group A', 'group B', 'group C'] }]}
@ -69,6 +43,6 @@ export default function Servers() {
width={500}
height={300}
/> */}
</Box>
</Flex>
)
}

View File

@ -1,4 +1,3 @@
import { Box, Stack } from "@mui/material"
import UserService from "../services/UserService"
import { setUserData, useAuthStore } from "../store/auth"
import { useEffect, useState } from "react"
@ -6,6 +5,7 @@ import { CreateField } from "../interfaces/create"
import { IUser } from "../interfaces/user"
import FormFields from "../components/FormFields"
import AuthService from "../services/AuthService"
import { Flex } from "@mantine/core"
export default function Settings() {
const { token } = useAuthStore()
@ -39,37 +39,31 @@ export default function Settings() {
]
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
gap: "16px",
}}
<Flex
direction='column'
align='flex-start'
gap='sm'
p='sm'
>
{currentUser &&
<Stack spacing={2} width='100%'>
<Stack width='100%'>
<FormFields
fields={profileFields}
defaultValues={currentUser}
mutateHandler={(data: any) => {
setUserData(data)
}}
submitHandler={(data) => UserService.updateUser({ id: currentUser.id, ...data })}
title="Пользователь"
/>
</Stack>
<Flex direction='column' gap='sm' w='100%'>
<FormFields
fields={profileFields}
defaultValues={currentUser}
mutateHandler={(data: any) => {
setUserData(data)
}}
submitHandler={(data) => UserService.updateUser({ id: currentUser.id, ...data })}
title="Пользователь"
/>
<Stack width='100%'>
<FormFields
fields={passwordFields}
submitHandler={(data) => AuthService.updatePassword({ id: currentUser.id, ...data })}
title="Смена пароля"
/>
</Stack>
</Stack>
<FormFields
fields={passwordFields}
submitHandler={(data) => AuthService.updatePassword({ id: currentUser.id, ...data })}
title="Смена пароля"
/>
</Flex>
}
</Box>
</Flex>
)
}

View File

@ -0,0 +1,10 @@
$ka-background-color: #2c2c2c;
$ka-border-color: #4d4d4d;
$ka-cell-hover-background-color: transparentize(#fff, 0.8);
$ka-color-base: #fefefe;
$ka-input-background-color: $ka-background-color;
$ka-input-border-color: $ka-border-color;
$ka-input-color: $ka-color-base;
$ka-row-hover-background-color: transparentize(#fff, 0.9);
$ka-thead-background-color: #1b1b1b;
$ka-thead-color: #c5c5c5;

View File

@ -0,0 +1,32 @@
import React from 'react'
import { Table, DataType } from 'ka-table'
import 'ka-table/style.css';
import { Flex } from '@mantine/core';
import styles from './TableTest.module.scss'
function TableTest() {
return (
<Flex direction='column' align='flex-start' gap='sm' p='sm'>
<Table
columns={[
{ key: 'column1', title: 'Column 1', dataType: DataType.String },
{ key: 'column2', title: 'Column 2', dataType: DataType.String },
{ key: 'column3', title: 'Column 3', dataType: DataType.String },
{ key: 'column4', title: 'Column 4', dataType: DataType.String },
]}
data={Array(100).fill(undefined).map(
(_, index) => ({
column1: `column:1 row:${index}`,
column2: `column:2 row:${index}`,
column3: `column:3 row:${index}`,
column4: `column:4 row:${index}`,
id: index,
}),
)}
rowKeyField={'id'}
/>
</Flex>
)
}
export default TableTest

View File

@ -1,18 +1,27 @@
import { Box, Button, CircularProgress, Modal } from "@mui/material"
import { DataGrid, GridColDef } from "@mui/x-data-grid"
import { GridColDef } from "@mui/x-data-grid"
import { useRoles, useUsers } from "../hooks/swrHooks"
import { IRole } from "../interfaces/role"
import { useState } from "react"
import { useEffect, useState } from "react"
import { CreateField } from "../interfaces/create"
import UserService from "../services/UserService"
import FormFields from "../components/FormFields"
import { Badge, Button, Flex, Loader, Modal, Pagination, Select, Table } from "@mantine/core"
import { useDisclosure } from "@mantine/hooks"
export default function Users() {
const { users, isError, isLoading } = useUsers()
const { roles } = useRoles()
const [open, setOpen] = useState(false)
const [roleOptions, setRoleOptions] = useState<any>()
useEffect(() => {
if (Array.isArray(roles)) {
setRoleOptions(roles.map((role: IRole) => ({ label: role.name, value: role.id.toString() })))
}
}, [roles])
const [opened, { open, close }] = useDisclosure(false);
const createFields: CreateField[] = [
{ key: 'email', headerName: 'E-mail', type: 'string', required: true, defaultValue: '' },
@ -30,7 +39,7 @@ export default function Users() {
{ field: 'phone', headerName: 'Телефон', flex: 1, editable: true },
{ field: 'name', headerName: 'Имя', flex: 1, editable: true },
{ field: 'surname', headerName: 'Фамилия', flex: 1, editable: true },
{ field: 'is_active', headerName: 'Активен', type: "boolean", flex: 1, editable: true },
{ field: 'is_active', headerName: 'Статус', type: "boolean", flex: 1, editable: true },
{
field: 'role_id',
headerName: 'Роль',
@ -41,44 +50,92 @@ export default function Users() {
},
];
if (isError) return <div>Произошла ошибка при получении данных.</div>
if (isLoading) return <CircularProgress />
if (isError) return (
<div>
Произошла ошибка при получении данных.
</div>
)
if (isLoading) {
return (
<Flex direction='column' align='flex-start' gap='sm' p='sm'>
<Loader />
</Flex>
)
}
return (
<Box sx={{
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
gap: "16px",
p: '16px'
}}
>
<Button onClick={() => setOpen(true)}>
<Flex direction='column' align='flex-start' gap='sm' p='sm'>
<Button onClick={open}>
Добавить пользователя
</Button>
<Modal
open={open}
onClose={() => setOpen(false)}
>
<Modal opened={opened} onClose={close} title="Регистрация пользователя" centered>
<FormFields
fields={createFields}
submitHandler={UserService.createUser}
title="Создание пользователя"
sx={{
position: 'absolute' as 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
boxShadow: 24,
p: 4,
}}
/>
</Modal>
<DataGrid
{Array.isArray(roleOptions) &&
<Table highlightOnHover>
<Table.Thead>
<Table.Tr>
{columns.map(column => (
<Table.Th key={column.field}>{column.headerName}</Table.Th>
))}
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{users.map((user: any) => (
<Table.Tr
key={user.id}
//bg={selectedRows.includes(element.position) ? 'var(--mantine-color-blue-light)' : undefined}
>
{columns.map(column => {
if (column.field === 'is_active') {
return (
user.is_active ? (
<Table.Td key={column.field}>
<Badge fullWidth variant="light">
Активен
</Badge>
</Table.Td>
) : (
<Table.Td key={column.field}>
<Badge color="gray" fullWidth variant="light">
Отключен
</Badge>
</Table.Td>
)
)
}
else if (column.field === 'role_id') {
return (
<Table.Td key={column.field}>
<Select
data={roleOptions}
defaultValue={user.role_id.toString()}
variant="unstyled"
allowDeselect={false}
/>
</Table.Td>
)
}
else return (
<Table.Td key={column.field}>{user[column.field]}</Table.Td>
)
})}
</Table.Tr>
))}
</Table.Tbody>
</Table>
}
<Pagination total={10} />
{/* <DataGrid
density="compact"
autoHeight
style={{ width: "100%" }}
@ -99,7 +156,7 @@ export default function Users() {
onProcessRowUpdateError={() => {
}}
/>
</Box>
/> */}
</Flex>
)
}

View File

@ -1,8 +1,9 @@
import { Box, Button, CircularProgress, Container, Fade, Grow, Stack, TextField, Typography } from '@mui/material'
import { CircularProgress, Fade, Grow } from '@mui/material'
import { useState } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form';
import AuthService from '../../services/AuthService';
import { CheckCircle } from '@mui/icons-material';
import { Button, Flex, Paper, Text, TextInput } from '@mantine/core';
interface PasswordResetProps {
email: string;
@ -31,61 +32,58 @@ function PasswordReset() {
}
return (
<Container maxWidth="sm">
<Box my={4}>
<Typography variant="h4" component="h1" gutterBottom>
<Paper flex={1} maw='500' withBorder radius='md' p='xl'>
<Flex direction='column' gap='sm'>
<Text size="xl" fw={500}>
Восстановление пароля
</Typography>
</Text>
<form onSubmit={handleSubmit(onSubmit)}>
{!success && <Fade in={!success}>
<Stack spacing={2}>
<Typography>
<Flex direction='column' gap={'md'}>
<Text>
Введите адрес электронной почты, на который будут отправлены новые данные для авторизации:
</Typography>
</Text>
<TextField
fullWidth
margin="normal"
label="E-mail"
<TextInput
label='E-mail'
required
{...register('email', { required: 'Введите E-mail' })}
error={!!errors.email}
helperText={errors.email?.message}
error={errors.email?.message}
/>
<Box sx={{ display: 'flex', gap: '16px' }}>
<Button fullWidth type="submit" disabled={isSubmitting || watch('email').length == 0} variant="contained" color="primary">
<Flex gap='sm'>
<Button flex={1} type="submit" disabled={isSubmitting || watch('email').length == 0} variant='filled'>
{isSubmitting ? <CircularProgress size={16} /> : 'Восстановить пароль'}
</Button>
<Button fullWidth href="/auth/signin" type="button" variant="text" color="primary">
<Button flex={1} component='a' href="/auth/signin" type="button" variant='light'>
Назад
</Button>
</Box>
</Flex>
</Stack>
</Flex>
</Fade>}
{success &&
<Grow in={success}>
<Stack spacing={2}>
<Stack direction='row' alignItems='center' spacing={2}>
<Flex direction='column' gap='sm'>
<Flex align='center' gap='sm'>
<CheckCircle color='success' />
<Typography>
<Text>
На указанный адрес было отправлено письмо с новыми данными для авторизации.
</Typography>
</Stack>
<Box sx={{ display: 'flex', gap: '16px' }}>
<Button fullWidth href="/auth/signin" type="button" variant="contained" color="primary">
</Text>
</Flex>
<Flex gap='sm'>
<Button component='a' href="/auth/signin" type="button">
Войти
</Button>
</Box>
</Stack>
</Flex>
</Flex>
</Grow>
}
</form>
</Box>
</Container>
</Flex>
</Paper>
)
}

View File

@ -1,14 +1,14 @@
import { useForm, SubmitHandler } from 'react-hook-form';
import { TextField, Button, Container, Typography, Box, Stack, Link, CircularProgress } from '@mui/material';
import { AxiosResponse } from 'axios';
import { ApiResponse, LoginFormData } from '../../interfaces/auth';
import { login, setUserData } from '../../store/auth';
import { useNavigate } from 'react-router-dom';
import AuthService from '../../services/AuthService';
import UserService from '../../services/UserService';
import { Button, Flex, Loader, Paper, Text, TextInput } from '@mantine/core';
const SignIn = () => {
const { register, handleSubmit, setError, formState: { errors, isSubmitting } } = useForm<LoginFormData>({
const { register, handleSubmit, setError, formState: { errors, isSubmitting, isValid } } = useForm<LoginFormData>({
defaultValues: {
username: '',
password: '',
@ -47,54 +47,48 @@ const SignIn = () => {
};
return (
<Container maxWidth="sm">
<Box my={4}>
<Typography variant="h4" component="h1" gutterBottom>
<Paper flex={1} maw='500' withBorder radius='md' p='xl'>
<Flex direction='column' gap='sm'>
<Text size="xl" fw={500}>
Вход
</Typography>
</Text>
<form onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={2}>
<TextField
fullWidth
margin="normal"
label="Логин"
<Flex direction='column' gap='sm'>
<TextInput
label='Логин'
required
{...register('username', { required: 'Введите логин' })}
error={!!errors.username}
helperText={errors.username?.message}
error={errors.username?.message}
/>
<TextField
fullWidth
margin="normal"
type="password"
label="Пароль"
<TextInput
label='Пароль'
type='password'
required
{...register('password', { required: 'Введите пароль' })}
error={!!errors.password}
helperText={errors.password?.message}
error={errors.password?.message}
/>
<Box sx={{ display: 'flex', gap: '16px', justifyContent: 'flex-end' }}>
<Link href="/auth/password-reset" color="primary">
<Flex justify='flex-end' gap='sm'>
<Button component='a' href='/auth/password-reset' variant='transparent'>
Восстановить пароль
</Link>
</Box>
</Button>
</Flex>
<Box sx={{ display: 'flex', gap: '16px' }}>
<Button fullWidth type="submit" variant="contained" color="primary">
{isSubmitting ? <CircularProgress size={16} /> : 'Вход'}
<Flex gap='sm'>
<Button disabled={!isValid} type="submit" flex={1} variant='filled'>
{isSubmitting ? <Loader size={16} /> : 'Вход'}
</Button>
{/* <Button fullWidth href="/auth/signup" type="button" variant="text" color="primary">
{/* <Button component='a' flex={1} href='/auth/signup' type="button" variant='light'>
Регистрация
</Button> */}
</Box>
</Stack>
</Flex>
</Flex>
</form>
</Box>
</Container>
</Flex>
</Paper>
);
};