Removed mantine libraries; Removed mandatory authentication
This commit is contained in:
@ -1,17 +1,30 @@
|
||||
import { BrowserRouter as Router, Route, Routes, Navigate } from "react-router-dom"
|
||||
import { BrowserRouter as Router, Route, Routes } from "react-router-dom"
|
||||
//import { Navigate } from "react-router-dom"
|
||||
import NotFound from "./pages/NotFound"
|
||||
import MainLayout from "./layouts/MainLayout"
|
||||
import { initAuth, useAuthStore } from "./store/auth"
|
||||
import { useEffect, useState } from "react"
|
||||
import DashboardLayout from "./layouts/DashboardLayout"
|
||||
import { pages } from "./constants/app"
|
||||
import { Spinner } from "@fluentui/react-components"
|
||||
import { FluentProvider, Spinner, webDarkTheme, webLightTheme } from "@fluentui/react-components"
|
||||
import { setColorScheme, useAppStore } from "./store/app"
|
||||
|
||||
function App() {
|
||||
const auth = useAuthStore()
|
||||
const { colorScheme } = useAppStore()
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const localColorScheme = localStorage.getItem('colorScheme');
|
||||
|
||||
if (localColorScheme === 'light') {
|
||||
setColorScheme('light')
|
||||
} else if (localColorScheme === 'dark') {
|
||||
setColorScheme('dark')
|
||||
} else if (localColorScheme === 'auto') {
|
||||
setColorScheme('auto')
|
||||
}
|
||||
|
||||
initAuth()
|
||||
}, [])
|
||||
|
||||
@ -28,27 +41,30 @@ function App() {
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div style={{
|
||||
width: '100%',
|
||||
height: '100vh'
|
||||
}}>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route element={<MainLayout />}>
|
||||
{pages.filter((page) => !page.dashboard).filter((page) => page.enabled).map((page, index) => (
|
||||
<Route key={`ml-${index}`} path={page.path} element={page.component} />
|
||||
))}
|
||||
</Route>
|
||||
<FluentProvider theme={colorScheme === 'light' ? webLightTheme : webDarkTheme}>
|
||||
<div style={{
|
||||
width: '100%',
|
||||
height: '100vh'
|
||||
}}>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route element={<MainLayout />}>
|
||||
{pages.filter((page) => !page.dashboard).filter((page) => page.enabled).map((page, index) => (
|
||||
<Route key={`ml-${index}`} path={page.path} element={page.component} />
|
||||
))}
|
||||
</Route>
|
||||
|
||||
<Route element={auth.isAuthenticated ? <DashboardLayout></DashboardLayout> : <Navigate to={"/auth/signin"} />}>
|
||||
{pages.filter((page) => page.dashboard).filter((page) => page.enabled).map((page, index) => (
|
||||
<Route key={`dl-${index}`} path={page.path} element={page.component} />
|
||||
))}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
</div>
|
||||
{/* <Route element={auth.isAuthenticated || pages.find(page => page.path === '/auth/signin')?.enabled === false ? <DashboardLayout></DashboardLayout> : <Navigate to={"/auth/signin"} />}> */}
|
||||
<Route element={<DashboardLayout></DashboardLayout>}>
|
||||
{pages.filter((page) => page.dashboard).filter((page) => page.enabled).map((page, index) => (
|
||||
<Route key={`dl-${index}`} path={page.path} element={page.component} />
|
||||
))}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
</div>
|
||||
</FluentProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ type CustomTableProps<T> = {
|
||||
createFields?: CreateField[];
|
||||
submitHandler?: (data: T) => Promise<AxiosResponse>
|
||||
onEditCell?: (rowId: number, columnId: string, value: any) => any
|
||||
searchable?: boolean
|
||||
}
|
||||
|
||||
const CustomTable = <T extends object>({
|
||||
@ -19,6 +20,7 @@ const CustomTable = <T extends object>({
|
||||
columns,
|
||||
createFields,
|
||||
submitHandler,
|
||||
searchable = false
|
||||
}: CustomTableProps<T>) => {
|
||||
const [data, setData] = useState<(T & { id: number })[]>(initialData);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
@ -138,11 +140,12 @@ const CustomTable = <T extends object>({
|
||||
display: 'flex',
|
||||
gap: '1rem'
|
||||
}}>
|
||||
<Input
|
||||
placeholder="Поиск"
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
/>
|
||||
{searchable &&
|
||||
<Input
|
||||
placeholder="Поиск"
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
/>}
|
||||
|
||||
{createFields && submitHandler &&
|
||||
<Dialog>
|
||||
|
@ -156,7 +156,16 @@ export default function FolderViewer() {
|
||||
|
||||
if (foldersLoading || documentsLoading) {
|
||||
return (
|
||||
<Spinner />
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: '1rem',
|
||||
}}>
|
||||
<Spinner />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,6 @@ import { addInteractions, handleImageDrop, loadFeatures, processFigure, processL
|
||||
import useSWR, { SWRConfiguration } from 'swr'
|
||||
import { fetcher } from '../../http/axiosInstance'
|
||||
import { BASE_URL } from '../../constants'
|
||||
import { useMantineColorScheme } from '@mantine/core'
|
||||
import { IconBoxMultiple, IconBoxPadding, IconChevronLeft, IconPlus, IconUpload, } from '@tabler/icons-react'
|
||||
import { ICitySettings, IFigure, ILine } from '../../interfaces/gis'
|
||||
import axios from 'axios'
|
||||
@ -34,8 +33,9 @@ import GisService from '../../services/GisService'
|
||||
import MapMode from './MapMode'
|
||||
import { satMapsProviders, schemas } from '../../constants/map'
|
||||
import MapPrint from './MapPrint/MapPrint'
|
||||
import { Field, Menu, MenuButton, MenuList, MenuPopover, MenuTrigger, Combobox, Option, Button, Divider, Spinner, Portal } from '@fluentui/react-components'
|
||||
import { Field, Menu, MenuButton, MenuList, MenuPopover, MenuTrigger, Combobox, Option, Button, Divider, Spinner, Portal, Dropdown } from '@fluentui/react-components'
|
||||
import { IRegion } from '../../interfaces/fuel'
|
||||
import { useAppStore } from '../../store/app'
|
||||
|
||||
const swrOptions: SWRConfiguration = {
|
||||
revalidateOnFocus: false
|
||||
@ -60,8 +60,9 @@ const MapComponent = ({
|
||||
id: string,
|
||||
active: boolean,
|
||||
}) => {
|
||||
const { colorScheme } = useAppStore()
|
||||
|
||||
// Store
|
||||
const { colorScheme } = useMantineColorScheme()
|
||||
const { selectedYear, currentObjectId, selectedRegion, selectedDistrict } = useObjectsStore().id[id]
|
||||
const {
|
||||
mode, map, currentTool, alignMode, satMapsProvider,
|
||||
@ -559,7 +560,7 @@ const MapComponent = ({
|
||||
<Combobox
|
||||
placeholder="Схема"
|
||||
clearable
|
||||
style={{ width: "92px", minWidth: 'auto' }}
|
||||
style={{ minWidth: 'auto' }}
|
||||
value={selectedYear ? selectedYear.toString() : ""}
|
||||
onOptionSelect={(_ev, data) => {
|
||||
if (data.optionValue) {
|
||||
@ -585,25 +586,29 @@ const MapComponent = ({
|
||||
</MenuTrigger>
|
||||
|
||||
<MenuPopover>
|
||||
<MenuList>
|
||||
<MenuList style={{ padding: '1rem' }}>
|
||||
<Field>Настройка видимости слоёв</Field>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
||||
<Combobox
|
||||
defaultValue={satMapsProviders.find(provider => provider.value === satMapsProvider)?.label ?? ""}
|
||||
onOptionSelect={(_ev, data) => {
|
||||
if (data.optionValue) {
|
||||
setSatMapsProvider(id, data.optionValue as SatelliteMapsProvider);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{satMapsProviders.map((provider) => (
|
||||
<Option text={provider.label} key={provider.value} value={provider.value}>
|
||||
{provider.label}
|
||||
</Option>
|
||||
))}
|
||||
</Combobox>
|
||||
|
||||
<Field label="Спутниковые снимки">
|
||||
<Dropdown
|
||||
defaultValue={satMapsProviders.find(provider => provider.value === satMapsProvider)?.label}
|
||||
value={satMapsProviders.find(provider => provider.value === satMapsProvider)?.label}
|
||||
defaultSelectedOptions={[satMapsProvider]}
|
||||
selectedOptions={[satMapsProvider]}
|
||||
onOptionSelect={(_ev, data) => {
|
||||
if (data.optionValue) {
|
||||
setSatMapsProvider(id, data.optionValue as SatelliteMapsProvider);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{satMapsProviders.map((provider) => (
|
||||
<Option key={provider.value} text={provider.label} value={provider.value}>
|
||||
{provider.label}
|
||||
</Option>
|
||||
))}
|
||||
</Dropdown>
|
||||
</Field>
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
@ -652,7 +657,7 @@ const MapComponent = ({
|
||||
|
||||
<div style={{ position: 'absolute', width: '100%', height: '100%' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', height: '100%' }}>
|
||||
<div style={{ display: 'flex', width: '100%', height: '94%', padding: '0.5rem', flexGrow: 1 }}>
|
||||
<div style={{ display: 'flex', height: '94%', padding: '0.5rem', flexGrow: 1 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', maxWidth: '380px' }}>
|
||||
<div style={{ display: 'flex', width: '100%', height: '100%', gap: '0.5rem' }}>
|
||||
{selectedRegion && selectedDistrict && selectedYear &&
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useMantineColorScheme } from '@mantine/core'
|
||||
import useSWR from 'swr'
|
||||
import { fetcher } from '../../../http/axiosInstance'
|
||||
import { BASE_URL } from '../../../constants'
|
||||
import { Accordion, AccordionHeader, AccordionItem, AccordionPanel, ColorSwatch, Text } from '@fluentui/react-components'
|
||||
import { useAppStore } from '../../../store/app'
|
||||
|
||||
const MapLegend = ({
|
||||
selectedDistrict,
|
||||
@ -11,7 +11,7 @@ const MapLegend = ({
|
||||
selectedDistrict: number | null,
|
||||
selectedYear: number | null,
|
||||
}) => {
|
||||
const { colorScheme } = useMantineColorScheme()
|
||||
const { colorScheme } = useAppStore()
|
||||
|
||||
const { data: existingObjectsList } = useSWR(
|
||||
selectedYear && selectedDistrict ? `/general/objects/list?year=${selectedYear}&city_id=${selectedDistrict}&planning=0` : null,
|
||||
|
@ -1,49 +1,66 @@
|
||||
import { useMantineColorScheme } from '@mantine/core'
|
||||
import { IconArrowBackUp, IconArrowsMove, IconCircle, IconExclamationCircle, IconLine, IconPoint, IconPolygon, IconRuler, IconTransformPoint } from '@tabler/icons-react'
|
||||
import { getDraw, setCurrentTool, useMapStore } from '../../../store/map';
|
||||
import { saveFeatures } from '../mapUtils';
|
||||
import { Button } from '@fluentui/react-components';
|
||||
import { Button, Tooltip } from '@fluentui/react-components';
|
||||
import { useAppStore } from '../../../store/app';
|
||||
|
||||
const MapToolbar = ({
|
||||
map_id
|
||||
}: { map_id: string }) => {
|
||||
const { currentTool } = useMapStore().id[map_id]
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const { colorScheme } = useAppStore();
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', zIndex: 1, backdropFilter: 'blur(8px)', backgroundColor: colorScheme === 'light' ? '#FFFFFFAA' : '#000000AA', borderRadius: '4px' }}>
|
||||
<Button icon={<IconExclamationCircle />} appearance='transparent' onClick={() => saveFeatures(map_id)} />
|
||||
|
||||
<Button icon={<IconArrowBackUp />} appearance='transparent' onClick={() => getDraw(map_id)?.removeLastPoint()} />
|
||||
<Tooltip content={"Отмена"} relationship='label' hideDelay={0} showDelay={0} withArrow>
|
||||
<Button icon={<IconArrowBackUp />} appearance='transparent' onClick={() => getDraw(map_id)?.removeLastPoint()} />
|
||||
</Tooltip>
|
||||
|
||||
<Button icon={<IconTransformPoint />} appearance={currentTool === 'Edit' ? 'primary' : 'transparent'} onClick={() => {
|
||||
setCurrentTool(map_id, 'Edit')
|
||||
}} />
|
||||
<Tooltip content={"Редактировать"} relationship='label' hideDelay={0} showDelay={0} withArrow>
|
||||
<Button icon={<IconTransformPoint />} appearance={currentTool === 'Edit' ? 'primary' : 'transparent'} onClick={() => {
|
||||
setCurrentTool(map_id, 'Edit')
|
||||
}} />
|
||||
</Tooltip>
|
||||
|
||||
<Button icon={<IconPoint />} appearance={currentTool === 'Point' ? 'primary' : 'transparent'} onClick={() => {
|
||||
setCurrentTool(map_id, 'Point')
|
||||
}} />
|
||||
<Tooltip content={"Точка"} relationship='label' hideDelay={0} showDelay={0} withArrow>
|
||||
<Button icon={<IconPoint />} appearance={currentTool === 'Point' ? 'primary' : 'transparent'} onClick={() => {
|
||||
setCurrentTool(map_id, 'Point')
|
||||
}} />
|
||||
</Tooltip>
|
||||
|
||||
<Button icon={<IconLine />} appearance={currentTool === 'LineString' ? 'primary' : 'transparent'} onClick={() => {
|
||||
setCurrentTool(map_id, 'LineString')
|
||||
}} />
|
||||
|
||||
<Button icon={<IconPolygon />} appearance={currentTool === 'Polygon' ? 'primary' : 'transparent'} onClick={() => {
|
||||
setCurrentTool(map_id, 'Polygon')
|
||||
}} />
|
||||
<Tooltip content={"Линия"} relationship='label' hideDelay={0} showDelay={0} withArrow>
|
||||
<Button icon={<IconLine />} appearance={currentTool === 'LineString' ? 'primary' : 'transparent'} onClick={() => {
|
||||
setCurrentTool(map_id, 'LineString')
|
||||
}} />
|
||||
</Tooltip>
|
||||
|
||||
<Button icon={<IconCircle />} appearance={currentTool === 'Circle' ? 'primary' : 'transparent'} onClick={() => {
|
||||
setCurrentTool(map_id, 'Circle')
|
||||
}} />
|
||||
<Tooltip content={"Многоугольник"} relationship='label' hideDelay={0} showDelay={0} withArrow>
|
||||
<Button icon={<IconPolygon />} appearance={currentTool === 'Polygon' ? 'primary' : 'transparent'} onClick={() => {
|
||||
setCurrentTool(map_id, 'Polygon')
|
||||
}} />
|
||||
</Tooltip>
|
||||
|
||||
<Button icon={<IconArrowsMove />} appearance={currentTool === 'Mover' ? 'primary' : 'transparent'} onClick={() => {
|
||||
setCurrentTool(map_id, 'Mover')
|
||||
}} />
|
||||
<Tooltip content={"Окружность"} relationship='label' hideDelay={0} showDelay={0} withArrow>
|
||||
<Button icon={<IconCircle />} appearance={currentTool === 'Circle' ? 'primary' : 'transparent'} onClick={() => {
|
||||
setCurrentTool(map_id, 'Circle')
|
||||
}} />
|
||||
</Tooltip>
|
||||
|
||||
<Button icon={<IconRuler />} appearance={currentTool === 'Measure' ? 'primary' : 'transparent'} onClick={() => {
|
||||
setCurrentTool(map_id, 'Measure')
|
||||
}} />
|
||||
<Tooltip content={"Перемещение"} relationship='label' hideDelay={0} showDelay={0} withArrow>
|
||||
<Button icon={<IconArrowsMove />} appearance={currentTool === 'Mover' ? 'primary' : 'transparent'} onClick={() => {
|
||||
setCurrentTool(map_id, 'Mover')
|
||||
}} />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content={"Линейка"} relationship='label' hideDelay={0} showDelay={0} withArrow>
|
||||
<Button icon={<IconRuler />} appearance={currentTool === 'Measure' ? 'primary' : 'transparent'} onClick={() => {
|
||||
setCurrentTool(map_id, 'Measure')
|
||||
}} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -47,7 +47,7 @@ const pages = [
|
||||
component: <PasswordReset />,
|
||||
drawer: false,
|
||||
dashboard: false,
|
||||
enabled: true,
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
label: "Настройки",
|
||||
@ -137,7 +137,7 @@ const pages = [
|
||||
component: <MapLineTest />,
|
||||
drawer: true,
|
||||
dashboard: true,
|
||||
enabled: true,
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
label: "Монитор",
|
||||
@ -173,7 +173,7 @@ const pages = [
|
||||
component: <DBManager />,
|
||||
drawer: true,
|
||||
dashboard: true,
|
||||
enabled: true,
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
label: 'Fuel',
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { useMantineColorScheme } from '@mantine/core';
|
||||
import { Outlet, useNavigate } from 'react-router-dom';
|
||||
import { IconLogout, IconSettings, IconMoon, IconSun, IconMenu2, IconUser } from '@tabler/icons-react';
|
||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { IconLogout, IconSettings, IconMoon, IconSun, IconMenu2, IconUser, IconLogin } from '@tabler/icons-react';
|
||||
import { getUserData, logout, useAuthStore } from '../store/auth';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { UserData } from '../interfaces/auth';
|
||||
import { pages } from '../constants/app';
|
||||
import { Button, Image, makeStyles, Menu, MenuButton, MenuItem, MenuList, MenuPopover, MenuTrigger, Text } from '@fluentui/react-components';
|
||||
import { setColorScheme, useAppStore } from '../store/app';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
@ -49,6 +49,8 @@ const useStyles = makeStyles({
|
||||
|
||||
function DashboardLayout() {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const { colorScheme } = useAppStore()
|
||||
|
||||
const getPageTitle = () => {
|
||||
const currentPath = location.pathname
|
||||
@ -69,8 +71,6 @@ function DashboardLayout() {
|
||||
}
|
||||
}, [authStore])
|
||||
|
||||
const { colorScheme, setColorScheme } = useMantineColorScheme();
|
||||
|
||||
const classes = useStyles()
|
||||
|
||||
const [navbarOpen, setNavbarOpen] = useState(true)
|
||||
@ -80,7 +80,6 @@ function DashboardLayout() {
|
||||
<div className={classes.header}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
gap: '0.75rem',
|
||||
@ -96,29 +95,30 @@ function DashboardLayout() {
|
||||
|
||||
</div>
|
||||
|
||||
<div style={{ flexShrink: 0 }}>
|
||||
<Menu positioning={{ autoSize: true }}>
|
||||
<MenuTrigger>
|
||||
<MenuButton appearance='transparent' icon={<IconUser />}>{`${userData?.name} ${userData?.surname}`}</MenuButton>
|
||||
</MenuTrigger>
|
||||
<Menu positioning={{ autoSize: true }}>
|
||||
<MenuTrigger>
|
||||
<MenuButton appearance='transparent' icon={authStore.isAuthenticated ? <IconUser /> : <IconSettings />}>{authStore.isAuthenticated && `${userData?.name} ${userData?.surname}`}</MenuButton>
|
||||
</MenuTrigger>
|
||||
|
||||
<MenuPopover>
|
||||
<MenuList>
|
||||
<MenuItem icon={
|
||||
colorScheme === 'dark' ? <IconMoon /> : <IconSun />
|
||||
} onClick={() => colorScheme === 'dark' ? setColorScheme('light') : setColorScheme('dark')}>Тема: {colorScheme === 'dark' ? 'тёмная' : 'светлая'}</MenuItem>
|
||||
<MenuItem icon={<IconSettings />} onClick={() => navigate('/settings')}>Настройки профиля</MenuItem>
|
||||
<MenuItem icon={<IconLogout />} onClick={() => {
|
||||
logout()
|
||||
<MenuPopover>
|
||||
<MenuList>
|
||||
{!authStore.isAuthenticated && <MenuItem icon={<IconLogin />} onClick={() => navigate('/auth/signin')}>Войти</MenuItem>}
|
||||
|
||||
<MenuItem icon={colorScheme === 'dark' ? <IconMoon /> : <IconSun />} onClick={() => colorScheme === 'dark' ? setColorScheme('light') : setColorScheme('dark')}>Тема: {colorScheme === 'dark' ? 'тёмная' : 'светлая'}</MenuItem>
|
||||
{authStore.isAuthenticated && <MenuItem icon={<IconSettings />} onClick={() => navigate('/settings')}>Настройки профиля</MenuItem>}
|
||||
{authStore.isAuthenticated && <MenuItem icon={<IconLogout />} onClick={() => {
|
||||
logout()
|
||||
|
||||
if (pages.find(page => page.path === '/auth/signin')?.enabled) {
|
||||
navigate("/auth/signin")
|
||||
}}>Выход</MenuItem>
|
||||
<MenuItem icon={<Image src={'/logo2.svg'} width={24} />}>
|
||||
0.1.0
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</MenuPopover>
|
||||
</Menu>
|
||||
</div>
|
||||
}
|
||||
}}>Выход</MenuItem>}
|
||||
<MenuItem icon={<Image src={'/logo2.svg'} width={24} />}>
|
||||
0.1.0
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</MenuPopover>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -127,7 +127,7 @@ function DashboardLayout() {
|
||||
maxWidth: navbarOpen ? '200px' : '2.70rem',
|
||||
}}>
|
||||
{pages.filter((page) => page.drawer).filter((page) => page.enabled).map((item) => (
|
||||
<Button key={item.path} style={{ paddingLeft: '0.5rem', flexShrink: 0, flexWrap: 'nowrap', textWrap: 'nowrap', borderRadius: 0 }} appearance='subtle' onClick={() => navigate(item.path)}>
|
||||
<Button key={item.path} style={{ paddingLeft: '0.5rem', flexShrink: 0, flexWrap: 'nowrap', textWrap: 'nowrap', borderRadius: 0 }} appearance={location.pathname === item.path ? 'primary' : 'subtle'} onClick={() => navigate(item.path)}>
|
||||
<div style={{ display: 'flex', }}>
|
||||
{item.icon}
|
||||
</div>
|
||||
@ -143,9 +143,6 @@ function DashboardLayout() {
|
||||
</div>
|
||||
|
||||
</Button>
|
||||
// <NavItem style={{ flexShrink: 0, flexWrap: 'nowrap', textWrap: 'nowrap' }} onClick={() => navigate(item.path)} icon={item.icon} value={item.path}>
|
||||
// {item.label}
|
||||
// </NavItem>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -156,116 +153,6 @@ function DashboardLayout() {
|
||||
|
||||
</div>
|
||||
)
|
||||
|
||||
// return (
|
||||
// <AppShell
|
||||
// header={{ height: 60 }}
|
||||
// navbar={{
|
||||
// width: desktopOpened ? 200 : 50,
|
||||
// breakpoint: 'sm',
|
||||
// collapsed: { mobile: !mobileOpened },
|
||||
// }}
|
||||
// >
|
||||
// <AppShell.Header>
|
||||
// <Flex h="100%" px="md" w='100%' align='center' gap='sm'>
|
||||
// <Group>
|
||||
// <Burger opened={mobileOpened} onClick={toggleMobile} hiddenFrom="sm" size="sm" />
|
||||
// <Burger opened={desktopOpened} onClick={toggleDesktop} visibleFrom="sm" size="sm" />
|
||||
// </Group>
|
||||
|
||||
// <Group w='auto'>
|
||||
// <Text fw='600'>{getPageTitle()}</Text>
|
||||
// </Group>
|
||||
|
||||
// <Group id='header-portal' w='auto' ml='auto'>
|
||||
|
||||
// </Group>
|
||||
|
||||
// <Group style={{ flexShrink: 0 }}>
|
||||
// <Menu
|
||||
// width={260}
|
||||
// position="bottom-end"
|
||||
// transitionProps={{ transition: 'pop-top-right' }}
|
||||
// withinPortal
|
||||
// >
|
||||
// <Menu.Target>
|
||||
// <Button variant='transparent'>
|
||||
// <Group gap={7}>
|
||||
// <Avatar name={`${userData?.name} ${userData?.surname}`} radius="xl" size={30} />
|
||||
// <Text fw={500} size="sm" lh={1} mr={3}>
|
||||
// {`${userData?.name} ${userData?.surname}`}
|
||||
// </Text>
|
||||
// <IconChevronDown style={{ width: rem(12), height: rem(12) }} stroke={1.5} />
|
||||
// </Group>
|
||||
// </Button>
|
||||
// </Menu.Target>
|
||||
// <Menu.Dropdown>
|
||||
// <Menu.Label>{userData?.login}</Menu.Label>
|
||||
// <Menu.Item
|
||||
// leftSection={
|
||||
// colorScheme === 'dark' ? <IconMoon style={{ width: rem(16), height: rem(16) }} stroke={1.5} /> : <IconSun style={{ width: rem(16), height: rem(16) }} stroke={1.5} />
|
||||
// }
|
||||
// onClick={() => colorScheme === 'dark' ? setColorScheme('light') : setColorScheme('dark')}
|
||||
// >
|
||||
// Тема: {colorScheme === 'dark' ? 'тёмная' : 'светлая'}
|
||||
// </Menu.Item>
|
||||
// <Menu.Item
|
||||
// leftSection={
|
||||
// <IconSettings style={{ width: rem(16), height: rem(16) }} stroke={1.5} />
|
||||
// }
|
||||
// onClick={() => navigate('/settings')}
|
||||
// >
|
||||
// Настройки профиля
|
||||
// </Menu.Item>
|
||||
// <Menu.Item
|
||||
// onClick={() => {
|
||||
// logout()
|
||||
// navigate("/auth/signin")
|
||||
// }}
|
||||
// leftSection={<IconLogout style={{ width: rem(16), height: rem(16) }} stroke={1.5} />}
|
||||
// >
|
||||
// Выход
|
||||
// </Menu.Item>
|
||||
|
||||
// <Menu.Item>
|
||||
// <Flex gap='sm' align='center'>
|
||||
// <Image src={'/logo2.svg'} w={32} />
|
||||
// <Text>0.1.0</Text>
|
||||
// </Flex>
|
||||
// </Menu.Item>
|
||||
// </Menu.Dropdown>
|
||||
// </Menu>
|
||||
// </Group>
|
||||
// </Flex>
|
||||
// </AppShell.Header>
|
||||
// <AppShell.Navbar style={{ transition: "width 0.2s ease" }}>
|
||||
// {pages.filter((page) => page.drawer).filter((page) => page.enabled).map((item) => (
|
||||
// <NavLink
|
||||
// key={item.path}
|
||||
// onClick={() => navigate(item.path)}
|
||||
// label={item.label}
|
||||
// leftSection={item.icon}
|
||||
// active={location.pathname === item.path}
|
||||
// style={{ textWrap: 'nowrap' }}
|
||||
// // styles={(theme, { active }) => ({
|
||||
// // root: {
|
||||
// // color: active ? theme.colors.blue[6] : theme.colors.dark[5],
|
||||
// // fontWeight: active ? "bold" : "normal",
|
||||
// // },
|
||||
// // leftSection: {
|
||||
// // color: active ? theme.colors.blue[6] : theme.colors.dark[5], // Icon color
|
||||
// // }
|
||||
// // })}
|
||||
// />
|
||||
// ))}
|
||||
// </AppShell.Navbar>
|
||||
// <AppShell.Main>
|
||||
// <Flex bg={colorScheme === 'dark' ? undefined : '#E8E8E8'} w={{ sm: desktopOpened ? 'calc(100% - 200px)' : 'calc(100% - 50px)', base: '100%' }} h={'calc(100% - 60px)'} style={{ transition: "width 0.2s ease" }} pos={'fixed'}>
|
||||
// <Outlet />
|
||||
// </Flex>
|
||||
// </AppShell.Main>
|
||||
// </AppShell>
|
||||
// )
|
||||
}
|
||||
|
||||
export default DashboardLayout
|
@ -1,44 +1,12 @@
|
||||
import "@fontsource/inter";
|
||||
import '@mantine/core/styles.css';
|
||||
import '@mantine/dates/styles.css';
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
import './index.css'
|
||||
import { createTheme, DEFAULT_THEME, MantineProvider, mergeMantineTheme } from '@mantine/core';
|
||||
import 'dayjs/locale/ru';
|
||||
import { DatesProvider } from "@mantine/dates";
|
||||
import { FluentProvider, webLightTheme } from '@fluentui/react-components';
|
||||
|
||||
const overrides = createTheme({
|
||||
// Set this color to `--mantine-color-body` CSS variable
|
||||
white: '#F0F0F0',
|
||||
colors: {
|
||||
// ...
|
||||
},
|
||||
})
|
||||
|
||||
const theme = mergeMantineTheme(DEFAULT_THEME, overrides);
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<MantineProvider theme={theme}>
|
||||
<FluentProvider theme={webLightTheme}>
|
||||
<DatesProvider settings={{ locale: 'ru' }}>
|
||||
<App />
|
||||
</DatesProvider>
|
||||
</FluentProvider>
|
||||
</MantineProvider>
|
||||
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
|
||||
// ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
// <React.StrictMode>
|
||||
// <MantineProvider theme={theme}>
|
||||
// <DatesProvider settings={{ locale: 'ru' }}>
|
||||
// <App />
|
||||
// </DatesProvider>
|
||||
// </MantineProvider>
|
||||
// </React.StrictMode>,
|
||||
// )
|
||||
)
|
@ -1,15 +1,15 @@
|
||||
import { Modal, useMantineColorScheme } from "@mantine/core";
|
||||
import { IconMathMax, IconPlus, IconTableMinus } from "@tabler/icons-react";
|
||||
import { FuelExpenseDtoHeaders, FuelLimitDtoHeaders } from "../dto/fuel/fuel.dto";
|
||||
import useSWR from "swr";
|
||||
import { fetcher } from "../http/axiosInstanceNest";
|
||||
import { useEffect, useState } from "react";
|
||||
import { DateInput } from '@mantine/dates'
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
|
||||
import { AgGridReact } from "ag-grid-react";
|
||||
import { AllCommunityModule, ColDef, ModuleRegistry } from 'ag-grid-community'
|
||||
import { Button, Field, Input, Spinner, Tab, TabList } from "@fluentui/react-components";
|
||||
import { CalendarStrings, DatePicker, defaultDatePickerStrings } from "@fluentui/react-datepicker-compat"
|
||||
import { Button, Dialog, DialogSurface, DialogTitle, DialogTrigger, Field, Input, Spinner, Tab, TabList } from "@fluentui/react-components";
|
||||
import { useAppStore } from "../store/app";
|
||||
|
||||
ModuleRegistry.registerModules([AllCommunityModule])
|
||||
|
||||
@ -94,9 +94,7 @@ export default function FuelPage() {
|
||||
|
||||
const { isLoading } = useSWR(currentTab.get, () => fetcher(currentTab.get), { revalidateOnFocus: false })
|
||||
|
||||
const [openCreateModel, setOpenCreateModal] = useState(false)
|
||||
|
||||
const { colorScheme } = useMantineColorScheme()
|
||||
const { colorScheme } = useAppStore()
|
||||
|
||||
useEffect(() => {
|
||||
if (colorScheme === 'dark') {
|
||||
@ -108,8 +106,6 @@ export default function FuelPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ModalCreate openedCreateModal={openCreateModel} closeCreateModal={() => setOpenCreateModal(false)} currentTab={currentTab} />
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', width: '100%' }}>
|
||||
<TabList defaultValue={tables[0].value} selectedValue={currentTab.value}>
|
||||
{tables.map((table, index) => (
|
||||
@ -120,9 +116,22 @@ export default function FuelPage() {
|
||||
</TabList>
|
||||
|
||||
<div style={{ display: 'flex', padding: '1rem' }}>
|
||||
<Button appearance='primary' icon={<IconPlus />} onClick={() => setOpenCreateModal(true)}>
|
||||
Добавить
|
||||
</Button>
|
||||
<Dialog>
|
||||
<DialogTrigger>
|
||||
<Button
|
||||
appearance='primary'
|
||||
icon={<IconPlus />}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
Добавить
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogSurface>
|
||||
<DialogTitle>Добавление объекта</DialogTitle>
|
||||
<ModalCreate currentTab={currentTab} />
|
||||
</DialogSurface>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
{tables.map((table, index) => {
|
||||
@ -133,7 +142,7 @@ export default function FuelPage() {
|
||||
<Spinner />
|
||||
</div>
|
||||
:
|
||||
<>
|
||||
<div style={{ width: '100%', height: '100%', padding: '1rem' }}>
|
||||
<AgGridReact
|
||||
key={index}
|
||||
//rowData={data}
|
||||
@ -148,7 +157,7 @@ export default function FuelPage() {
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -159,12 +168,8 @@ export default function FuelPage() {
|
||||
}
|
||||
|
||||
const ModalCreate = ({
|
||||
openedCreateModal,
|
||||
closeCreateModal,
|
||||
currentTab
|
||||
}: {
|
||||
openedCreateModal: boolean
|
||||
closeCreateModal: () => void
|
||||
currentTab: ITableSchema
|
||||
}) => {
|
||||
const { register, handleSubmit,
|
||||
@ -177,38 +182,72 @@ const ModalCreate = ({
|
||||
console.log('Values to submit:', values)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal withinPortal opened={openedCreateModal} onClose={closeCreateModal}>
|
||||
<form style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }} onSubmit={handleSubmit(onSubmit)}>
|
||||
{currentTab.post_include.map((header, index) => {
|
||||
switch (header.field_type) {
|
||||
case 'date':
|
||||
return (
|
||||
<DateInput label={header.field} />
|
||||
)
|
||||
case 'text':
|
||||
return (
|
||||
<Field key={index} label={header.field}>
|
||||
<Input {...register(header.field, {
|
||||
required: true
|
||||
})} />
|
||||
</Field>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<Field key={index} label={header.field}>
|
||||
<Input {...register(header.field, {
|
||||
required: true
|
||||
})} />
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
})}
|
||||
const localizedStrings: CalendarStrings = {
|
||||
...defaultDatePickerStrings,
|
||||
days: [
|
||||
'Воскресенье',
|
||||
'Понедельник',
|
||||
'Вторник',
|
||||
'Среда',
|
||||
'Четверг',
|
||||
'Пятница',
|
||||
'Суббота'
|
||||
],
|
||||
shortDays: ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
|
||||
months: [
|
||||
"Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Ноябрь", "Декабрь"
|
||||
],
|
||||
shortMonths: [
|
||||
"Янв",
|
||||
"Фев",
|
||||
"Мар",
|
||||
"Апр",
|
||||
"Май",
|
||||
"Июн",
|
||||
"Июл",
|
||||
"Авг",
|
||||
"Сен",
|
||||
"Ноя",
|
||||
"Дек",
|
||||
]
|
||||
}
|
||||
|
||||
<Button style={{ marginTop: '2rem' }} appearance="primary" type='submit'>
|
||||
Добавить
|
||||
</Button>
|
||||
</form>
|
||||
</Modal>
|
||||
return (
|
||||
<form style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }} onSubmit={handleSubmit(onSubmit)}>
|
||||
{currentTab.post_include.map((header, index) => {
|
||||
switch (header.field_type) {
|
||||
case 'date':
|
||||
return (
|
||||
<Field key={index} label={header.field}>
|
||||
<DatePicker
|
||||
strings={localizedStrings}
|
||||
{...register(header.field, {
|
||||
required: true
|
||||
})} />
|
||||
</Field>
|
||||
)
|
||||
case 'text':
|
||||
return (
|
||||
<Field key={index} label={header.field}>
|
||||
<Input {...register(header.field, {
|
||||
required: true
|
||||
})} />
|
||||
</Field>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<Field key={index} label={header.field}>
|
||||
<Input {...register(header.field, {
|
||||
required: true
|
||||
})} />
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
})}
|
||||
|
||||
<Button style={{ marginTop: '2rem' }} appearance="primary" type='submit'>
|
||||
Добавить
|
||||
</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import createReport, { listCommands } from 'docx-templates'
|
||||
import { Dropzone, IMAGE_MIME_TYPE } from '@mantine/dropzone'
|
||||
import { IconFileTypeDocx, IconPlus, IconUpload, IconX } from "@tabler/icons-react"
|
||||
import { IconPlus, IconX } from "@tabler/icons-react"
|
||||
import { CommandSummary } from "docx-templates/lib/types"
|
||||
import { Control, Controller, FieldValues, SubmitHandler, useFieldArray, useForm, UseFormRegister } from "react-hook-form"
|
||||
import { Button, Field, Input, Text } from "@fluentui/react-components"
|
||||
import { Button, Field, Input, Text, tokens, useId } from "@fluentui/react-components"
|
||||
import { ArrowUploadRegular } from "@fluentui/react-icons"
|
||||
|
||||
const xslTemplate = `<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsl:stylesheet version="1.0" xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
@ -1062,6 +1062,8 @@ const FormLoop = ({
|
||||
)
|
||||
}
|
||||
|
||||
const IMAGE_MIME_TYPE = ["image/png", "image/jpeg", "image/jpg", "image/webp"];
|
||||
|
||||
const renderCommand = (
|
||||
control: Control<FieldValues, any>,
|
||||
register: UseFormRegister<FieldValues>,
|
||||
@ -1080,50 +1082,72 @@ const renderCommand = (
|
||||
}
|
||||
|
||||
if (command.type === 'IMAGE') {
|
||||
const inputId = useId("file-input");
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Text size={200} weight="semibold">{command.code}</Text>
|
||||
<Controller
|
||||
key={key}
|
||||
name={name}
|
||||
control={control}
|
||||
render={({ field: { onChange } }) => (
|
||||
<Dropzone
|
||||
accept={IMAGE_MIME_TYPE}
|
||||
maxSize={5 * 1024 ** 2}
|
||||
onReject={(files) => console.log('rejected files', files)}
|
||||
onDrop={(files) => {
|
||||
console.log(files[0])
|
||||
files[0].arrayBuffer().then(res => {
|
||||
onChange({
|
||||
width: 6,
|
||||
height: 6,
|
||||
data: new Uint8Array(res),
|
||||
extension: files[0]?.path?.match(/\.[^.]+$/)?.[0] || ""
|
||||
})
|
||||
})
|
||||
}}
|
||||
maxFiles={1}
|
||||
>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '2rem', minHeight: '220px', pointerEvents: 'none' }}>
|
||||
<Dropzone.Accept>
|
||||
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
||||
</Dropzone.Accept>
|
||||
<Dropzone.Reject>
|
||||
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
|
||||
</Dropzone.Reject>
|
||||
<Dropzone.Idle>
|
||||
<IconFileTypeDocx size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
|
||||
</Dropzone.Idle>
|
||||
render={({ field: { onChange } }) => {
|
||||
const handleFiles = (files: FileList | null) => {
|
||||
if (!files || files.length === 0) return;
|
||||
const file = files[0];
|
||||
if (!IMAGE_MIME_TYPE.includes(file.type)) {
|
||||
console.log("Rejected file:", file);
|
||||
return;
|
||||
}
|
||||
|
||||
<div>
|
||||
<Text size={300}>
|
||||
Перетащите файлы сюда или нажмите, чтобы выбрать их
|
||||
</Text>
|
||||
</div>
|
||||
file.arrayBuffer().then((res) => {
|
||||
onChange({
|
||||
width: 6,
|
||||
height: 6,
|
||||
data: new Uint8Array(res),
|
||||
extension: file.name.match(/\.[^.]+$/)?.[0] || "",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
handleFiles(e.dataTransfer.files);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onDrop={handleDrop}
|
||||
style={{
|
||||
border: `2px dashed ${tokens.colorNeutralStroke1}`,
|
||||
borderRadius: tokens.borderRadiusLarge,
|
||||
minHeight: "220px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: "1rem",
|
||||
padding: "1rem",
|
||||
textAlign: "center",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => document.getElementById(inputId)?.click()}
|
||||
>
|
||||
<input
|
||||
id={inputId}
|
||||
type="file"
|
||||
accept={IMAGE_MIME_TYPE.join(",")}
|
||||
style={{ display: "none" }}
|
||||
onChange={(e) => handleFiles(e.target.files)}
|
||||
/>
|
||||
|
||||
<ArrowUploadRegular fontSize={40} color={tokens.colorBrandForeground1} />
|
||||
<Text size={300}>
|
||||
Перетащите изображение сюда или нажмите, чтобы выбрать
|
||||
</Text>
|
||||
</div>
|
||||
</Dropzone>
|
||||
)}
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@ -2,7 +2,7 @@ import { useRoles } from '../hooks/swrHooks'
|
||||
import { CreateField } from '../interfaces/create'
|
||||
import RoleService from '../services/RoleService'
|
||||
import CustomTable from '../components/CustomTable'
|
||||
import { Spinner } from '@fluentui/react-components'
|
||||
import { Link, Spinner } from '@fluentui/react-components'
|
||||
|
||||
export default function Roles() {
|
||||
const { roles, isError, isLoading } = useRoles()
|
||||
@ -12,8 +12,37 @@ export default function Roles() {
|
||||
{ key: 'description', headerName: 'Описание', type: 'string', required: false, defaultValue: '' },
|
||||
]
|
||||
|
||||
if (isError) return <div>Произошла ошибка при получении данных.</div>
|
||||
if (isLoading) return <Spinner />
|
||||
const handleError = (error: any) => {
|
||||
if (error?.response?.status === 401) {
|
||||
return (
|
||||
<Link href="/auth/signin">
|
||||
Войдите, чтобы продолжить
|
||||
</Link>
|
||||
)
|
||||
} else {
|
||||
return "Произошла ошибка при получении данных."
|
||||
}
|
||||
}
|
||||
|
||||
if (isError) return (
|
||||
<div style={{ padding: '1rem' }}>
|
||||
{handleError(isError)}
|
||||
</div>
|
||||
)
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: '1rem',
|
||||
}}>
|
||||
<Spinner />
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
|
@ -4,7 +4,7 @@ import { useEffect, useState } from "react"
|
||||
import { CreateField } from "../interfaces/create"
|
||||
import UserService from "../services/UserService"
|
||||
import CustomTable from "../components/CustomTable"
|
||||
import { Spinner } from "@fluentui/react-components"
|
||||
import { Link, Spinner } from "@fluentui/react-components"
|
||||
import { IUser } from "../interfaces/user"
|
||||
|
||||
export default function Users() {
|
||||
@ -37,15 +37,34 @@ export default function Users() {
|
||||
{ key: 'password', headerName: 'Пароль', type: 'string', required: true, defaultValue: '' },
|
||||
]
|
||||
|
||||
const handleError = (error: any) => {
|
||||
if (error?.response?.status === 401) {
|
||||
return (
|
||||
<Link href="/auth/signin">
|
||||
Войдите, чтобы продолжить
|
||||
</Link>
|
||||
)
|
||||
} else {
|
||||
return "Произошла ошибка при получении данных."
|
||||
}
|
||||
}
|
||||
|
||||
if (isError) return (
|
||||
<div>
|
||||
Произошла ошибка при получении данных.
|
||||
<div style={{ padding: '1rem' }}>
|
||||
{handleError(isError)}
|
||||
</div>
|
||||
)
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: '1rem',
|
||||
}}>
|
||||
<Spinner />
|
||||
</div>
|
||||
)
|
||||
|
@ -100,7 +100,8 @@ const SignIn = () => {
|
||||
{pages.find(page => page.path === '/auth/signup')?.enabled &&
|
||||
<Button as='a' href='/auth/signup' type="button" appearance='subtle'>
|
||||
Регистрация
|
||||
</Button>}
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -2,7 +2,10 @@ import { create } from 'zustand';
|
||||
|
||||
export type Mode = 'edit' | 'view'
|
||||
|
||||
export type ColorScheme = 'light' | 'dark' | 'auto'
|
||||
|
||||
export interface AppState {
|
||||
colorScheme: ColorScheme,
|
||||
mapTab: Record<string, {
|
||||
year: number | null,
|
||||
region: number | null,
|
||||
@ -12,10 +15,20 @@ export interface AppState {
|
||||
}
|
||||
|
||||
export const useAppStore = create<AppState>(() => ({
|
||||
colorScheme: 'auto',
|
||||
currentTab: null,
|
||||
mapTab: {}
|
||||
}))
|
||||
|
||||
const getColorScheme = () => {
|
||||
useAppStore.getState().colorScheme
|
||||
}
|
||||
|
||||
const setColorScheme = (colorScheme: ColorScheme) => {
|
||||
useAppStore.setState(() => ({ colorScheme: colorScheme }))
|
||||
localStorage.setItem('colorScheme', colorScheme.toString())
|
||||
}
|
||||
|
||||
const getCurrentTab = () => useAppStore.getState().currentTab
|
||||
const setCurrentTab = (id: string | null) => useAppStore.setState(() => ({ currentTab: id }))
|
||||
|
||||
@ -39,5 +52,7 @@ export {
|
||||
deleteMapTab,
|
||||
getCurrentTab,
|
||||
setCurrentTab,
|
||||
setMapTabYear
|
||||
setMapTabYear,
|
||||
getColorScheme,
|
||||
setColorScheme
|
||||
}
|
Reference in New Issue
Block a user