forked from VinokurovVE/tests
mantine
This commit is contained in:
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
10
client/src/pages/TableTest.module.scss
Normal file
10
client/src/pages/TableTest.module.scss
Normal 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;
|
32
client/src/pages/TableTest.tsx
Normal file
32
client/src/pages/TableTest.tsx
Normal 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
|
@ -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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user